diff --git a/oden-js/src/callback.rs b/oden-js/src/callback.rs index b8dc3120..d90ab651 100644 --- a/oden-js/src/callback.rs +++ b/oden-js/src/callback.rs @@ -69,7 +69,7 @@ where *ret } } - Err(Error::Exception(e, _)) => unsafe { + Err(Error::Exception(e, _, _)) => unsafe { // If we returned `Error::Exception` then we're propagating an // exception through the JS stack, just flip it. let exc = &e.val; diff --git a/oden-js/src/context.rs b/oden-js/src/context.rs index 22f75654..ae994378 100644 --- a/oden-js/src/context.rs +++ b/oden-js/src/context.rs @@ -308,7 +308,12 @@ impl ContextRef { pub(crate) fn exception_error(&self) -> Error { let exc = self.exception(); let desc = exc.to_string(&self).unwrap_or_else(|_| String::new()); - Error::Exception(exc, desc) + let stack = exc + .get_property(&self, "stack") + .and_then(|stack| stack.to_string(&self)) + .unwrap_or_else(|_| String::new()); + + Error::Exception(exc, desc, stack) } } diff --git a/oden-js/src/conversion/from.rs b/oden-js/src/conversion/from.rs index d53f270c..3a3ad944 100644 --- a/oden-js/src/conversion/from.rs +++ b/oden-js/src/conversion/from.rs @@ -1,4 +1,4 @@ -use crate::{ContextRef, Error, Result, Value, ValueRef}; +use crate::{ContextRef, Error, Result, Value, ValueRef, ValueType}; use std::num::TryFromIntError; pub trait TryFromValue: Sized { @@ -105,10 +105,24 @@ impl TryFromValue for Value { } } +impl TryFromValue for () { + #[inline] + fn try_from_value(value: &ValueRef, _ctx: &ContextRef) -> Result { + if value.is_undefined() { + Ok(()) + } else { + Err(Error::InvalidType { + expected: ValueType::Undefined, + found: value.value_type(), + }) + } + } +} + impl TryFromValue for Option { #[inline] fn try_from_value(value: &ValueRef, ctx: &ContextRef) -> Result { - if value.is_undefined() { + if value.is_null() || value.is_undefined() { Ok(None) } else { Ok(Some(T::try_from_value(value, ctx)?)) diff --git a/oden-js/src/conversion/into.rs b/oden-js/src/conversion/into.rs index 945b66f4..cff22a69 100644 --- a/oden-js/src/conversion/into.rs +++ b/oden-js/src/conversion/into.rs @@ -7,21 +7,21 @@ pub trait TryIntoValue { impl TryIntoValue for u8 { #[inline] fn try_into_value(self, ctx: &ContextRef) -> ValueResult { - ctx.new_u64(self) + ctx.new_u32(self) } } impl TryIntoValue for u16 { #[inline] fn try_into_value(self, ctx: &ContextRef) -> ValueResult { - ctx.new_u64(self) + ctx.new_u32(self) } } impl TryIntoValue for u32 { #[inline] fn try_into_value(self, ctx: &ContextRef) -> ValueResult { - ctx.new_u64(self) + ctx.new_u32(self) } } @@ -116,7 +116,7 @@ impl TryIntoValue for Error { } Error::ConversionError(e) => Err(Error::ConversionError(e)), Error::RustFunctionError(e) => Err(Error::RustFunctionError(e)), - Error::Exception(v, d) => Err(Error::Exception(v.dup(ctx), d)), + Error::Exception(v, d, s) => Err(Error::Exception(v.dup(ctx), d, s)), Error::OutOfMemory => Err(Error::OutOfMemory), Error::IOError(e) => Err(Error::IOError(e)), Error::ParseError(name, err) => Err(Error::ParseError(name, err)), @@ -135,8 +135,15 @@ impl TryIntoValue for Option { #[inline] fn try_into_value(self, ctx: &ContextRef) -> ValueResult { match self { - None => Ok(ctx.undefined()), + None => Ok(ctx.null()), Some(v) => v.try_into_value(ctx), } } } + +impl TryIntoValue for () { + #[inline] + fn try_into_value(self, ctx: &ContextRef) -> ValueResult { + Ok(ctx.undefined()) + } +} diff --git a/oden-js/src/lib.rs b/oden-js/src/lib.rs index 4bef020b..3246ecbc 100644 --- a/oden-js/src/lib.rs +++ b/oden-js/src/lib.rs @@ -18,7 +18,7 @@ pub use conversion::*; pub use runtime::Runtime; pub use value::{Value, ValueRef, ValueType}; -#[derive(Error, Debug)] +#[derive(Debug, Error)] pub enum Error { #[error("too many classes have been registered")] TooManyClasses, @@ -39,8 +39,8 @@ pub enum Error { ConversionError(String), #[error("an error occurred calling a rust function: {0}")] RustFunctionError(String), - #[error("an exception was thrown during evaluation: {1}")] - Exception(Value, String), + #[error("an exception was thrown during evaluation: {1}\nStack: {2}")] + Exception(Value, String, String), #[error("out of memory")] OutOfMemory, #[error("an io error occurred: {0}")] @@ -66,7 +66,7 @@ pub type ValueResult = core::result::Result; pub(crate) fn throw_error(context: &ContextRef, error: Error) -> sys::JSValue { match error { - Error::Exception(v, _) => unsafe { + Error::Exception(v, _, _) => unsafe { sys::JS_DupValue(context.ctx, v.val); sys::JS_Throw(context.ctx, v.val) }, diff --git a/oden-js/src/module/native.rs b/oden-js/src/module/native.rs index c65bda95..23f7c4fe 100644 --- a/oden-js/src/module/native.rs +++ b/oden-js/src/module/native.rs @@ -164,7 +164,7 @@ unsafe extern "C" fn init_func( let context = ContextRef::from_raw(ctx); match NativeModuleState::::define(&context, m) { Ok(_) => 0, - Err(Error::Exception(e, _)) => unsafe { + Err(Error::Exception(e, _, _)) => unsafe { // If we returned `Error::Exception` then we're propagating an // exception through the JS stack, just flip it. let exc = &e.val; diff --git a/oden-js/src/value.rs b/oden-js/src/value.rs index 52180e8d..58a42134 100644 --- a/oden-js/src/value.rs +++ b/oden-js/src/value.rs @@ -137,9 +137,7 @@ impl ValueRef { let mut res: u32 = 0; let ret = sys::JS_ToUint32(ctx.ctx, &mut res, self.val); if ret < 0 { - let exc = ctx.exception(); - let desc = exc.to_string(&ctx).unwrap_or_else(|_| String::new()); - Err(Error::Exception(exc, desc)) + Err(ctx.exception_error()) } else { Ok(res) } diff --git a/src/assets.ts b/src/assets.ts new file mode 100644 index 00000000..d611c934 --- /dev/null +++ b/src/assets.ts @@ -0,0 +1,5 @@ +import * as core from "asset-core"; + +export function load_texture(path: string): number { + return core.load_texture(path); +} diff --git a/src/graphics.ts b/src/graphics.ts index 2f3ace59..aaa7ad14 100644 --- a/src/graphics.ts +++ b/src/graphics.ts @@ -51,3 +51,12 @@ export function spr( sh = sh || h; core.spr(x, y, w, h, sx, sy, sw, sh); } + +/** + * Set the specified texture as the current texture for calls to e.g. spr(). + * + * @param id - The identifier of the texture to use. + */ +export function use_texture(id: number) { + core.use_texture(id); +} diff --git a/src/lib.rs b/src/lib.rs index c0e5aa6a..f44bc109 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ use bytemuck; +use std::collections::HashMap; use wgpu::util::DeviceExt; use winit::{ event::*, @@ -65,7 +66,8 @@ struct State { vertex_buffer: wgpu::Buffer, max_vertices: usize, - diffuse_bind_group: wgpu::BindGroup, + sprite_bind_group_layout: wgpu::BindGroupLayout, + sprite_textures: HashMap, screen_uniform: ScreenUniforms, screen_uniform_buffer: wgpu::Buffer, @@ -146,11 +148,12 @@ impl State { }; surface.configure(&device, &config); - let diffuse_bytes = include_bytes!("happy-tree.png"); - let diffuse_texture = - texture::Texture::from_bytes(&device, &queue, diffuse_bytes, "happy-tree.png").unwrap(); + // TODO: DELETE THIS + // let diffuse_bytes = include_bytes!("happy-tree.png"); + // let diffuse_texture = + // texture::Texture::from_bytes(&device, &queue, diffuse_bytes, "happy-tree.png").unwrap(); - let texture_bind_group_layout = + let sprite_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { entries: &[ wgpu::BindGroupLayoutEntry { @@ -172,23 +175,24 @@ impl State { count: None, }, ], - label: Some("texture_bind_group_layout"), + label: Some("sprite_bind_group_layout"), }); - let diffuse_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - layout: &texture_bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::TextureView(&diffuse_texture.view), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler), - }, - ], - label: Some("diffuse_bind_group"), - }); + // TODO: DELETE THIS + // let sprite_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + // layout: &sprite_bind_group_layout, + // entries: &[ + // wgpu::BindGroupEntry { + // binding: 0, + // resource: wgpu::BindingResource::TextureView(&diffuse_texture.view), + // }, + // wgpu::BindGroupEntry { + // binding: 1, + // resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler), + // }, + // ], + // label: Some("diffuse_bind_group"), + // }); let screen_uniform = ScreenUniforms::new(size.width, size.height); let screen_uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { @@ -229,10 +233,7 @@ impl State { let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("Render Pipeline Layout"), - bind_group_layouts: &[ - &texture_bind_group_layout, - &screen_uniform_bind_group_layout, - ], + bind_group_layouts: &[&sprite_bind_group_layout, &screen_uniform_bind_group_layout], push_constant_ranges: &[], }); @@ -294,7 +295,8 @@ impl State { render_pipeline, vertex_buffer, max_vertices, - diffuse_bind_group, + sprite_bind_group_layout, + sprite_textures: HashMap::new(), screen_uniform, screen_uniform_buffer, screen_uniform_bind_group, @@ -348,6 +350,31 @@ impl State { color: Some(cc.color), commands: Vec::new(), }), + GraphicsCommand::CreateTexture(ct) => { + let texture = texture::Texture::from_image( + &self.device, + &self.queue, + &ct.image, + Some(&ct.label), + ); + 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: Some(&ct.label), + }); + + self.sprite_textures.insert(ct.id, sprite_bind_group); + } GraphicsCommand::EndFrame => (), other => match passes.last_mut() { Some(pass) => pass.commands.push(other), @@ -395,6 +422,7 @@ impl State { depth_stencil_attachment: None, }); + let mut texture_id = None; vertices.clear(); for command in pass.commands { match command { @@ -428,19 +456,29 @@ impl State { }); } - GraphicsCommand::Clear(_) => (), // Already handled - GraphicsCommand::EndFrame => (), // Should never appear + GraphicsCommand::UseTexture(id) => texture_id = Some(id), + GraphicsCommand::CreateTexture(_) => (), // Already handled + GraphicsCommand::Clear(_) => (), // Already handled + GraphicsCommand::EndFrame => (), // Should never appear } } - assert!(vertices.len() < self.max_vertices); // ! - self.queue - .write_buffer(&self.vertex_buffer, 0, bytemuck::cast_slice(&vertices)); - render_pass.set_pipeline(&self.render_pipeline); - render_pass.set_bind_group(0, &self.diffuse_bind_group, &[]); - render_pass.set_bind_group(1, &self.screen_uniform_bind_group, &[]); - render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); - render_pass.draw(0..(vertices.len() as u32), 0..1); + if let Some(id) = texture_id { + assert!(vertices.len() < self.max_vertices); // ! + self.queue.write_buffer( + &self.vertex_buffer, + 0, + bytemuck::cast_slice(&vertices), + ); + render_pass.set_pipeline(&self.render_pipeline); + + let bind_group = self.sprite_textures.get(&id).unwrap(); + render_pass.set_bind_group(0, bind_group, &[]); + + render_pass.set_bind_group(1, &self.screen_uniform_bind_group, &[]); + render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); + render_pass.draw(0..(vertices.len() as u32), 0..1); + } } // Submit will accept anything that implements IntoIter diff --git a/src/main.ts b/src/main.ts index 4306f38d..4a987474 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,12 +1,17 @@ -import { cls, print, spr } from "./graphics.ts"; +import { cls, print, spr, use_texture } from "./graphics.ts"; +import { load_texture } from "./assets.ts"; + +let the_texture = 0; export function init() { print("Hello world!"); + the_texture = load_texture("./src/happy-tree.png"); } 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); } diff --git a/src/script.rs b/src/script.rs index 67bdc032..d1bb9828 100644 --- a/src/script.rs +++ b/src/script.rs @@ -12,6 +12,8 @@ use graphics::GraphicsCommand; mod typescript; use typescript::transpile_to_javascript; +pub mod assets; + struct Loader {} impl Loader { @@ -42,6 +44,7 @@ pub struct ScriptContext { draw: Value, gfx: graphics::GraphicsAPI, + _assets: assets::AssetsAPI, gfx_receive: Receiver, } @@ -56,8 +59,10 @@ impl ScriptContext { let (gfx_send, gfx_receive) = channel(); - let gfx = graphics::GraphicsAPI::define(&context, gfx_send) + 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 module = context .import_module("./src/main.ts", "") @@ -82,6 +87,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 b3058396..0156b6ee 100644 --- a/src/script/graphics.rs +++ b/src/script/graphics.rs @@ -1,4 +1,4 @@ -use oden_js::{module, ContextRef, Value, ValueResult}; +use oden_js::{module, ContextRef}; use std::sync::mpsc::Sender; use std::sync::Arc; @@ -24,11 +24,20 @@ pub struct SpriteCommand { pub sh: f32, } +#[derive(Debug)] +pub struct CreateTextureCommand { + pub id: u32, + pub image: image::DynamicImage, + pub label: String, +} + #[derive(Debug)] pub enum GraphicsCommand { Clear(ClearCommand), Print(PrintCommand), Sprite(SpriteCommand), + CreateTexture(CreateTextureCommand), + UseTexture(u32), EndFrame, } @@ -41,33 +50,19 @@ impl GraphicsImpl { GraphicsImpl { sender } } - fn print_fn(&self, ctx: &ContextRef, text: String) -> ValueResult { + fn print(&self, text: String) -> () { let _ = self .sender .send(GraphicsCommand::Print(PrintCommand { text })); - - Ok(Value::undefined(ctx)) } - fn cls_fn(&self, ctx: &ContextRef, r: f64, g: f64, b: f64) -> ValueResult { + fn cls(&self, r: f64, g: f64, b: f64) -> () { let _ = self.sender.send(GraphicsCommand::Clear(ClearCommand { color: [r, g, b, 1.0], })); - Ok(Value::undefined(ctx)) } - fn spr_fn( - &self, - ctx: &ContextRef, - x: f32, - y: f32, - w: f32, - h: f32, - u: f32, - v: f32, - sw: f32, - sh: f32, - ) -> ValueResult { + fn spr(&self, x: f32, y: f32, w: f32, h: f32, u: f32, v: f32, sw: f32, sh: f32) { let _ = self.sender.send(GraphicsCommand::Sprite(SpriteCommand { x, y, @@ -78,7 +73,10 @@ impl GraphicsImpl { sw, sh, })); - Ok(Value::undefined(ctx)) + } + + fn use_texture(&self, id: u32) { + let _ = self.sender.send(GraphicsCommand::UseTexture(id)); } } @@ -94,16 +92,14 @@ impl GraphicsAPI { let gfx = gfx.clone(); builder.export( "print", - ctx.new_fn(move |ctx: &ContextRef, t: String| gfx.print_fn(ctx, t))?, + ctx.new_fn(move |_: &ContextRef, t: String| gfx.print(t))?, )?; } { let gfx = gfx.clone(); builder.export( "cls", - ctx.new_fn(move |ctx: &ContextRef, r: f64, g: f64, b: f64| { - gfx.cls_fn(ctx, r, g, b) - })?, + ctx.new_fn(move |_: &ContextRef, r: f64, g: f64, b: f64| gfx.cls(r, g, b))?, )?; } { @@ -111,7 +107,7 @@ impl GraphicsAPI { builder.export( "spr", ctx.new_fn( - move |ctx: &ContextRef, + move |_: &ContextRef, x: f32, y: f32, w: f32, @@ -119,12 +115,17 @@ impl GraphicsAPI { u: f32, v: f32, sw: f32, - sh: f32| { - gfx.spr_fn(ctx, x, y, w, h, u, v, sw, sh) - }, + sh: f32| gfx.spr(x, y, w, h, u, v, sw, sh), )?, )?; } + { + let gfx = gfx.clone(); + builder.export( + "use_texture", + ctx.new_fn(move |_: &ContextRef, id: u32| gfx.use_texture(id))?, + )?; + } builder.build("graphics-core")?; Ok(GraphicsAPI { gfx }) } diff --git a/src/texture.rs b/src/texture.rs index f3d869c8..eb1407e0 100644 --- a/src/texture.rs +++ b/src/texture.rs @@ -1,4 +1,3 @@ -use anyhow::*; use image::GenericImageView; pub struct Texture { @@ -8,22 +7,22 @@ pub struct Texture { } impl Texture { - pub fn from_bytes( - device: &wgpu::Device, - queue: &wgpu::Queue, - bytes: &[u8], - label: &str, - ) -> Result { - let img = image::load_from_memory(bytes)?; - Self::from_image(device, queue, &img, Some(label)) - } + // pub fn from_bytes( + // device: &wgpu::Device, + // queue: &wgpu::Queue, + // bytes: &[u8], + // label: &str, + // ) -> Result { + // let img = image::load_from_memory(bytes)?; + // Ok(Self::from_image(device, queue, &img, Some(label))) + // } pub fn from_image( device: &wgpu::Device, queue: &wgpu::Queue, img: &image::DynamicImage, label: Option<&str>, - ) -> Result { + ) -> Self { let rgba = img.to_rgba8(); let dimensions = img.dimensions(); @@ -70,10 +69,10 @@ impl Texture { ..Default::default() }); - Ok(Self { + Self { texture, view, sampler, - }) + } } } 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 5f79ec8d..7f0afd2e 100644 --- a/types/graphics-core.d.ts +++ b/types/graphics-core.d.ts @@ -12,3 +12,4 @@ export function spr( sw: number, sh: number ); +export function use_texture(id: number);