[oden] Catch and render script errors, stop crashing

This is kinda nice actually
This commit is contained in:
John Doty 2023-08-31 21:14:27 -07:00
parent a08bc07cbb
commit 22732c2b05
2 changed files with 72 additions and 30 deletions

View file

@ -41,7 +41,7 @@ pub enum Error {
ConversionError(String), ConversionError(String),
#[error("an error occurred calling a rust function: {0}")] #[error("an error occurred calling a rust function: {0}")]
RustFunctionError(String), 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), Exception(Value, String, String),
#[error("out of memory")] #[error("out of memory")]
OutOfMemory, OutOfMemory,

View file

@ -15,7 +15,7 @@ mod input;
mod io; mod io;
mod time; mod time;
use graphics::GraphicsCommand; use graphics::{ClearCommand, GraphicsCommand, PrintCommand};
mod typescript; mod typescript;
use typescript::transpile_to_javascript; use typescript::transpile_to_javascript;
@ -66,6 +66,8 @@ pub struct ScriptContext {
time: time::TimeAPI, time: time::TimeAPI,
input: input::InputAPI, input: input::InputAPI,
error_lines: Vec<String>,
} }
impl ScriptContext { impl ScriptContext {
@ -115,6 +117,8 @@ impl ScriptContext {
time, time,
input, input,
error_lines: Vec::new(),
}) })
} }
@ -123,15 +127,17 @@ impl ScriptContext {
pub fn suspend(&self) -> Option<Vec<u8>> { pub fn suspend(&self) -> Option<Vec<u8>> {
let _span = span!("script suspend"); let _span = span!("script suspend");
if !self.suspend.is_undefined() { if !self.suspend.is_undefined() {
let suspend_state = self match self.suspend.call(&self.context, &[&self.state]) {
.suspend Ok(suspend_state) => Some(
.call(&self.context, &[&self.state]) suspend_state
.expect("Exception in suspend"); .serialize(&self.context)
Some( .expect("Unable to serialize state"),
suspend_state ),
.serialize(&self.context) Err(e) => {
.expect("Unable to serialize state"), eprintln!("WARNING: Error during suspend, state will not be saved: {e}");
) None
}
}
} else { } else {
None None
} }
@ -151,6 +157,10 @@ impl ScriptContext {
pub fn update(&mut self) { pub fn update(&mut self) {
let _span = span!("script update"); 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? // Do we update the frame time before of after async completion?
// Hmmmmm. // Hmmmmm.
self.time.set_frame_time(Instant::now()); self.time.set_frame_time(Instant::now());
@ -159,37 +169,69 @@ impl ScriptContext {
// promise completions. // promise completions.
{ {
let _span = span!("process jobs"); let _span = span!("process jobs");
self.context self.handle_result(self.context.process_all_jobs());
.process_all_jobs()
.expect("Error processing async jobs");
} }
// Now run the update function. // Now run the update function.
{ if self.error_lines.len() == 0 {
let _span = span!("javascript update"); let _span = span!("javascript update");
let old_state = &self.state; let old_state = &self.state;
self.state = self self.state = match self.update.call(&self.context, &[old_state]) {
.update Ok(v) => v,
.call(&self.context, &[old_state]) Err(e) => {
.expect("Exception in update"); self.handle_error(e);
self.context.null()
}
}
} }
} }
pub fn render(&mut self) -> Vec<graphics::GraphicsCommand> { pub fn render(&mut self) -> Vec<graphics::GraphicsCommand> {
let _span = span!("script render"); 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 let mut y = 20.0;
.call(&self.context, &[&self.state]) for line in &self.error_lines {
.expect("Exception in draw"); commands.push(GraphicsCommand::Print(PrintCommand {
self.gfx.end_frame(); text: line.clone(),
pos: [6.0, y],
let mut commands = Vec::new(); }));
loop { y += 8.0;
match self.gfx_receive.recv().unwrap() {
GraphicsCommand::EndFrame => break,
other => commands.push(other),
} }
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<T>(&mut self, result: Result<T>) {
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();
} }
} }