diff --git a/oden-js/src/context.rs b/oden-js/src/context.rs index b7b09239..7ce96641 100644 --- a/oden-js/src/context.rs +++ b/oden-js/src/context.rs @@ -324,34 +324,6 @@ 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/promise.rs b/oden-js/src/promise.rs index 8ee23b26..0bf846cc 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: FnOnce(&ContextRef) -> ValueResult + Send + 'static, + T: Fn(&ContextRef) -> ValueResult + Send + 'static, { let _ = self .channel @@ -58,7 +58,7 @@ impl Promise { pub fn reject(mut self, value: T) where - T: FnOnce(&ContextRef) -> ValueResult + Send + 'static, + T: Fn(&ContextRef) -> ValueResult + Send + 'static, { let _ = self .channel diff --git a/oden-js/src/runtime.rs b/oden-js/src/runtime.rs index e349d955..1719f3a0 100644 --- a/oden-js/src/runtime.rs +++ b/oden-js/src/runtime.rs @@ -175,44 +175,37 @@ impl Runtime { /// Process all pending async jobs. This includes all promise resolutions. fn process_promise_completions(&self) { - 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. + // 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 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) { - promises.push((entry, evt)); + 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"); } } - 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. diff --git a/oden-js/src/value.rs b/oden-js/src/value.rs index c8a95def..dfc8d65c 100644 --- a/oden-js/src/value.rs +++ b/oden-js/src/value.rs @@ -323,20 +323,6 @@ 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 { diff --git a/src/assets.ts b/src/assets.ts index fe1dd156..d611c934 100644 --- a/src/assets.ts +++ b/src/assets.ts @@ -1,7 +1,5 @@ -import * as io from "./io.ts"; -import * as gfx from "./graphics.ts"; +import * as core from "asset-core"; -export async function load_texture(path: string): Promise { - const buffer = await io.load(path); - return gfx.create_texture(buffer, path); +export function load_texture(path: string): number { + return core.load_texture(path); } diff --git a/src/graphics.ts b/src/graphics.ts index 185413b2..aaa7ad14 100644 --- a/src/graphics.ts +++ b/src/graphics.ts @@ -52,19 +52,6 @@ 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/io.ts b/src/io.ts deleted file mode 100644 index e95fada6..00000000 --- a/src/io.ts +++ /dev/null @@ -1,11 +0,0 @@ -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/lib.rs b/src/lib.rs index 9a23cacc..8577050a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -339,10 +339,7 @@ impl State { &self.device, &self.queue, &ct.image, - match &ct.label { - Some(l) => Some(&l), - None => None, - }, + Some(&ct.label), ); let sprite_bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor { @@ -357,10 +354,7 @@ impl State { resource: wgpu::BindingResource::Sampler(&texture.sampler), }, ], - label: match &ct.label { - Some(l) => Some(&l), - None => None, - }, + label: Some(&ct.label), }); self.sprite_textures.insert(ct.id, sprite_bind_group); diff --git a/src/main.ts b/src/main.ts index b1f49aba..01e8041a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,22 +1,18 @@ import { cls, print, spr, use_texture } from "./graphics.ts"; import { load_texture } from "./assets.ts"; -let the_texture: number | undefined = undefined; +let the_texture = 0; export function init() { print("Hello world!"); - - // Start this load, but then... - load_texture("./src/happy-tree.png").then((n) => (the_texture = n)); + // TODO: Async IO + the_texture = load_texture("./src/happy-tree.png"); } export function update() {} export function draw() { cls(0.1, 0.2, 0.3); - if (the_texture != undefined) { - // ...it gets resolved here? - use_texture(the_texture); - spr((320 - 256) / 2, 0, 256, 240, 0, 0); - } + use_texture(the_texture); + spr((320 - 256) / 2, 0, 256, 240, 0, 0); } diff --git a/src/script.rs b/src/script.rs index 3eecfd58..fe03f228 100644 --- a/src/script.rs +++ b/src/script.rs @@ -12,7 +12,8 @@ use graphics::GraphicsCommand; mod typescript; use typescript::transpile_to_javascript; -mod io; +pub mod assets; +pub mod io; struct Loader {} @@ -44,6 +45,7 @@ pub struct ScriptContext { draw: Value, gfx: graphics::GraphicsAPI, + _assets: assets::AssetsAPI, gfx_receive: Receiver, } @@ -60,6 +62,8 @@ 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 @@ -85,6 +89,8 @@ impl ScriptContext { gfx, gfx_receive, + + _assets: assets, } } diff --git a/src/script/assets.rs b/src/script/assets.rs new file mode 100644 index 00000000..26e9b42b --- /dev/null +++ b/src/script/assets.rs @@ -0,0 +1,56 @@ +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: 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/src/script/graphics.rs b/src/script/graphics.rs index c27a089a..0156b6ee 100644 --- a/src/script/graphics.rs +++ b/src/script/graphics.rs @@ -1,5 +1,4 @@ -use oden_js::{module, ContextRef, Error, Result, Value}; -use std::sync::atomic::{AtomicU32, Ordering}; +use oden_js::{module, ContextRef}; use std::sync::mpsc::Sender; use std::sync::Arc; @@ -29,7 +28,7 @@ pub struct SpriteCommand { pub struct CreateTextureCommand { pub id: u32, pub image: image::DynamicImage, - pub label: Option, + pub label: String, } #[derive(Debug)] @@ -43,16 +42,12 @@ pub enum GraphicsCommand { } struct GraphicsImpl { - next_texture_id: AtomicU32, sender: Sender, } impl GraphicsImpl { pub fn new(sender: Sender) -> Self { - GraphicsImpl { - sender, - next_texture_id: AtomicU32::new(0), - } + GraphicsImpl { sender } } fn print(&self, text: String) -> () { @@ -80,30 +75,6 @@ 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)); } @@ -155,17 +126,6 @@ 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/src/script/io.rs b/src/script/io.rs index bfda8333..3d9aaa9a 100644 --- a/src/script/io.rs +++ b/src/script/io.rs @@ -71,12 +71,7 @@ impl IoImpl { self.thread_pool.execute(Box::new(move || { // TODO: Actually read the path. let path = 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()), - }); + promise.resolve(move |ctx: &ContextRef| ctx.new_string(&path)); })); Ok(value) diff --git a/types/asset-core.d.ts b/types/asset-core.d.ts new file mode 100644 index 00000000..dfcee30d --- /dev/null +++ b/types/asset-core.d.ts @@ -0,0 +1 @@ +export function load_texture(path: string): number; diff --git a/types/graphics-core.d.ts b/types/graphics-core.d.ts index 3fde39fd..7f0afd2e 100644 --- a/types/graphics-core.d.ts +++ b/types/graphics-core.d.ts @@ -12,8 +12,4 @@ export function spr( sw: number, sh: number ); -export function create_texture( - buffer: ArrayBuffer, - label: string | undefined -): number; export function use_texture(id: number); diff --git a/types/io-core.d.ts b/types/io-core.d.ts deleted file mode 100644 index e5114f02..00000000 --- a/types/io-core.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -// These are the functions exposed by the native IO module. -// -export function load(path: string): Promise;