[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),
#[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,

View file

@ -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<String>,
}
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<Vec<u8>> {
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<graphics::GraphicsCommand> {
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<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();
}
}