diff --git a/oden-js/src/context.rs b/oden-js/src/context.rs index b11b82df..ae994378 100644 --- a/oden-js/src/context.rs +++ b/oden-js/src/context.rs @@ -1,6 +1,6 @@ use crate::{ - callback::new_fn, conversion::RustFunction, module::Module, Atom, ClassID, Error, Promise, - Result, Runtime, Value, ValueRef, ValueResult, + callback::new_fn, conversion::RustFunction, module::Module, Atom, ClassID, Error, Result, + Runtime, Value, ValueRef, ValueResult, }; use bitflags::bitflags; use oden_js_sys as sys; @@ -34,7 +34,6 @@ pub struct ContextRef { pub(crate) ctx: *mut sys::JSContext, } -// TODO: Should all these require mutability to enforce single-threadedness? impl ContextRef { pub(crate) fn from_raw(ctx: *mut sys::JSContext) -> Self { ContextRef { ctx } @@ -278,64 +277,6 @@ impl ContextRef { Value::from_raw(v, self) } - /// Construct a new promise. - pub fn new_promise(&self) -> Result { - unsafe { - let mut resolving_funcs: [sys::JSValue; 2] = - [sys::JS_MakeUndefined(), sys::JS_MakeUndefined()]; - let val = - sys::JS_NewPromiseCapability(self.ctx, &mut resolving_funcs as *mut sys::JSValue); - - if sys::JS_ValueGetTag(val) == sys::JS_TAG_EXCEPTION { - Err(self.exception_error()) - } else { - Ok(Promise::new( - Value::from_raw(val, self), - Value::from_raw(resolving_funcs[0], self), - Value::from_raw(resolving_funcs[1], self), - )) - } - } - } - - /// Construct a new exception object, suitable for throwing. - pub fn new_error(&self, message: &str) -> Value { - let e = match self.new_string(message) { - Ok(e) => e, - Err(_) => match self.new_string("INTERNAL ERROR: Embedded NUL in message") { - Ok(e) => e, - - // Faulting this hard is inexcusable. - Err(_) => return self.exception(), - }, - }; - unsafe { - let err = Value::from_raw(sys::JS_NewError(self.ctx), self); - - // NOTE: Throughout this function we work at the lower-level - // error handling stuff because the errors are easier to - // manage. (We know how it can fail!) - if sys::JS_ValueGetTag(err.val) == sys::JS_TAG_EXCEPTION { - // GIVE UP; This is out of memory anyway things probably - // went wrong because of that. We'll return *that* - // exception. - return self.exception(); - } - - sys::JS_DupValue(self.ctx, e.val); // SetProperty takes ownership. - let prop = CString::new("message").unwrap(); - if sys::JS_SetPropertyStr(self.ctx, err.val, prop.as_ptr(), e.val) == -1 { - // As before, we're just going to take the exception from - // the context, and drop the one we were trying to create - // on the floor. - return self.exception(); - } - - // We put the message in, we can return the value. - err - } - } - /// Fetch the global object for the context. pub fn global_object(&self) -> ValueResult { self.check_exception(unsafe { sys::JS_GetGlobalObject(self.ctx) }) @@ -374,23 +315,6 @@ impl ContextRef { Error::Exception(exc, desc, stack) } - - /// Process all pending async jobs. This includes all promise resolutions. - pub fn process_all_jobs(&self) -> Result<()> { - // TODO: SAFETY - // This is unsafe because multiple contexts can be sharing the same runtime and cause - // a race condition on the underlying runtime. - loop { - let mut ctx1: *mut sys::JSContext = std::ptr::null_mut(); - let err = unsafe { sys::JS_ExecutePendingJob(sys::JS_GetRuntime(self.ctx), &mut ctx1) }; - if err == 0 { - break; - } else if err < 0 { - return Err(ContextRef::from_raw(ctx1).exception_error()); - } - } - Ok(()) - } } #[derive(Debug)] diff --git a/oden-js/src/lib.rs b/oden-js/src/lib.rs index 1784abb6..3246ecbc 100644 --- a/oden-js/src/lib.rs +++ b/oden-js/src/lib.rs @@ -8,7 +8,6 @@ mod class; mod context; mod conversion; pub mod module; -mod promise; mod runtime; mod value; @@ -16,7 +15,6 @@ pub use atom::{Atom, AtomRef}; pub use class::{Class, ClassID}; pub use context::{Context, ContextRef, EvalFlags}; pub use conversion::*; -pub use promise::Promise; pub use runtime::Runtime; pub use value::{Value, ValueRef, ValueType}; @@ -51,20 +49,6 @@ pub enum Error { ParseError(String, String), } -impl Error { - // Convert the error into an exception-type object which can be - // thrown. This is *different* from try_into_value which just propagates - // the error. - pub fn to_js_error(&self, context: &ContextRef) -> Value { - if let Error::Exception(e, _, _) = self { - e.clone() - } else { - let message = self.to_string(); - context.new_error(&message) - } - } -} - impl From for Error { fn from(_: NulError) -> Self { Error::UnexpectedNul @@ -91,9 +75,35 @@ pub(crate) fn throw_error(context: &ContextRef, error: Error) -> sys::JSValue { } pub(crate) fn throw_string(context: &ContextRef, message: String) -> sys::JSValue { - let err = context.new_error(&message); - unsafe { - sys::JS_DupValue(context.ctx, err.val); - sys::JS_Throw(context.ctx, err.val) + let ctx = context.ctx; + match context.new_string(&message) { + Ok(e) => unsafe { + // Because context.new_string yields an owned Value, and will + // clean it up on the way out, we need to explicitly DupValue a + // reference for the `Throw` to own. + let err = sys::JS_NewError(ctx); + if sys::JS_ValueGetTag(err) == sys::JS_TAG_EXCEPTION { + // GIVE UP; this is out of memory anyway things probably went + // wrong because of that. + return err; + } + + sys::JS_DupValue(ctx, e.val); // SetProperty takes ownership. + let prop = CString::new("message").unwrap(); + if sys::JS_SetPropertyStr(ctx, err, prop.as_ptr(), e.val) == -1 { + // Also an out of memory but we need to free the error object + // on our way out. + sys::JS_FreeValue(ctx, err); + return sys::JS_MakeException(); // JS_EXCEPTION + } + + sys::JS_Throw(ctx, err) + }, + Err(_) => unsafe { + sys::JS_Throw( + ctx, + sys::JS_NewString(ctx, "Errors within errors: embedded nulls in the description of the error that occurred".as_bytes().as_ptr() as *const i8), + ) + }, } } diff --git a/oden-js/src/promise.rs b/oden-js/src/promise.rs deleted file mode 100644 index 152085b2..00000000 --- a/oden-js/src/promise.rs +++ /dev/null @@ -1,34 +0,0 @@ -use crate::{ContextRef, Value, ValueRef}; - -#[derive(Debug, Clone)] -pub struct Promise { - pub object: Value, - pub resolve_fn: Value, - pub reject_fn: Value, -} - -impl Promise { - pub(crate) fn new(object: Value, resolve_fn: Value, reject_fn: Value) -> Self { - Promise { - object, - resolve_fn, - reject_fn, - } - } - - pub fn dup(&self, ctx: &ContextRef) -> Self { - Promise { - object: self.object.dup(ctx), - resolve_fn: self.resolve_fn.dup(ctx), - reject_fn: self.reject_fn.dup(ctx), - } - } - - pub fn resolve(self, context: &ContextRef, value: &ValueRef) { - let _ = self.resolve_fn.call(context, &[value]); - } - - pub fn reject(self, context: &ContextRef, value: &ValueRef) { - let _ = self.reject_fn.call(context, &[value]); - } -} diff --git a/oden-js/src/value.rs b/oden-js/src/value.rs index dfc8d65c..58a42134 100644 --- a/oden-js/src/value.rs +++ b/oden-js/src/value.rs @@ -310,16 +310,14 @@ impl ValueRef { Ok(result) } - pub fn call(&self, ctx: &ContextRef, args: &[&ValueRef]) -> Result { - // TODO: There *must* be a way to avoid this allocation. - let mut args: Vec = args.iter().map(|v| v.val).collect(); + pub fn call(&self, ctx: &ContextRef) -> Result { unsafe { ctx.check_exception(sys::JS_Call( ctx.ctx, self.val, sys::JS_MakeUndefined(), - args.len() as i32, - args.as_mut_ptr(), + 0, + std::ptr::null_mut(), )) } } @@ -347,16 +345,9 @@ impl Value { /// the runtime of the specified context, if not the context itself. This /// function makes no attempt to validate this. pub(crate) fn from_raw(val: sys::JSValue, ctx: &ContextRef) -> Self { - Value::from_raw_rt( - val, - Runtime::from_raw(unsafe { sys::JS_GetRuntime(ctx.ctx) }), - ) - } - - pub(crate) fn from_raw_rt(val: sys::JSValue, rt: Runtime) -> Self { Value { value: ValueRef::from_raw(val), - rt, + rt: Runtime::from_raw(unsafe { sys::JS_GetRuntime(ctx.ctx) }), } } @@ -403,15 +394,6 @@ impl fmt::Debug for Value { } } -impl Clone for Value { - fn clone(&self) -> Self { - unsafe { - sys::JS_DupValueRT(self.rt.rt, self.val); - } - Value::from_raw_rt(self.val, self.rt.clone()) - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/lib.rs b/src/lib.rs index 8577050a..f44bc109 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -178,6 +178,22 @@ impl State { label: Some("sprite_bind_group_layout"), }); + // 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 { label: Some("Screen Uniform Buffer"), @@ -189,7 +205,7 @@ impl State { device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { entries: &[wgpu::BindGroupLayoutEntry { binding: 0, - visibility: wgpu::ShaderStages::VERTEX | wgpu::ShaderStages::FRAGMENT, + visibility: wgpu::ShaderStages::VERTEX, ty: wgpu::BindingType::Buffer { ty: wgpu::BufferBindingType::Uniform, has_dynamic_offset: false, @@ -209,28 +225,28 @@ impl State { label: Some("camera_bind_group"), }); - let sprite_shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { - label: Some("Sprite Shader"), - source: wgpu::ShaderSource::Wgsl(include_str!("sprite_shader.wgsl").into()), + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("Shader"), + source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()), }); - let sprite_pipeline_layout = + let render_pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("Sprite Pipeline Layout"), + label: Some("Render Pipeline Layout"), bind_group_layouts: &[&sprite_bind_group_layout, &screen_uniform_bind_group_layout], push_constant_ranges: &[], }); - let sprite_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { - label: Some("Sprite Pipeline"), - layout: Some(&sprite_pipeline_layout), + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("Render Pipeline"), + layout: Some(&render_pipeline_layout), vertex: wgpu::VertexState { - module: &sprite_shader, + module: &shader, entry_point: "vs_main", buffers: &[Vertex::desc()], }, fragment: Some(wgpu::FragmentState { - module: &sprite_shader, + module: &shader, entry_point: "fs_main", targets: &[Some(wgpu::ColorTargetState { format: config.format, @@ -276,7 +292,7 @@ impl State { queue, config, size, - render_pipeline: sprite_pipeline, + render_pipeline, vertex_buffer, max_vertices, sprite_bind_group_layout, @@ -513,7 +529,7 @@ pub async fn run() { let mut state = State::new(window).await; - let mut context = script::ScriptContext::new(); + let context = script::ScriptContext::new(); context.init(); event_loop.run(move |event, _, control_flow| { diff --git a/src/script.rs b/src/script.rs index 496b6a5d..d1bb9828 100644 --- a/src/script.rs +++ b/src/script.rs @@ -1,11 +1,9 @@ use oden_js::{ module::loader::{ModuleLoader, ModuleSource}, - Context, ContextRef, Promise, Result, Runtime, Value, ValueResult, + Context, ContextRef, Result, Runtime, Value, }; -use std::collections::HashMap; use std::ffi::OsStr; use std::path::Path; -use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::mpsc::{channel, Receiver}; pub mod graphics; @@ -39,21 +37,6 @@ impl ModuleLoader for Loader { } } -#[derive(Eq, PartialEq, Hash, Debug, Clone, Copy)] -pub struct PromiseHandle(u64); - -impl PromiseHandle { - pub fn new() -> Self { - static NEXT_ID: AtomicU64 = AtomicU64::new(0); - PromiseHandle(NEXT_ID.fetch_add(1, Ordering::SeqCst)) - } -} - -pub enum ScriptEvent { - AddPromise(PromiseHandle, Promise), - CompletePromise(PromiseHandle, Box ValueResult>), -} - pub struct ScriptContext { context: Context, init: Value, @@ -63,9 +46,6 @@ pub struct ScriptContext { gfx: graphics::GraphicsAPI, _assets: assets::AssetsAPI, gfx_receive: Receiver, - - script_receive: Receiver, - promises: HashMap, } impl ScriptContext { @@ -78,7 +58,6 @@ impl ScriptContext { context.add_intrinsic_operators(); let (gfx_send, gfx_receive) = channel(); - let (script_send, script_receive) = channel(); let gfx = graphics::GraphicsAPI::define(&context, gfx_send.clone()) .expect("Graphics module should load without error"); @@ -110,9 +89,6 @@ impl ScriptContext { gfx_receive, _assets: assets, - - script_receive, - promises: HashMap::new(), } } @@ -120,52 +96,18 @@ impl ScriptContext { // We would want a bi-directional gate for frames to not let the // game thread go to fast probably? And to discard whole frames &c. - pub fn init(&mut self) { - self.init - .call(&self.context, &[]) - .expect("Exception in init"); + pub fn init(&self) { + self.init.call(&self.context).expect("Exception in init"); } - pub fn update(&mut self) { - // Handle any promises that have completed before calling update. - while let Ok(event) = self.script_receive.try_recv() { - match event { - // TODO: Capture debugging information. - ScriptEvent::AddPromise(handle, promise) => { - self.promises.insert(handle, promise); - } - ScriptEvent::CompletePromise(handle, value_producer) => { - if let Some(promise) = self.promises.remove(&handle) { - let result = value_producer(&self.context); - match result { - Ok(v) => { - promise.resolve(&self.context, &v); - } - Err(e) => { - let error = e.to_js_error(&self.context); - promise.reject(&self.context, &error); - } - } - } - } - } - } - - // Tell the runtime to process all pending "jobs". - self.context - .process_all_jobs() - .expect("Error processing async jobs"); - - // Now run the update function. + pub fn update(&self) { self.update - .call(&self.context, &[]) + .call(&self.context) .expect("Exception in update"); } - pub fn render(&mut self) -> Vec { - self.draw - .call(&self.context, &[]) - .expect("Exception in draw"); + pub fn render(&self) -> Vec { + self.draw.call(&self.context).expect("Exception in draw"); self.gfx.end_frame(); let mut commands = Vec::new(); diff --git a/src/shader.wgsl b/src/shader.wgsl index a1ab6a39..06cfa782 100644 --- a/src/shader.wgsl +++ b/src/shader.wgsl @@ -48,30 +48,13 @@ const RES = vec2f(320.0, 240.0); // The logical resolution of the screen. return out; } -// Fragment shader.... +// Fragment shader @group(0) @binding(0) var t_diffuse : texture_2d; @group(0) @binding(1) var s_diffuse : sampler; @fragment fn fs_main(in : VertexOutput)->@location(0) vec4 { - // The "screen" is centered in the window, so anything outside of the - // screen borders should be black. But *where are they*? - let RES_AR = RES.x / RES.y; // The aspect ratio of the logical screen. - let screen_ar = screen.resolution.x / screen.resolution.y; - var black_mod = 1.0; - if (screen_ar > RES_AR) { - // Wider than tall, bars are on the left and right. - let active_width = screen.resolution.y * RES_AR; - let half_delta = (screen.resolution.x - active_width) / 2.0; - if (in.clip_position.x < half_delta || - in.clip_position.x > half_delta + active_width) { - black_mod = 0.0; - } - } else { - // Taller than wide, bars are on top and bottom. - } - let dims = vec2f(textureDimensions(t_diffuse)); - return black_mod * textureSample(t_diffuse, s_diffuse, in.tex_coords / dims); + return textureSample(t_diffuse, s_diffuse, in.tex_coords / dims); } diff --git a/src/sprite_shader.wgsl b/src/sprite_shader.wgsl deleted file mode 100644 index a1ab6a39..00000000 --- a/src/sprite_shader.wgsl +++ /dev/null @@ -1,77 +0,0 @@ -// Vertex shader - -struct ScreenUniform { - resolution : vec2f, -}; -@group(1) @binding(0) // 1. - var screen : ScreenUniform; - -struct VertexInput { - @location(0) position : vec3, @location(1) tex_coords : vec2, -}; - -struct VertexOutput { - @builtin(position) clip_position : vec4, - @location(0) tex_coords : vec2, -}; - -const RES = vec2f(320.0, 240.0); // The logical resolution of the screen. - -@vertex fn vs_main(model : VertexInput)->VertexOutput { - var out : VertexOutput; - out.tex_coords = model.tex_coords; - - let RES_AR = RES.x / RES.y; // The aspect ratio of the logical screen. - - // the actual resolution of the screen. - let screen_ar = screen.resolution.x / screen.resolution.y; - - // Compute the difference in resolution ... correctly? - // - // nudge is the amount to add to the logical resolution so that the pixels - // stay the same size but we respect the aspect ratio of the screen. (So - // there's more of them in either the x or y direction.) - var nudge = vec2f(0.0); - if (screen_ar > RES_AR) { - nudge.x = (RES.y * screen_ar) - RES.x; - } else { - nudge.y = (RES.x / screen_ar) - RES.y; - } - var new_logical_resolution = RES + nudge; - - // Now we can convert the incoming position to clip space, in the new screen. - let in_pos = vec2f(model.position.x, model.position.y); - let centered = in_pos + (nudge / 2.0); - let position = (2.0 * centered / new_logical_resolution) - 1.0; - - out.clip_position = vec4f(position, model.position.z, 1.0); - return out; -} - -// Fragment shader.... - -@group(0) @binding(0) var t_diffuse : texture_2d; -@group(0) @binding(1) var s_diffuse : sampler; - -@fragment fn fs_main(in : VertexOutput)->@location(0) vec4 { - // The "screen" is centered in the window, so anything outside of the - // screen borders should be black. But *where are they*? - let RES_AR = RES.x / RES.y; // The aspect ratio of the logical screen. - let screen_ar = screen.resolution.x / screen.resolution.y; - var black_mod = 1.0; - if (screen_ar > RES_AR) { - // Wider than tall, bars are on the left and right. - let active_width = screen.resolution.y * RES_AR; - let half_delta = (screen.resolution.x - active_width) / 2.0; - if (in.clip_position.x < half_delta || - in.clip_position.x > half_delta + active_width) { - black_mod = 0.0; - } - } else { - // Taller than wide, bars are on top and bottom. - } - - let dims = vec2f(textureDimensions(t_diffuse)); - - return black_mod * textureSample(t_diffuse, s_diffuse, in.tex_coords / dims); -}