use oden_js::{ module::loader::{ModuleLoader, ModuleSource}, Context, ContextRef, Result, Runtime, Value, }; use std::ffi::OsStr; use std::sync::mpsc::{channel, Receiver}; use std::time::Instant; pub mod graphics; use graphics::GraphicsCommand; mod typescript; use typescript::transpile_to_javascript; mod io; mod time; struct Loader {} impl Loader { pub fn new() -> Loader { Loader {} } } impl ModuleLoader for Loader { fn load(&self, _context: &ContextRef, name: &str) -> Result { eprintln!("Loading {name}..."); let path = io::resolve_path(name, &["ts"])?; let contents = std::fs::read_to_string(&path)?; let contents = if path.extension().and_then(OsStr::to_str) == Some("ts") { transpile_to_javascript(name, contents)? } else { contents }; Ok(ModuleSource::JavaScript(contents)) } } pub struct ScriptContext { context: Context, init: Value, update: Value, draw: Value, gfx: graphics::GraphicsAPI, gfx_receive: Receiver, time: time::TimeAPI, } impl ScriptContext { pub fn new() -> Self { let runtime = Runtime::with_loader(Loader::new()); let mut context = Context::new(runtime); context.add_intrinsic_bigfloat(); context.add_intrinsic_bigdecimal(); context.add_intrinsic_operators(); let (gfx_send, gfx_receive) = channel(); let gfx = graphics::GraphicsAPI::define(&context, gfx_send.clone()) .expect("Graphics module should load without error"); let _io = io::IoAPI::define(&context).expect("IO module should load without error"); let time = time::TimeAPI::define(&context).expect("Time module should load without error"); let module = context .import_module("./main.ts", "") .expect("Unable to load main"); let init = module .get_export(&context, "init") .expect("Unable to fetch init"); let update = module .get_export(&context, "update") .expect("Unable to fetch update"); let draw = module .get_export(&context, "draw") .expect("Unable to fetch draw"); ScriptContext { context, init, update, draw, gfx, gfx_receive, time, } } // 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. pub fn init(&mut self) { self.init .call(&self.context, &[]) .expect("Exception in init"); } pub fn update(&mut self) { // Do we update the frame time before of after async completion? // Hmmmmm. self.time.set_frame_time(Instant::now()); // Tell the runtime to process all pending "jobs". This includes // promise completions. self.context .process_all_jobs() .expect("Error processing async jobs"); // Now run the update function. self.update .call(&self.context, &[]) .expect("Exception in update"); } pub fn render(&mut self) -> Vec { self.draw .call(&self.context, &[]) .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), } } commands } }