From 22732c2b0522e1c678aed1da6a3ff10c37ae8bf5 Mon Sep 17 00:00:00 2001 From: John Doty Date: Thu, 31 Aug 2023 21:14:27 -0700 Subject: [PATCH] [oden] Catch and render script errors, stop crashing This is kinda nice actually --- oden-js/src/lib.rs | 2 +- src/script.rs | 100 ++++++++++++++++++++++++++++++++------------- 2 files changed, 72 insertions(+), 30 deletions(-) diff --git a/oden-js/src/lib.rs b/oden-js/src/lib.rs index cf11d155..95485c5e 100644 --- a/oden-js/src/lib.rs +++ b/oden-js/src/lib.rs @@ -41,7 +41,7 @@ pub enum Error { ConversionError(String), #[error("an error occurred calling a rust function: {0}")] RustFunctionError(String), - #[error("an exception was thrown during evaluation: {1}\nStack: {2}")] + #[error("an exception was thrown during evaluation: {1}\nStack:\n{2}")] Exception(Value, String, String), #[error("out of memory")] OutOfMemory, diff --git a/src/script.rs b/src/script.rs index be3abd74..c36f804e 100644 --- a/src/script.rs +++ b/src/script.rs @@ -15,7 +15,7 @@ mod input; mod io; mod time; -use graphics::GraphicsCommand; +use graphics::{ClearCommand, GraphicsCommand, PrintCommand}; mod typescript; use typescript::transpile_to_javascript; @@ -66,6 +66,8 @@ pub struct ScriptContext { time: time::TimeAPI, input: input::InputAPI, + + error_lines: Vec, } impl ScriptContext { @@ -115,6 +117,8 @@ impl ScriptContext { time, input, + + error_lines: Vec::new(), }) } @@ -123,15 +127,17 @@ impl ScriptContext { pub fn suspend(&self) -> Option> { let _span = span!("script suspend"); if !self.suspend.is_undefined() { - let suspend_state = self - .suspend - .call(&self.context, &[&self.state]) - .expect("Exception in suspend"); - Some( - suspend_state - .serialize(&self.context) - .expect("Unable to serialize state"), - ) + match self.suspend.call(&self.context, &[&self.state]) { + Ok(suspend_state) => Some( + suspend_state + .serialize(&self.context) + .expect("Unable to serialize state"), + ), + Err(e) => { + eprintln!("WARNING: Error during suspend, state will not be saved: {e}"); + None + } + } } else { None } @@ -151,6 +157,10 @@ impl ScriptContext { pub fn update(&mut self) { let _span = span!("script update"); + if self.error_lines.len() > 0 { + return; // Don't bother, nothing. + } + // Do we update the frame time before of after async completion? // Hmmmmm. self.time.set_frame_time(Instant::now()); @@ -159,37 +169,69 @@ impl ScriptContext { // promise completions. { let _span = span!("process jobs"); - self.context - .process_all_jobs() - .expect("Error processing async jobs"); + self.handle_result(self.context.process_all_jobs()); } // Now run the update function. - { + if self.error_lines.len() == 0 { let _span = span!("javascript update"); let old_state = &self.state; - self.state = self - .update - .call(&self.context, &[old_state]) - .expect("Exception in update"); + self.state = match self.update.call(&self.context, &[old_state]) { + Ok(v) => v, + Err(e) => { + self.handle_error(e); + self.context.null() + } + } } } pub fn render(&mut self) -> Vec { let _span = span!("script render"); + if self.error_lines.len() > 0 { + // TODO: Use font 0 for a fallback. + let mut commands = vec![ + GraphicsCommand::Clear(ClearCommand { + color: [0.0, 0.0, 1.0, 1.0], + }), + GraphicsCommand::Print(PrintCommand { + text: "FATAL SCRIPT ERROR".to_owned(), + pos: [6.0, 6.0], + }), + ]; - self.draw - .call(&self.context, &[&self.state]) - .expect("Exception in draw"); - self.gfx.end_frame(); - - let mut commands = Vec::new(); - loop { - match self.gfx_receive.recv().unwrap() { - GraphicsCommand::EndFrame => break, - other => commands.push(other), + let mut y = 20.0; + for line in &self.error_lines { + commands.push(GraphicsCommand::Print(PrintCommand { + text: line.clone(), + pos: [6.0, y], + })); + y += 8.0; } + + commands + } else { + self.handle_result(self.draw.call(&self.context, &[&self.state])); + self.gfx.end_frame(); + + let mut commands = Vec::new(); + loop { + match self.gfx_receive.recv().unwrap() { + GraphicsCommand::EndFrame => break, + other => commands.push(other), + } + } + commands } - commands + } + + fn handle_result(&mut self, result: Result) { + if let Err(e) = result { + self.handle_error(e); + } + } + + fn handle_error(&mut self, error: oden_js::Error) { + self.error_lines = error.to_string().lines().map(|l| l.to_owned()).collect(); } }