[oden] Hot-reload script files

This commit is contained in:
John Doty 2023-08-19 16:54:50 -07:00
parent 642ced45f8
commit a850c3cc58
7 changed files with 241 additions and 9 deletions

View file

@ -1,9 +1,11 @@
use notify::{RecommendedWatcher, RecursiveMode, Watcher};
use oden_js::{
module::loader::{ModuleLoader, ModuleSource},
Context, ContextRef, Result, Runtime, Value,
};
use std::ffi::OsStr;
use std::sync::mpsc::{channel, Receiver};
use std::sync::mpsc::{channel, Receiver, Sender};
use std::sync::{Arc, Mutex};
use std::time::Instant;
use tracy_client::span;
use winit::event::*;
@ -18,11 +20,19 @@ use graphics::GraphicsCommand;
mod typescript;
use typescript::transpile_to_javascript;
struct Loader {}
struct Loader {
watcher: Arc<Mutex<RecommendedWatcher>>,
}
impl Loader {
pub fn new() -> Loader {
Loader {}
pub fn new(reload_trigger: Sender<()>) -> Loader {
let watcher = Arc::new(Mutex::new(
notify::recommended_watcher(move |_| {
let _ = reload_trigger.send(());
})
.expect("Unable to create watcher"),
));
Loader { watcher }
}
}
@ -37,6 +47,8 @@ impl ModuleLoader for Loader {
contents
};
let mut watcher = self.watcher.lock().unwrap();
let _ = watcher.watch(&path, RecursiveMode::NonRecursive);
Ok(ModuleSource::JavaScript(contents))
}
}
@ -46,6 +58,8 @@ pub struct ScriptContext {
init: Value,
update: Value,
draw: Value,
suspend: Value,
resume: Value,
state: Value,
@ -57,9 +71,9 @@ pub struct ScriptContext {
}
impl ScriptContext {
pub fn new() -> Self {
pub fn new(reload_trigger: Sender<()>) -> Self {
let mut runtime = Runtime::new();
runtime.set_module_loader(Loader::new());
runtime.set_module_loader(Loader::new(reload_trigger));
let mut context = Context::new(runtime);
context.add_intrinsic_bigfloat();
@ -82,6 +96,12 @@ impl ScriptContext {
let init = module
.get_export(&context, "init")
.expect("Unable to fetch init");
let suspend = module
.get_export(&context, "suspend")
.expect("Unable to fetch suspend");
let resume = module
.get_export(&context, "resume")
.expect("Unable to fetch suspend");
let update = module
.get_export(&context, "update")
.expect("Unable to fetch update");
@ -95,6 +115,8 @@ impl ScriptContext {
context,
init,
suspend,
resume,
update,
draw,
@ -108,6 +130,42 @@ impl ScriptContext {
}
}
/// Allow the script to save its state before we destroy it and re-create
/// it.
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"),
)
} else {
None
}
}
/// Allow the script to restore its state after being destroyed and
/// re-created.
pub fn resume(&mut self, state: &[u8]) {
let _span = span!("script resume");
if !self.resume.is_undefined() {
let suspend_state = self
.context
.deserialize(state)
.expect("Unable to deserialize state");
let prev_state = self.state.clone();
self.state = self
.resume
.call(&self.context, &[&suspend_state, &prev_state])
.expect("Exception in resume");
}
}
// TODO: The script could really be on a background thread you know.
// 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.