From 44c39b174012ee6570e55aa41e34b2e8a4a79d4f Mon Sep 17 00:00:00 2001 From: John Doty Date: Fri, 30 Jun 2023 05:52:32 -0700 Subject: [PATCH 1/7] [oden-js] Promise completion should be FnOnce --- oden-js/src/promise.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/oden-js/src/promise.rs b/oden-js/src/promise.rs index 0bf846cc..8ee23b26 100644 --- a/oden-js/src/promise.rs +++ b/oden-js/src/promise.rs @@ -12,7 +12,7 @@ impl PromiseHandle { } } -pub type PromiseResult = Box ValueResult + Send + 'static>; +pub type PromiseResult = Box ValueResult + Send + 'static>; pub(crate) enum PromiseEvent { Resolved(PromiseResult), @@ -48,7 +48,7 @@ impl Promise { pub fn resolve(mut self, value: T) where - T: Fn(&ContextRef) -> ValueResult + Send + 'static, + T: FnOnce(&ContextRef) -> ValueResult + Send + 'static, { let _ = self .channel @@ -58,7 +58,7 @@ impl Promise { pub fn reject(mut self, value: T) where - T: Fn(&ContextRef) -> ValueResult + Send + 'static, + T: FnOnce(&ContextRef) -> ValueResult + Send + 'static, { let _ = self .channel From b77e7eba3e1e276b27dc2257c998f0039a72638f Mon Sep 17 00:00:00 2001 From: John Doty Date: Fri, 30 Jun 2023 06:26:16 -0700 Subject: [PATCH 2/7] [oden-js] Promise completion needs to be more robust As predicted. --- oden-js/src/runtime.rs | 59 +++++++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/oden-js/src/runtime.rs b/oden-js/src/runtime.rs index 1719f3a0..e349d955 100644 --- a/oden-js/src/runtime.rs +++ b/oden-js/src/runtime.rs @@ -175,37 +175,44 @@ impl Runtime { /// Process all pending async jobs. This includes all promise resolutions. fn process_promise_completions(&self) { - // TODO: This could be more robust if we buffered all the completed - // promise entries and then dropped the borrow of the state, so - // that we never invoked user code while borrowing our private - // state mutably. + let mut promises = vec![]; + + // First, gather all the completed promises into a temporary vector + // so that we only need to borrow our mutable state for a short + // period of time. let mut state = unsafe { PrivateState::from_rt_mut(self.rt) }; while let Ok((handle, evt)) = state.promise_recv.try_recv() { if let Some(entry) = state.promise_table.remove(&handle) { - let ctx = ContextRef::from_raw(entry.context); - let (callback, value) = match evt { - PromiseEvent::Resolved(v) => (entry.resolve, v), - PromiseEvent::Rejected(v) => (entry.reject, v), - }; - - // Convert the result into a JS value, which we can only - // really do while we are on this thread. - let value = value(&ctx).expect("Should be able to convert promise result to value"); - - // Call the particular callback and make sure it doesn't throw. - ctx.check_exception(unsafe { - let mut args = [value.val]; - sys::JS_Call( - entry.context, - callback, - sys::JS_MakeUndefined(), - 1, - args.as_mut_ptr(), - ) - }) - .expect("Exception thrown by promise callback"); + promises.push((entry, evt)); } } + drop(state); // Don't need our internal state anymore. + + // Nowe we can complete all the promises. + for (entry, evt) in promises { + let ctx = ContextRef::from_raw(entry.context); + let (callback, value) = match evt { + PromiseEvent::Resolved(v) => (entry.resolve, v), + PromiseEvent::Rejected(v) => (entry.reject, v), + }; + + // Convert the result into a JS value, which we can only + // really do while we are on this thread. + let value = value(&ctx).expect("Should be able to convert promise result to value"); + + // Call the particular callback and make sure it doesn't throw. + ctx.check_exception(unsafe { + let mut args = [value.val]; + sys::JS_Call( + entry.context, + callback, + sys::JS_MakeUndefined(), + 1, + args.as_mut_ptr(), + ) + }) + .expect("Exception thrown by promise callback"); + } } /// Process all pending async jobs. This includes all promise resolutions. From f7ed78ce3b3319011a7f301bf2ba2c13bb7849e2 Mon Sep 17 00:00:00 2001 From: John Doty Date: Fri, 30 Jun 2023 06:26:53 -0700 Subject: [PATCH 3/7] [oden-js] ArrayBuffer values --- oden-js/src/context.rs | 28 ++++++++++++++++++++++++++++ oden-js/src/value.rs | 14 ++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/oden-js/src/context.rs b/oden-js/src/context.rs index 7ce96641..b7b09239 100644 --- a/oden-js/src/context.rs +++ b/oden-js/src/context.rs @@ -324,6 +324,34 @@ impl ContextRef { } } + unsafe extern "C" fn free_array_buffer_bytes( + _rt: *mut sys::JSRuntime, + opaque: *mut std::ffi::c_void, + _ptr: *mut std::ffi::c_void, + ) { + drop(Box::from_raw(opaque as *mut Vec)); + } + + pub fn new_array_buffer(&self, buffer: T) -> ValueResult + where + T: Into>, + { + let mut vec_box = Box::new(buffer.into()); + unsafe { + let is_shared = 0; + let byte_ptr = vec_box.as_mut_ptr(); + let byte_len = vec_box.len(); + self.check_exception(sys::JS_NewArrayBuffer( + self.ctx, + byte_ptr, + byte_len, + Some(Self::free_array_buffer_bytes), + Box::into_raw(vec_box) as *mut _, + is_shared, + )) + } + } + /// Fetch the global object for the context. pub fn global_object(&self) -> ValueResult { self.check_exception(unsafe { sys::JS_GetGlobalObject(self.ctx) }) diff --git a/oden-js/src/value.rs b/oden-js/src/value.rs index dfc8d65c..c8a95def 100644 --- a/oden-js/src/value.rs +++ b/oden-js/src/value.rs @@ -323,6 +323,20 @@ impl ValueRef { )) } } + + /// Get the underlying bytes for a an ArrayBuffer object. Obviously it + /// only works if this is an un-detached ArrayBuffer value. + pub fn get_array_buffer<'a>(&'a self, ctx: &ContextRef) -> Result<&'a [u8]> { + unsafe { + let mut size: usize = 0; + let buffer = sys::JS_GetArrayBuffer(ctx.ctx, &mut size as *mut usize, self.val); + if buffer.is_null() { + Err(ctx.exception_error()) + } else { + Ok(std::slice::from_raw_parts(buffer, size)) + } + } + } } impl fmt::Debug for ValueRef { From 4959adc7e699127aa9dc4e4f2abe9ca6988fcdf6 Mon Sep 17 00:00:00 2001 From: John Doty Date: Fri, 30 Jun 2023 06:28:04 -0700 Subject: [PATCH 4/7] [oden] Native IO module --- src/io.ts | 11 +++++++++++ src/script/io.rs | 7 ++++++- types/io-core.d.ts | 3 +++ 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 src/io.ts create mode 100644 types/io-core.d.ts diff --git a/src/io.ts b/src/io.ts new file mode 100644 index 00000000..e95fada6 --- /dev/null +++ b/src/io.ts @@ -0,0 +1,11 @@ +import * as core from "io-core"; + +/** + * Load the specified file into memory. + * + * @param path The path of the file to load. + * @returns The contents of the file. + */ +export function load(path: string): Promise { + return core.load(path); +} diff --git a/src/script/io.rs b/src/script/io.rs index 3d9aaa9a..bfda8333 100644 --- a/src/script/io.rs +++ b/src/script/io.rs @@ -71,7 +71,12 @@ impl IoImpl { self.thread_pool.execute(Box::new(move || { // TODO: Actually read the path. let path = path; - promise.resolve(move |ctx: &ContextRef| ctx.new_string(&path)); + + let result = std::fs::read(path); + promise.resolve(move |ctx: &ContextRef| match result { + Ok(v) => ctx.new_array_buffer(v), + Err(err) => Err(err.into()), + }); })); Ok(value) diff --git a/types/io-core.d.ts b/types/io-core.d.ts new file mode 100644 index 00000000..e5114f02 --- /dev/null +++ b/types/io-core.d.ts @@ -0,0 +1,3 @@ +// These are the functions exposed by the native IO module. +// +export function load(path: string): Promise; From d2dfa7c401160d00c6c1d1d54de997fb56cf3377 Mon Sep 17 00:00:00 2001 From: John Doty Date: Fri, 30 Jun 2023 06:28:57 -0700 Subject: [PATCH 5/7] [oden] Graphics module can create textures directly --- src/graphics.ts | 13 ++++++++++++ src/lib.rs | 10 +++++++-- src/script/assets.rs | 2 +- src/script/graphics.rs | 46 +++++++++++++++++++++++++++++++++++++--- types/graphics-core.d.ts | 4 ++++ 5 files changed, 69 insertions(+), 6 deletions(-) diff --git a/src/graphics.ts b/src/graphics.ts index aaa7ad14..185413b2 100644 --- a/src/graphics.ts +++ b/src/graphics.ts @@ -52,6 +52,19 @@ export function spr( core.spr(x, y, w, h, sx, sy, sw, sh); } +/** + * Create a texture based on the loaded buffer. + * + * @param buffer The underlying bytes that make up the texture image. + * @param label The label to put onto the texture (for debugging). + */ +export function create_texture( + buffer: ArrayBuffer, + label: string | undefined = undefined +): number { + return core.create_texture(buffer, label); +} + /** * Set the specified texture as the current texture for calls to e.g. spr(). * diff --git a/src/lib.rs b/src/lib.rs index 8577050a..9a23cacc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -339,7 +339,10 @@ impl State { &self.device, &self.queue, &ct.image, - Some(&ct.label), + match &ct.label { + Some(l) => Some(&l), + None => None, + }, ); let sprite_bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor { @@ -354,7 +357,10 @@ impl State { resource: wgpu::BindingResource::Sampler(&texture.sampler), }, ], - label: Some(&ct.label), + label: match &ct.label { + Some(l) => Some(&l), + None => None, + }, }); self.sprite_textures.insert(ct.id, sprite_bind_group); diff --git a/src/script/assets.rs b/src/script/assets.rs index 26e9b42b..f6bcbaec 100644 --- a/src/script/assets.rs +++ b/src/script/assets.rs @@ -30,7 +30,7 @@ impl AssetsImpl { .send(GraphicsCommand::CreateTexture(CreateTextureCommand { id, image, - label: path.into(), + label: Some(path.into()), })); Ok(id) diff --git a/src/script/graphics.rs b/src/script/graphics.rs index 0156b6ee..c27a089a 100644 --- a/src/script/graphics.rs +++ b/src/script/graphics.rs @@ -1,4 +1,5 @@ -use oden_js::{module, ContextRef}; +use oden_js::{module, ContextRef, Error, Result, Value}; +use std::sync::atomic::{AtomicU32, Ordering}; use std::sync::mpsc::Sender; use std::sync::Arc; @@ -28,7 +29,7 @@ pub struct SpriteCommand { pub struct CreateTextureCommand { pub id: u32, pub image: image::DynamicImage, - pub label: String, + pub label: Option, } #[derive(Debug)] @@ -42,12 +43,16 @@ pub enum GraphicsCommand { } struct GraphicsImpl { + next_texture_id: AtomicU32, sender: Sender, } impl GraphicsImpl { pub fn new(sender: Sender) -> Self { - GraphicsImpl { sender } + GraphicsImpl { + sender, + next_texture_id: AtomicU32::new(0), + } } fn print(&self, text: String) -> () { @@ -75,6 +80,30 @@ impl GraphicsImpl { })); } + fn create_texture( + &self, + context: &ContextRef, + buffer: Value, + label: Option, + ) -> Result { + let bytes = buffer.get_array_buffer(context)?; + let image = match image::load_from_memory(&bytes) { + Ok(i) => i, + Err(e) => return Err(Error::RustFunctionError(format!("{e}"))), + }; + + let id = self.next_texture_id.fetch_add(1, Ordering::SeqCst); + let _ = self + .sender + .send(GraphicsCommand::CreateTexture(CreateTextureCommand { + id, + image, + label, + })); + + Ok(id) + } + fn use_texture(&self, id: u32) { let _ = self.sender.send(GraphicsCommand::UseTexture(id)); } @@ -126,6 +155,17 @@ impl GraphicsAPI { ctx.new_fn(move |_: &ContextRef, id: u32| gfx.use_texture(id))?, )?; } + { + let gfx = gfx.clone(); + builder.export( + "create_texture", + ctx.new_fn( + move |c: &ContextRef, buffer: Value, label: Option| { + gfx.create_texture(c, buffer, label) + }, + )?, + )?; + } builder.build("graphics-core")?; Ok(GraphicsAPI { gfx }) } diff --git a/types/graphics-core.d.ts b/types/graphics-core.d.ts index 7f0afd2e..3fde39fd 100644 --- a/types/graphics-core.d.ts +++ b/types/graphics-core.d.ts @@ -12,4 +12,8 @@ export function spr( sw: number, sh: number ); +export function create_texture( + buffer: ArrayBuffer, + label: string | undefined +): number; export function use_texture(id: number); From f3f9988314d0d8056a81b53098fe57c4d840bea2 Mon Sep 17 00:00:00 2001 From: John Doty Date: Fri, 30 Jun 2023 06:29:21 -0700 Subject: [PATCH 6/7] [oden] Assets becomes async load in terms of IO/GFX --- src/assets.ts | 8 +++++--- src/main.ts | 14 +++++++++----- types/asset-core.d.ts | 2 ++ 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/assets.ts b/src/assets.ts index d611c934..fe1dd156 100644 --- a/src/assets.ts +++ b/src/assets.ts @@ -1,5 +1,7 @@ -import * as core from "asset-core"; +import * as io from "./io.ts"; +import * as gfx from "./graphics.ts"; -export function load_texture(path: string): number { - return core.load_texture(path); +export async function load_texture(path: string): Promise { + const buffer = await io.load(path); + return gfx.create_texture(buffer, path); } diff --git a/src/main.ts b/src/main.ts index 01e8041a..b1f49aba 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,18 +1,22 @@ import { cls, print, spr, use_texture } from "./graphics.ts"; import { load_texture } from "./assets.ts"; -let the_texture = 0; +let the_texture: number | undefined = undefined; export function init() { print("Hello world!"); - // TODO: Async IO - the_texture = load_texture("./src/happy-tree.png"); + + // Start this load, but then... + load_texture("./src/happy-tree.png").then((n) => (the_texture = n)); } export function update() {} export function draw() { cls(0.1, 0.2, 0.3); - use_texture(the_texture); - spr((320 - 256) / 2, 0, 256, 240, 0, 0); + if (the_texture != undefined) { + // ...it gets resolved here? + use_texture(the_texture); + spr((320 - 256) / 2, 0, 256, 240, 0, 0); + } } diff --git a/types/asset-core.d.ts b/types/asset-core.d.ts index dfcee30d..87680547 100644 --- a/types/asset-core.d.ts +++ b/types/asset-core.d.ts @@ -1 +1,3 @@ +// These are the functions exposed by the native assets module. +// export function load_texture(path: string): number; From 96e95e22cefb4726f60d37294f12461f7a9394fb Mon Sep 17 00:00:00 2001 From: John Doty Date: Fri, 30 Jun 2023 06:31:01 -0700 Subject: [PATCH 7/7] [oden] Remove native assets module What's the point? --- src/script.rs | 8 +------ src/script/assets.rs | 56 ------------------------------------------- types/asset-core.d.ts | 3 --- 3 files changed, 1 insertion(+), 66 deletions(-) delete mode 100644 src/script/assets.rs delete mode 100644 types/asset-core.d.ts diff --git a/src/script.rs b/src/script.rs index fe03f228..3eecfd58 100644 --- a/src/script.rs +++ b/src/script.rs @@ -12,8 +12,7 @@ use graphics::GraphicsCommand; mod typescript; use typescript::transpile_to_javascript; -pub mod assets; -pub mod io; +mod io; struct Loader {} @@ -45,7 +44,6 @@ pub struct ScriptContext { draw: Value, gfx: graphics::GraphicsAPI, - _assets: assets::AssetsAPI, gfx_receive: Receiver, } @@ -62,8 +60,6 @@ impl ScriptContext { let gfx = graphics::GraphicsAPI::define(&context, gfx_send.clone()) .expect("Graphics module should load without error"); - let assets = assets::AssetsAPI::define(&context, gfx_send.clone()) - .expect("Assets module should load without error"); let _io = io::IoAPI::define(&context).expect("IO module should load without error"); let module = context @@ -89,8 +85,6 @@ impl ScriptContext { gfx, gfx_receive, - - _assets: assets, } } diff --git a/src/script/assets.rs b/src/script/assets.rs deleted file mode 100644 index f6bcbaec..00000000 --- a/src/script/assets.rs +++ /dev/null @@ -1,56 +0,0 @@ -use crate::script::graphics::{CreateTextureCommand, GraphicsCommand}; -use oden_js::{module::native::NativeModuleBuilder, ContextRef, Error, Result}; -use std::sync::atomic::{AtomicU32, Ordering}; -use std::sync::mpsc::Sender; -use std::sync::Arc; - -struct AssetsImpl { - next_texture_id: AtomicU32, - gfx_sender: Sender, -} - -impl AssetsImpl { - fn new(sender: Sender) -> Self { - AssetsImpl { - gfx_sender: sender, - next_texture_id: AtomicU32::new(0), - } - } - - fn load_texture(&self, path: &str) -> Result { - let bytes = std::fs::read(path)?; - let image = match image::load_from_memory(&bytes) { - Ok(i) => i, - Err(e) => return Err(Error::RustFunctionError(format!("{e}"))), - }; - - let id = self.next_texture_id.fetch_add(1, Ordering::SeqCst); - let _ = self - .gfx_sender - .send(GraphicsCommand::CreateTexture(CreateTextureCommand { - id, - image, - label: Some(path.into()), - })); - - Ok(id) - } -} - -pub struct AssetsAPI {} - -impl AssetsAPI { - pub fn define(ctx: &ContextRef, sender: Sender) -> oden_js::Result { - let assets = Arc::new(AssetsImpl::new(sender)); - let mut builder = NativeModuleBuilder::new(ctx); - { - let assets = assets.clone(); - builder.export( - "load_texture", - ctx.new_fn(move |_ctx: &ContextRef, p: String| assets.load_texture(&p))?, - )?; - } - builder.build("asset-core")?; - Ok(AssetsAPI {}) - } -} diff --git a/types/asset-core.d.ts b/types/asset-core.d.ts deleted file mode 100644 index 87680547..00000000 --- a/types/asset-core.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -// These are the functions exposed by the native assets module. -// -export function load_texture(path: string): number;