Compare commits
4 commits
29b1a854c5
...
22327a71b3
| Author | SHA1 | Date | |
|---|---|---|---|
| 22327a71b3 | |||
| 17c701a7d6 | |||
| 89045ccbcc | |||
| 12cc715873 |
14 changed files with 470 additions and 53 deletions
36
game/main.ts
36
game/main.ts
|
|
@ -1,26 +1,48 @@
|
||||||
import { cls, print, spr, use_texture } from "./graphics";
|
import { cls, print, spr, use_texture, Texture } from "./graphics";
|
||||||
import { load_texture } from "./assets";
|
import { load_texture } from "./assets";
|
||||||
import { since_start } from "./time";
|
import { since_start } from "./time";
|
||||||
import { btn, Button } from "./input";
|
import { btn, Button } from "./input";
|
||||||
|
// import { load_string } from "./io";
|
||||||
import { new_v2, vadd, vmul, vnorm } from "./vector";
|
import { new_v2, vadd, vmul, vnorm } from "./vector";
|
||||||
|
|
||||||
|
/// TODO: Support reload by saving and restoring state from init/update/restore.
|
||||||
|
|
||||||
/// A nice looping frame counter.
|
/// A nice looping frame counter.
|
||||||
let clock = 0;
|
let clock = 0;
|
||||||
|
|
||||||
let bot_sprite: number | undefined = undefined;
|
let bot_sprite: Texture | undefined = undefined;
|
||||||
|
|
||||||
// Note zelda overworld is 16x8 screens
|
// Note zelda overworld is 16x8 screens
|
||||||
// zelda screen is 16x11 tiles
|
// zelda screen is 16x11 tiles
|
||||||
// from a feeling point of view this is sufficient, apparently :D
|
// from a feeling point of view this is sufficient, apparently :D
|
||||||
|
|
||||||
export function init() {
|
let loaded = false;
|
||||||
print("Hello world!");
|
|
||||||
|
|
||||||
|
async function load_map(path: string) {
|
||||||
|
// print("Loading map:", path);
|
||||||
|
// const blob = await load_string(path);
|
||||||
|
// const map = JSON.parse(blob);
|
||||||
|
// print("Loaded map:", map);
|
||||||
|
}
|
||||||
|
|
||||||
|
function load_assets() {
|
||||||
// Start this load, but then...
|
// Start this load, but then...
|
||||||
load_texture("./bot.png").then((n) => {
|
let texture_load = load_texture("./bot.png").then((n) => {
|
||||||
print("Bot loaded at", since_start());
|
print("Bot loaded at", since_start());
|
||||||
bot_sprite = n;
|
bot_sprite = n;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let map_load = load_map("./overworld.ldtk");
|
||||||
|
|
||||||
|
Promise.all([texture_load, map_load]).then(() => {
|
||||||
|
loaded = true;
|
||||||
|
print("All are loaded.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function init() {
|
||||||
|
print("Hello world!");
|
||||||
|
load_assets();
|
||||||
}
|
}
|
||||||
|
|
||||||
const friction = 0.6;
|
const friction = 0.6;
|
||||||
|
|
@ -80,6 +102,10 @@ const robo_info = {
|
||||||
|
|
||||||
export function draw() {
|
export function draw() {
|
||||||
cls(0.1, 0.2, 0.3);
|
cls(0.1, 0.2, 0.3);
|
||||||
|
if (!loaded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (bot_sprite != undefined) {
|
if (bot_sprite != undefined) {
|
||||||
// ...it gets resolved here?
|
// ...it gets resolved here?
|
||||||
use_texture(bot_sprite);
|
use_texture(bot_sprite);
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ pub use atom::{Atom, AtomRef};
|
||||||
pub use class::{Class, ClassID};
|
pub use class::{Class, ClassID};
|
||||||
pub use context::{Context, ContextRef, EvalFlags};
|
pub use context::{Context, ContextRef, EvalFlags};
|
||||||
pub use conversion::*;
|
pub use conversion::*;
|
||||||
pub use promise::Promise;
|
pub use promise::{DefaultRejectedPromiseTracker, Promise, RejectedPromiseTracker};
|
||||||
pub use runtime::Runtime;
|
pub use runtime::Runtime;
|
||||||
pub use value::{Value, ValueRef, ValueType};
|
pub use value::{Value, ValueRef, ValueType};
|
||||||
|
|
||||||
|
|
@ -77,6 +77,12 @@ impl From<std::io::Error> for Error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<std::str::Utf8Error> for Error {
|
||||||
|
fn from(e: std::str::Utf8Error) -> Self {
|
||||||
|
Error::ConversionError(e.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub type Result<T> = core::result::Result<T, Error>;
|
pub type Result<T> = core::result::Result<T, Error>;
|
||||||
pub type ValueResult = core::result::Result<Value, Error>;
|
pub type ValueResult = core::result::Result<Value, Error>;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{ContextRef, ValueResult};
|
use crate::{ContextRef, ValueRef, ValueResult};
|
||||||
use std::sync::atomic::{AtomicU64, Ordering};
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
use std::sync::mpsc::Sender;
|
use std::sync::mpsc::Sender;
|
||||||
|
|
||||||
|
|
@ -72,3 +72,53 @@ impl Drop for Promise {
|
||||||
assert!(self.complete);
|
assert!(self.complete);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait RejectedPromiseTracker {
|
||||||
|
fn on_rejected_promise(
|
||||||
|
&self,
|
||||||
|
ctx: &ContextRef,
|
||||||
|
promise: &ValueRef,
|
||||||
|
reason: &ValueRef,
|
||||||
|
is_handled: bool,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> RejectedPromiseTracker for T
|
||||||
|
where
|
||||||
|
T: Fn(&ContextRef, &ValueRef, &ValueRef, bool) -> (),
|
||||||
|
{
|
||||||
|
fn on_rejected_promise(
|
||||||
|
&self,
|
||||||
|
ctx: &ContextRef,
|
||||||
|
promise: &ValueRef,
|
||||||
|
reason: &ValueRef,
|
||||||
|
is_handled: bool,
|
||||||
|
) {
|
||||||
|
self(ctx, promise, reason, is_handled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DefaultRejectedPromiseTracker {}
|
||||||
|
|
||||||
|
impl DefaultRejectedPromiseTracker {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
DefaultRejectedPromiseTracker {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RejectedPromiseTracker for DefaultRejectedPromiseTracker {
|
||||||
|
fn on_rejected_promise(
|
||||||
|
&self,
|
||||||
|
ctx: &ContextRef,
|
||||||
|
_promise: &ValueRef,
|
||||||
|
reason: &ValueRef,
|
||||||
|
is_handled: bool,
|
||||||
|
) {
|
||||||
|
if !is_handled {
|
||||||
|
let reason_str = reason.to_string(ctx).expect(
|
||||||
|
"Unhandled rejected promise: reason unknown: unable to convert reason to string",
|
||||||
|
);
|
||||||
|
panic!("Unhandled rejected promise: {reason_str}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
module::loader::{load_module, DefaultModuleLoader, ModuleLoader},
|
module::loader::{load_module, DefaultModuleLoader, ModuleLoader},
|
||||||
promise::{PromiseEvent, PromiseHandle},
|
promise::{PromiseEvent, PromiseHandle},
|
||||||
ContextRef, Promise, Result, Value,
|
ContextRef, DefaultRejectedPromiseTracker, Promise, RejectedPromiseTracker, Result, Value,
|
||||||
|
ValueRef,
|
||||||
};
|
};
|
||||||
use oden_js_sys as sys;
|
use oden_js_sys as sys;
|
||||||
use std::cell::{RefCell, RefMut};
|
use std::cell::{RefCell, RefMut};
|
||||||
|
|
@ -45,20 +46,19 @@ struct PrivateState {
|
||||||
promise_send: Sender<(PromiseHandle, PromiseEvent)>,
|
promise_send: Sender<(PromiseHandle, PromiseEvent)>,
|
||||||
promise_recv: Receiver<(PromiseHandle, PromiseEvent)>,
|
promise_recv: Receiver<(PromiseHandle, PromiseEvent)>,
|
||||||
promise_table: HashMap<PromiseHandle, PromiseEntry>, // !
|
promise_table: HashMap<PromiseHandle, PromiseEntry>, // !
|
||||||
|
rejection_tracker: Arc<Box<dyn RejectedPromiseTracker>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PrivateState {
|
impl PrivateState {
|
||||||
pub fn new<T>(loader: T) -> Box<RefCell<Self>>
|
pub fn new() -> Box<RefCell<Self>> {
|
||||||
where
|
|
||||||
T: ModuleLoader + 'static,
|
|
||||||
{
|
|
||||||
let (send, recv) = channel();
|
let (send, recv) = channel();
|
||||||
Box::new(RefCell::new(PrivateState {
|
Box::new(RefCell::new(PrivateState {
|
||||||
refs: 1,
|
refs: 1,
|
||||||
loader: Arc::new(Box::new(loader)),
|
loader: Arc::new(Box::new(DefaultModuleLoader::new())),
|
||||||
promise_send: send,
|
promise_send: send,
|
||||||
promise_recv: recv,
|
promise_recv: recv,
|
||||||
promise_table: HashMap::new(),
|
promise_table: HashMap::new(),
|
||||||
|
rejection_tracker: Arc::new(Box::new(DefaultRejectedPromiseTracker::new())),
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -92,6 +92,28 @@ impl PrivateState {
|
||||||
let context = ContextRef::from_raw(ctx);
|
let context = ContextRef::from_raw(ctx);
|
||||||
load_module(&context, path, &loader)
|
load_module(&context, path, &loader)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsafe extern "C" fn promise_rejection_tracker(
|
||||||
|
ctx: *mut sys::JSContext,
|
||||||
|
promise: sys::JSValue,
|
||||||
|
reason: sys::JSValue,
|
||||||
|
is_handled: i32,
|
||||||
|
opaque: *mut std::os::raw::c_void,
|
||||||
|
) {
|
||||||
|
let ctx = ContextRef::from_raw(ctx);
|
||||||
|
let promise = ValueRef::from_raw(promise);
|
||||||
|
let reason = ValueRef::from_raw(reason);
|
||||||
|
let is_handled = is_handled != 0;
|
||||||
|
let handler = unsafe {
|
||||||
|
let ptr = opaque as *const RefCell<PrivateState>;
|
||||||
|
ptr.as_ref()
|
||||||
|
.expect("We already know this runtime is one of ours!")
|
||||||
|
.borrow()
|
||||||
|
.rejection_tracker
|
||||||
|
.clone()
|
||||||
|
};
|
||||||
|
handler.on_rejected_promise(&ctx, &promise, &reason, is_handled);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -101,16 +123,17 @@ pub struct Runtime {
|
||||||
|
|
||||||
impl Runtime {
|
impl Runtime {
|
||||||
pub fn new() -> Runtime {
|
pub fn new() -> Runtime {
|
||||||
Self::with_loader(DefaultModuleLoader::new())
|
let state = PrivateState::new();
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_loader<TLoader: ModuleLoader + 'static>(loader: TLoader) -> Runtime {
|
|
||||||
let state = PrivateState::new(loader);
|
|
||||||
let rt = unsafe {
|
let rt = unsafe {
|
||||||
let rt = sys::JS_NewRuntime();
|
let rt = sys::JS_NewRuntime();
|
||||||
let state = Box::into_raw(state) as *mut _;
|
let state = Box::into_raw(state) as *mut _;
|
||||||
sys::JS_SetRuntimeOpaque(rt, state);
|
sys::JS_SetRuntimeOpaque(rt, state);
|
||||||
sys::JS_SetModuleLoaderFunc(rt, None, Some(PrivateState::module_loader), state);
|
sys::JS_SetModuleLoaderFunc(rt, None, Some(PrivateState::module_loader), state);
|
||||||
|
sys::JS_SetHostPromiseRejectionTracker(
|
||||||
|
rt,
|
||||||
|
Some(PrivateState::promise_rejection_tracker),
|
||||||
|
state,
|
||||||
|
);
|
||||||
rt
|
rt
|
||||||
};
|
};
|
||||||
Runtime { rt }
|
Runtime { rt }
|
||||||
|
|
@ -141,6 +164,26 @@ impl Runtime {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the handler for loading modules. By default this is an instance
|
||||||
|
/// of `DefaultModuleLoader`.
|
||||||
|
pub fn set_module_loader<T>(&mut self, loader: T)
|
||||||
|
where
|
||||||
|
T: ModuleLoader + 'static,
|
||||||
|
{
|
||||||
|
let mut state = unsafe { PrivateState::from_rt_mut(self.rt) };
|
||||||
|
state.loader = Arc::new(Box::new(loader));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set a tracker to be notified whenever a promise is rejected. By
|
||||||
|
/// default, this is an instance of `DefaultRejectedPromiseHandler`.
|
||||||
|
pub fn set_rejected_promise_tracker<T>(&mut self, tracker: T)
|
||||||
|
where
|
||||||
|
T: RejectedPromiseTracker + 'static,
|
||||||
|
{
|
||||||
|
let mut state = unsafe { PrivateState::from_rt_mut(self.rt) };
|
||||||
|
state.rejection_tracker = Arc::new(Box::new(tracker));
|
||||||
|
}
|
||||||
|
|
||||||
pub fn run_gc(&mut self) {
|
pub fn run_gc(&mut self) {
|
||||||
unsafe {
|
unsafe {
|
||||||
sys::JS_RunGC(self.rt);
|
sys::JS_RunGC(self.rt);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import * as io from "./io.ts";
|
import * as io from "./io.ts";
|
||||||
import * as gfx from "./graphics.ts";
|
import * as gfx from "./graphics.ts";
|
||||||
|
|
||||||
export async function load_texture(path: string): Promise<number> {
|
export async function load_texture(path: string): Promise<gfx.Texture> {
|
||||||
const buffer = await io.load(path);
|
const buffer = await io.load(path);
|
||||||
return gfx.create_texture(buffer, path);
|
return gfx.create_texture(buffer, path);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,16 @@ export function spr(
|
||||||
core.spr(x, y, w, h, sx, sy, sw, sh);
|
core.spr(x, y, w, h, sx, sy, sw, sh);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class Texture {
|
||||||
|
#id: number;
|
||||||
|
constructor(id: number) {
|
||||||
|
this.#id = id;
|
||||||
|
}
|
||||||
|
id(): number {
|
||||||
|
return this.#id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a texture based on the loaded buffer.
|
* Create a texture based on the loaded buffer.
|
||||||
*
|
*
|
||||||
|
|
@ -61,15 +71,41 @@ export function spr(
|
||||||
export function create_texture(
|
export function create_texture(
|
||||||
buffer: ArrayBuffer,
|
buffer: ArrayBuffer,
|
||||||
label: string | undefined = undefined
|
label: string | undefined = undefined
|
||||||
): number {
|
): Texture {
|
||||||
return core.create_texture(buffer, label);
|
const id = core.create_texture(buffer, label);
|
||||||
|
return new Texture(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the specified texture as the current texture for calls to e.g. spr().
|
* Set the specified texture as the current texture for calls to e.g. spr().
|
||||||
*
|
*
|
||||||
* @param id - The identifier of the texture to use.
|
* @param texture - The texture to use.
|
||||||
*/
|
*/
|
||||||
export function use_texture(id: number) {
|
export function use_texture(id: Texture) {
|
||||||
core.use_texture(id);
|
core.use_texture(id.id());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a texture that we can render to.
|
||||||
|
*/
|
||||||
|
export function create_writable_texture(
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
label: string | undefined = undefined
|
||||||
|
): Texture {
|
||||||
|
return new Texture(core.create_writable_texture(width, height, label));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the current render target to the screen.
|
||||||
|
*/
|
||||||
|
export function write_to_screen() {
|
||||||
|
core.write_to_screen();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the current render target to the specified texture.
|
||||||
|
*/
|
||||||
|
export function write_to_texture(texture: Texture) {
|
||||||
|
core.write_to_texture(texture.id());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
12
src/io.ts
12
src/io.ts
|
|
@ -6,6 +6,12 @@ import * as core from "io-core";
|
||||||
* @param path The path of the file to load.
|
* @param path The path of the file to load.
|
||||||
* @returns The contents of the file.
|
* @returns The contents of the file.
|
||||||
*/
|
*/
|
||||||
export function load(path: string): Promise<ArrayBuffer> {
|
export const load = core.load;
|
||||||
return core.load(path);
|
|
||||||
}
|
/**
|
||||||
|
* Load the specified file into memory as a string.
|
||||||
|
*
|
||||||
|
* @param path The path of the file to load.
|
||||||
|
* @returns The contents of the file decoded from utf-8.
|
||||||
|
*/
|
||||||
|
export const load_string = core.load_string;
|
||||||
|
|
|
||||||
149
src/lib.rs
149
src/lib.rs
|
|
@ -1,10 +1,16 @@
|
||||||
use bytemuck;
|
use bytemuck;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::rc::Rc;
|
||||||
use std::sync::mpsc::Receiver;
|
use std::sync::mpsc::Receiver;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use tracy_client::{frame_mark, set_thread_name, span};
|
use tracy_client::{frame_mark, set_thread_name, span};
|
||||||
use wgpu::util::DeviceExt;
|
use wgpu::util::DeviceExt;
|
||||||
use winit::{event::*, event_loop::EventLoop, window::Window, window::WindowBuilder};
|
use winit::{
|
||||||
|
event::*,
|
||||||
|
event_loop::{EventLoopBuilder, EventLoopProxy},
|
||||||
|
window::Window,
|
||||||
|
window::WindowBuilder,
|
||||||
|
};
|
||||||
|
|
||||||
mod script;
|
mod script;
|
||||||
use script::graphics::GraphicsCommand;
|
use script::graphics::GraphicsCommand;
|
||||||
|
|
@ -67,6 +73,8 @@ struct State {
|
||||||
sprite_bind_group_layout: wgpu::BindGroupLayout,
|
sprite_bind_group_layout: wgpu::BindGroupLayout,
|
||||||
sprite_textures: HashMap<u32, wgpu::BindGroup>,
|
sprite_textures: HashMap<u32, wgpu::BindGroup>,
|
||||||
|
|
||||||
|
write_textures: HashMap<u32, texture::Texture>,
|
||||||
|
|
||||||
screen_uniform: ScreenUniforms,
|
screen_uniform: ScreenUniforms,
|
||||||
screen_uniform_buffer: wgpu::Buffer,
|
screen_uniform_buffer: wgpu::Buffer,
|
||||||
screen_uniform_bind_group: wgpu::BindGroup,
|
screen_uniform_bind_group: wgpu::BindGroup,
|
||||||
|
|
@ -274,6 +282,7 @@ impl State {
|
||||||
max_vertices,
|
max_vertices,
|
||||||
sprite_bind_group_layout,
|
sprite_bind_group_layout,
|
||||||
sprite_textures: HashMap::new(),
|
sprite_textures: HashMap::new(),
|
||||||
|
write_textures: HashMap::new(),
|
||||||
screen_uniform,
|
screen_uniform,
|
||||||
screen_uniform_buffer,
|
screen_uniform_buffer,
|
||||||
screen_uniform_bind_group,
|
screen_uniform_bind_group,
|
||||||
|
|
@ -308,14 +317,19 @@ impl State {
|
||||||
fn render(&mut self, commands: Vec<GraphicsCommand>) -> Result<(), wgpu::SurfaceError> {
|
fn render(&mut self, commands: Vec<GraphicsCommand>) -> Result<(), wgpu::SurfaceError> {
|
||||||
let _span = span!("context render");
|
let _span = span!("context render");
|
||||||
let output = self.surface.get_current_texture()?;
|
let output = self.surface.get_current_texture()?;
|
||||||
let view = output
|
|
||||||
.texture
|
|
||||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
|
||||||
|
|
||||||
// Group the commands into passes.
|
// Group the commands into passes.
|
||||||
|
let screen_view = Rc::new(
|
||||||
|
output
|
||||||
|
.texture
|
||||||
|
.create_view(&wgpu::TextureViewDescriptor::default()),
|
||||||
|
);
|
||||||
|
let mut last_view = screen_view.clone();
|
||||||
|
|
||||||
struct Pass {
|
struct Pass {
|
||||||
color: Option<[f64; 4]>,
|
color: Option<[f64; 4]>,
|
||||||
commands: Vec<GraphicsCommand>,
|
commands: Vec<GraphicsCommand>,
|
||||||
|
target: Rc<wgpu::TextureView>,
|
||||||
}
|
}
|
||||||
let mut passes = Vec::new();
|
let mut passes = Vec::new();
|
||||||
for command in commands {
|
for command in commands {
|
||||||
|
|
@ -323,7 +337,9 @@ impl State {
|
||||||
GraphicsCommand::Clear(cc) => passes.push(Pass {
|
GraphicsCommand::Clear(cc) => passes.push(Pass {
|
||||||
color: Some(cc.color),
|
color: Some(cc.color),
|
||||||
commands: Vec::new(),
|
commands: Vec::new(),
|
||||||
|
target: last_view.clone(),
|
||||||
}),
|
}),
|
||||||
|
|
||||||
GraphicsCommand::CreateTexture(ct) => {
|
GraphicsCommand::CreateTexture(ct) => {
|
||||||
let texture = texture::Texture::from_image(
|
let texture = texture::Texture::from_image(
|
||||||
&self.device,
|
&self.device,
|
||||||
|
|
@ -355,12 +371,93 @@ impl State {
|
||||||
|
|
||||||
self.sprite_textures.insert(ct.id, sprite_bind_group);
|
self.sprite_textures.insert(ct.id, sprite_bind_group);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GraphicsCommand::CreateWritableTexture {
|
||||||
|
id,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
label,
|
||||||
|
} => {
|
||||||
|
let texture = texture::Texture::new_writable(
|
||||||
|
&self.device,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
match &label {
|
||||||
|
Some(l) => Some(&l),
|
||||||
|
None => None,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
let sprite_bind_group =
|
||||||
|
self.device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
|
layout: &self.sprite_bind_group_layout,
|
||||||
|
entries: &[
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 0,
|
||||||
|
resource: wgpu::BindingResource::TextureView(&texture.view),
|
||||||
|
},
|
||||||
|
wgpu::BindGroupEntry {
|
||||||
|
binding: 1,
|
||||||
|
resource: wgpu::BindingResource::Sampler(&texture.sampler),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
label: match &label {
|
||||||
|
Some(l) => Some(&l),
|
||||||
|
None => None,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
self.sprite_textures.insert(id, sprite_bind_group);
|
||||||
|
self.write_textures.insert(id, texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphicsCommand::WriteToTexture(id) => {
|
||||||
|
let texture = self.write_textures.get(&id).unwrap();
|
||||||
|
last_view = Rc::new(
|
||||||
|
texture
|
||||||
|
.texture
|
||||||
|
.create_view(&wgpu::TextureViewDescriptor::default()),
|
||||||
|
);
|
||||||
|
let new_pass = match passes.last_mut() {
|
||||||
|
Some(pass) => {
|
||||||
|
if pass.commands.is_empty() {
|
||||||
|
pass.target = last_view.clone();
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => true,
|
||||||
|
};
|
||||||
|
if new_pass {
|
||||||
|
passes.push(Pass {
|
||||||
|
color: None,
|
||||||
|
commands: vec![],
|
||||||
|
target: last_view.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GraphicsCommand::WriteToScreen => {
|
||||||
|
if !Rc::ptr_eq(&last_view, &screen_view) {
|
||||||
|
// If I have a pass already I need a new one.
|
||||||
|
last_view = screen_view.clone();
|
||||||
|
if !passes.is_empty() {
|
||||||
|
passes.push(Pass {
|
||||||
|
color: None,
|
||||||
|
commands: vec![],
|
||||||
|
target: last_view.clone(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
GraphicsCommand::EndFrame => (),
|
GraphicsCommand::EndFrame => (),
|
||||||
other => match passes.last_mut() {
|
other => match passes.last_mut() {
|
||||||
Some(pass) => pass.commands.push(other),
|
Some(pass) => pass.commands.push(other),
|
||||||
None => passes.push(Pass {
|
None => passes.push(Pass {
|
||||||
color: None,
|
color: None,
|
||||||
commands: vec![other],
|
commands: vec![other],
|
||||||
|
target: last_view.clone(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -383,7 +480,7 @@ impl State {
|
||||||
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
label: Some("Render Pass"),
|
label: Some("Render Pass"),
|
||||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||||
view: &view,
|
view: &pass.target,
|
||||||
resolve_target: None,
|
resolve_target: None,
|
||||||
ops: wgpu::Operations {
|
ops: wgpu::Operations {
|
||||||
load: if let Some([r, g, b, a]) = pass.color {
|
load: if let Some([r, g, b, a]) = pass.color {
|
||||||
|
|
@ -438,7 +535,11 @@ impl State {
|
||||||
}
|
}
|
||||||
|
|
||||||
GraphicsCommand::UseTexture(id) => texture_id = Some(id),
|
GraphicsCommand::UseTexture(id) => texture_id = Some(id),
|
||||||
|
|
||||||
GraphicsCommand::CreateTexture(_) => (), // Already handled
|
GraphicsCommand::CreateTexture(_) => (), // Already handled
|
||||||
|
GraphicsCommand::CreateWritableTexture { .. } => (), // Already handled
|
||||||
|
GraphicsCommand::WriteToTexture(_) => (), // Already handled
|
||||||
|
GraphicsCommand::WriteToScreen => (), // Already handled
|
||||||
GraphicsCommand::Clear(_) => (), // Already handled
|
GraphicsCommand::Clear(_) => (), // Already handled
|
||||||
GraphicsCommand::EndFrame => (), // Should never appear
|
GraphicsCommand::EndFrame => (), // Should never appear
|
||||||
}
|
}
|
||||||
|
|
@ -472,15 +573,19 @@ impl State {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum OdenEvent {
|
||||||
|
Close,
|
||||||
|
}
|
||||||
|
|
||||||
struct UIEvent {
|
struct UIEvent {
|
||||||
winit: Event<'static, ()>,
|
winit: Event<'static, OdenEvent>,
|
||||||
#[allow(unused)]
|
#[allow(unused)]
|
||||||
time: Instant,
|
time: Instant,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: flume? (https://docs.rs/flume/latest/flume/)
|
// TODO: flume? (https://docs.rs/flume/latest/flume/)
|
||||||
|
|
||||||
fn main_thread(state: State, reciever: Receiver<UIEvent>) {
|
fn main_thread(event_loop: EventLoopProxy<OdenEvent>, state: State, reciever: Receiver<UIEvent>) {
|
||||||
let mut state = state;
|
let mut state = state;
|
||||||
let mut script = script::ScriptContext::new();
|
let mut script = script::ScriptContext::new();
|
||||||
script.init();
|
script.init();
|
||||||
|
|
@ -500,6 +605,10 @@ fn main_thread(state: State, reciever: Receiver<UIEvent>) {
|
||||||
} if window_id == state.window().id() => {
|
} if window_id == state.window().id() => {
|
||||||
if !script.input(event) {
|
if !script.input(event) {
|
||||||
match event {
|
match event {
|
||||||
|
WindowEvent::CloseRequested => {
|
||||||
|
let _ = event_loop.send_event(OdenEvent::Close);
|
||||||
|
}
|
||||||
|
|
||||||
WindowEvent::CursorMoved { position, .. } => {
|
WindowEvent::CursorMoved { position, .. } => {
|
||||||
state.mouse_x = position.x;
|
state.mouse_x = position.x;
|
||||||
state.mouse_y = position.y;
|
state.mouse_y = position.y;
|
||||||
|
|
@ -557,27 +666,35 @@ pub async fn run() {
|
||||||
set_thread_name!("ui thread");
|
set_thread_name!("ui thread");
|
||||||
|
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
let event_loop = EventLoop::new();
|
let event_loop = EventLoopBuilder::<OdenEvent>::with_user_event().build();
|
||||||
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||||
|
let event_loop_proxy = event_loop.create_proxy();
|
||||||
|
|
||||||
let state = State::new(window).await;
|
let state = State::new(window).await;
|
||||||
let (sender, reciever) = std::sync::mpsc::channel();
|
let (sender, reciever) = std::sync::mpsc::channel();
|
||||||
|
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
set_thread_name!("game thread");
|
set_thread_name!("game thread");
|
||||||
main_thread(state, reciever);
|
main_thread(event_loop_proxy, state, reciever);
|
||||||
});
|
});
|
||||||
|
|
||||||
event_loop.run(move |event, _, control_flow| {
|
event_loop.run(move |event, _, control_flow| {
|
||||||
control_flow.set_wait();
|
control_flow.set_wait();
|
||||||
|
|
||||||
if let Some(e) = event.to_static() {
|
match event {
|
||||||
sender
|
Event::UserEvent(OdenEvent::Close) => {
|
||||||
.send(UIEvent {
|
control_flow.set_exit();
|
||||||
winit: e,
|
}
|
||||||
time: Instant::now(),
|
_ => {
|
||||||
})
|
if let Some(e) = event.to_static() {
|
||||||
.unwrap();
|
sender
|
||||||
|
.send(UIEvent {
|
||||||
|
winit: e,
|
||||||
|
time: Instant::now(),
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,8 @@ pub struct ScriptContext {
|
||||||
|
|
||||||
impl ScriptContext {
|
impl ScriptContext {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let runtime = Runtime::with_loader(Loader::new());
|
let mut runtime = Runtime::new();
|
||||||
|
runtime.set_module_loader(Loader::new());
|
||||||
|
|
||||||
let mut context = Context::new(runtime);
|
let mut context = Context::new(runtime);
|
||||||
context.add_intrinsic_bigfloat();
|
context.add_intrinsic_bigfloat();
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,15 @@ pub enum GraphicsCommand {
|
||||||
Print(PrintCommand),
|
Print(PrintCommand),
|
||||||
Sprite(SpriteCommand),
|
Sprite(SpriteCommand),
|
||||||
CreateTexture(CreateTextureCommand),
|
CreateTexture(CreateTextureCommand),
|
||||||
|
CreateWritableTexture {
|
||||||
|
id: u32,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
label: Option<String>,
|
||||||
|
},
|
||||||
UseTexture(u32),
|
UseTexture(u32),
|
||||||
|
WriteToTexture(u32),
|
||||||
|
WriteToScreen,
|
||||||
EndFrame,
|
EndFrame,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -109,6 +117,31 @@ impl GraphicsImpl {
|
||||||
fn use_texture(&self, id: u32) {
|
fn use_texture(&self, id: u32) {
|
||||||
let _ = self.sender.send(GraphicsCommand::UseTexture(id));
|
let _ = self.sender.send(GraphicsCommand::UseTexture(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_writable_texture(
|
||||||
|
&self,
|
||||||
|
width: u32,
|
||||||
|
height: u32,
|
||||||
|
label: Option<String>,
|
||||||
|
) -> Result<u32> {
|
||||||
|
let id = self.next_texture_id.fetch_add(1, Ordering::SeqCst);
|
||||||
|
let _ = self.sender.send(GraphicsCommand::CreateWritableTexture {
|
||||||
|
id,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
label,
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_to_screen(&self) {
|
||||||
|
let _ = self.sender.send(GraphicsCommand::WriteToScreen);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_to_texture(&self, id: u32) {
|
||||||
|
let _ = self.sender.send(GraphicsCommand::WriteToTexture(id));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct GraphicsAPI {
|
pub struct GraphicsAPI {
|
||||||
|
|
@ -168,6 +201,32 @@ impl GraphicsAPI {
|
||||||
)?,
|
)?,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
let gfx = gfx.clone();
|
||||||
|
builder.export(
|
||||||
|
"create_writable_texture",
|
||||||
|
ctx.new_fn(
|
||||||
|
move |_: &ContextRef, width: u32, height: u32, label: Option<String>| {
|
||||||
|
gfx.create_writable_texture(width, height, label)
|
||||||
|
},
|
||||||
|
)?,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let gfx = gfx.clone();
|
||||||
|
builder.export(
|
||||||
|
"write_to_screen",
|
||||||
|
ctx.new_fn(move |_: &ContextRef| gfx.write_to_screen())?,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let gfx = gfx.clone();
|
||||||
|
builder.export(
|
||||||
|
"write_to_texture",
|
||||||
|
ctx.new_fn(move |_: &ContextRef, id: u32| gfx.write_to_texture(id))?,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
builder.build("graphics-core")?;
|
builder.build("graphics-core")?;
|
||||||
Ok(GraphicsAPI { gfx })
|
Ok(GraphicsAPI { gfx })
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,24 @@ impl IoImpl {
|
||||||
|
|
||||||
Ok(value)
|
Ok(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn load_string(&self, context: &ContextRef, path: &str) -> ValueResult {
|
||||||
|
let (value, promise) = context.new_promise()?;
|
||||||
|
|
||||||
|
let path = path.to_string();
|
||||||
|
self.thread_pool.execute(Box::new(move || {
|
||||||
|
let path = path;
|
||||||
|
|
||||||
|
let result = resolve_path(&path, &[]).and_then(|p| std::fs::read(p));
|
||||||
|
promise.resolve(move |ctx: &ContextRef| {
|
||||||
|
let result = result?;
|
||||||
|
let string = std::str::from_utf8(&result)?;
|
||||||
|
ctx.new_string(string)
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct IoAPI {}
|
pub struct IoAPI {}
|
||||||
|
|
@ -97,6 +115,13 @@ impl IoAPI {
|
||||||
ctx.new_fn(move |ctx: &ContextRef, p: String| io.load(ctx, &p))?,
|
ctx.new_fn(move |ctx: &ContextRef, p: String| io.load(ctx, &p))?,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
let io = io.clone();
|
||||||
|
builder.export(
|
||||||
|
"load_string",
|
||||||
|
ctx.new_fn(move |ctx: &ContextRef, p: String| io.load_string(ctx, &p))?,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
builder.build("io-core")?;
|
builder.build("io-core")?;
|
||||||
Ok(IoAPI {})
|
Ok(IoAPI {})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,15 +7,46 @@ pub struct Texture {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Texture {
|
impl Texture {
|
||||||
// pub fn from_bytes(
|
pub fn new_writable(
|
||||||
// device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
// queue: &wgpu::Queue,
|
width: u32,
|
||||||
// bytes: &[u8],
|
height: u32,
|
||||||
// label: &str,
|
label: Option<&str>,
|
||||||
// ) -> Result<Self> {
|
) -> Self {
|
||||||
// let img = image::load_from_memory(bytes)?;
|
let size = wgpu::Extent3d {
|
||||||
// Ok(Self::from_image(device, queue, &img, Some(label)))
|
width,
|
||||||
// }
|
height,
|
||||||
|
depth_or_array_layers: 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||||
|
label,
|
||||||
|
size,
|
||||||
|
mip_level_count: 1,
|
||||||
|
sample_count: 1,
|
||||||
|
dimension: wgpu::TextureDimension::D2,
|
||||||
|
format: wgpu::TextureFormat::Rgba8UnormSrgb,
|
||||||
|
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||||
|
view_formats: &[],
|
||||||
|
});
|
||||||
|
|
||||||
|
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
|
||||||
|
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||||
|
address_mode_u: wgpu::AddressMode::ClampToEdge,
|
||||||
|
address_mode_v: wgpu::AddressMode::ClampToEdge,
|
||||||
|
address_mode_w: wgpu::AddressMode::ClampToEdge,
|
||||||
|
mag_filter: wgpu::FilterMode::Linear,
|
||||||
|
min_filter: wgpu::FilterMode::Nearest,
|
||||||
|
mipmap_filter: wgpu::FilterMode::Nearest,
|
||||||
|
..Default::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
Self {
|
||||||
|
texture,
|
||||||
|
view,
|
||||||
|
sampler,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn from_image(
|
pub fn from_image(
|
||||||
device: &wgpu::Device,
|
device: &wgpu::Device,
|
||||||
|
|
@ -31,6 +62,7 @@ impl Texture {
|
||||||
height: dimensions.1,
|
height: dimensions.1,
|
||||||
depth_or_array_layers: 1,
|
depth_or_array_layers: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||||
label,
|
label,
|
||||||
size,
|
size,
|
||||||
|
|
|
||||||
14
types/graphics-core.d.ts
vendored
14
types/graphics-core.d.ts
vendored
|
|
@ -1,7 +1,9 @@
|
||||||
// These are the functions exposed by the native graphics module.
|
// These are the functions exposed by the native graphics module.
|
||||||
//
|
//
|
||||||
export function cls(r: number, g: number, b: number);
|
export function cls(r: number, g: number, b: number);
|
||||||
|
|
||||||
export function print(msg: string);
|
export function print(msg: string);
|
||||||
|
|
||||||
export function spr(
|
export function spr(
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
|
|
@ -12,8 +14,20 @@ export function spr(
|
||||||
sw: number,
|
sw: number,
|
||||||
sh: number
|
sh: number
|
||||||
);
|
);
|
||||||
|
|
||||||
export function create_texture(
|
export function create_texture(
|
||||||
buffer: ArrayBuffer,
|
buffer: ArrayBuffer,
|
||||||
label: string | undefined
|
label: string | undefined
|
||||||
): number;
|
): number;
|
||||||
|
|
||||||
export function use_texture(id: number);
|
export function use_texture(id: number);
|
||||||
|
|
||||||
|
export function create_writable_texture(
|
||||||
|
width: number,
|
||||||
|
height: number,
|
||||||
|
label: string | undefined
|
||||||
|
): number;
|
||||||
|
|
||||||
|
export function write_to_screen();
|
||||||
|
|
||||||
|
export function write_to_texture(id: number);
|
||||||
|
|
|
||||||
2
types/io-core.d.ts
vendored
2
types/io-core.d.ts
vendored
|
|
@ -1,3 +1,5 @@
|
||||||
// These are the functions exposed by the native IO module.
|
// These are the functions exposed by the native IO module.
|
||||||
//
|
//
|
||||||
export function load(path: string): Promise<ArrayBuffer>;
|
export function load(path: string): Promise<ArrayBuffer>;
|
||||||
|
|
||||||
|
export function load_string(path: string): Promise<string>;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue