diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/file.rs | 24 | ||||
| -rw-r--r-- | src/main.rs | 2 | ||||
| -rw-r--r-- | src/scene.rs | 4 | ||||
| -rw-r--r-- | src/script.rs | 249 | ||||
| -rw-r--r-- | src/script/wasm.rs | 307 |
5 files changed, 499 insertions, 87 deletions
diff --git a/src/file.rs b/src/file.rs index 20fdd68..9b9888c 100644 --- a/src/file.rs +++ b/src/file.rs @@ -2,7 +2,7 @@ use std::{ffi::OsStr, path::Path}; use rustc_hash::FxHashMap; use serde::{Deserialize, Serialize}; -use smol::{Task, block_on, unblock}; +use smol::{Task, unblock}; use crate::{ scene::{ResourceRequirements, Scene, scene_id_by_name}, @@ -63,7 +63,10 @@ fn normalize_game_manifest(manifest: TomlGameManifest) -> std::io::Result<GameMa .into_iter() .map(|script| match script { TomlScript::Wasm { name, path } => { - scripts.push(ScriptDeclaration::Wasm { path }); + scripts.push(ScriptDeclaration::Wasm { + name: name.clone(), + path, + }); (name, scripts.len() - 1) } }) @@ -159,21 +162,8 @@ impl FileManager { Ok(self.cache.get(&path).unwrap()) } - pub fn get_loaded_file(&mut self, path: Box<Path>) -> Option<std::io::Result<&FileData>> { - if let Some(task) = self.active_tasks.get(path.as_ref()) - && task.is_finished() - { - let data = match block_on(self.active_tasks.remove(&path).unwrap()) { - Ok(data) => data, - Err(e) => return Some(Err(e)), - }; - self.cache.entry(path.clone()).insert_entry(data); - Some(Ok(self.cache.get(&path).unwrap())) - } else if let Some(data) = self.cache.get(&path) { - Some(Ok(data)) - } else { - None - } + pub fn get_cached_file(&self, path: &Path) -> Option<&FileData> { + self.cache.get(path) } pub fn start_loading_file(&mut self, path: impl AsRef<Path>, wasm_compiler: &wasmi::Engine) { diff --git a/src/main.rs b/src/main.rs index 2978393..f5c869e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -32,6 +32,7 @@ fn main() { buffer: &mut buffer, input: &input, time: &time, + operation_queue: Vec::new(), }; switch_scenes( @@ -59,6 +60,7 @@ fn main() { buffer, input: &input, time: &time, + operation_queue: Vec::new(), }; scripts.run_scripts(&mut context, &wasm_runner, &mut files); input.reset_next_frame(); diff --git a/src/scene.rs b/src/scene.rs index af6b84e..89a0d99 100644 --- a/src/scene.rs +++ b/src/scene.rs @@ -44,8 +44,8 @@ pub fn preload_scene( pub fn is_scene_loaded( scenes: &[Scene], - scripts: &mut ScriptManager, - files: &mut FileManager, + scripts: &ScriptManager, + files: &FileManager, id: usize, ) -> bool { let scene = &scenes[id]; diff --git a/src/script.rs b/src/script.rs index 4429b9c..93f6f99 100644 --- a/src/script.rs +++ b/src/script.rs @@ -3,9 +3,10 @@ use std::path::Path; use rustc_hash::FxHashMap; use crate::{ - buffer::Buffer, + buffer::{Buffer, Cell}, file::{FileManager, GameManifest}, input::InputManager, + scene::{preload_scene, switch_scenes}, time::TimeContext, }; @@ -19,7 +20,7 @@ pub struct ScriptManager<'a> { pub struct WasmRunner { pub engine: wasmi::Engine, - pub linker: wasmi::Linker<&'static mut ScriptContext<'static>>, + pub linker: wasmi::Linker<FullScriptContext<'static>>, } pub struct ScriptContext<'a> { @@ -27,6 +28,34 @@ pub struct ScriptContext<'a> { pub buffer: &'a mut Buffer, pub input: &'a InputManager, pub time: &'a TimeContext, + pub operation_queue: Vec<Operation>, +} + +pub struct FullScriptContext<'a> { + pub script_id: usize, + pub manifest: &'a GameManifest, + pub buffer: &'a Buffer, + pub input: &'a InputManager, + pub time: &'a TimeContext, + pub scripts: &'a ScriptManager<'a>, + pub files: &'a FileManager, + pub wasm_runner: &'a WasmRunner, + pub operation_queue: &'a mut Vec<Operation>, +} + +pub enum Operation { + AddCell(Cell), + PreloadScene(usize), + SwitchScene(usize), + PreloadScript(usize), + UnloadScript(usize), + ActivateScript(usize), + DeactivateScript(usize), + UpdateState { + script_id: usize, + new_state: Box<[u8]>, + }, + Exit, } type StaticScriptFunction = Option<fn(&mut ScriptContext<'_>, &mut Box<[u8]>)>; @@ -34,11 +63,13 @@ type StaticScriptFunction = Option<fn(&mut ScriptContext<'_>, &mut Box<[u8]>)>; #[derive(Debug, Clone)] pub enum ScriptDeclaration { Static { + name: Box<str>, start: StaticScriptFunction, update: StaticScriptFunction, end: StaticScriptFunction, }, Wasm { + name: Box<str>, path: Box<Path>, }, } @@ -65,8 +96,8 @@ impl WasmRunner { let engine = wasmi::Engine::default(); let linker = unsafe { std::mem::transmute::< - wasmi::Linker<&mut ScriptContext<'_>>, - wasmi::Linker<&mut ScriptContext<'_>>, + wasmi::Linker<FullScriptContext<'_>>, + wasmi::Linker<FullScriptContext<'_>>, >(wasm::linker(&engine).unwrap()) }; Self { engine, linker } @@ -76,9 +107,14 @@ impl WasmRunner { wasmi::Module::new(&self.engine, data) } + #[allow(clippy::too_many_arguments)] pub fn run_function( &self, + script_id: usize, module: &wasmi::Module, + scripts: &ScriptManager, + files: &FileManager, + wasm_runner: &WasmRunner, context: &mut ScriptContext<'_>, function_name: &str, ) -> Result<(), wasmi::Error> { @@ -87,7 +123,19 @@ impl WasmRunner { let mut store = { puffin::profile_scope!("create store"); wasmi::Store::new(&self.engine, unsafe { - std::mem::transmute::<&mut ScriptContext<'_>, &mut ScriptContext<'_>>(context) + std::mem::transmute::<FullScriptContext<'_>, FullScriptContext<'_>>( + FullScriptContext { + script_id, + manifest: context.manifest, + buffer: context.buffer, + input: context.input, + time: context.time, + scripts, + files, + wasm_runner, + operation_queue: &mut context.operation_queue, + }, + ) }) }; let instance = { @@ -116,6 +164,25 @@ impl<'a> ScriptManager<'a> { } } + pub fn script_id_by_name(&self, needle: &str) -> Option<usize> { + for (i, script) in self.scripts.iter().enumerate() { + match script { + ScriptDeclaration::Static { name, .. } => { + if &**name == needle { + return Some(i); + } + } + ScriptDeclaration::Wasm { name, .. } => { + if &**name == needle { + return Some(i); + } + } + } + } + + None + } + pub fn preload_script( &self, script_id: usize, @@ -125,15 +192,17 @@ impl<'a> ScriptManager<'a> { let script = &self.scripts[script_id]; match script { ScriptDeclaration::Static { .. } => (), - ScriptDeclaration::Wasm { path } => files.start_loading_file(path, &wasm_runner.engine), + ScriptDeclaration::Wasm { path, .. } => { + files.start_loading_file(path, &wasm_runner.engine) + } } } - pub fn is_script_loaded(&self, script_id: usize, files: &mut FileManager) -> bool { + pub fn is_script_loaded(&self, script_id: usize, files: &FileManager) -> bool { let script = &self.scripts[script_id]; match script { ScriptDeclaration::Static { .. } => true, - ScriptDeclaration::Wasm { path } => files.is_file_loaded(path), + ScriptDeclaration::Wasm { path, .. } => files.is_file_loaded(path), } } @@ -141,8 +210,69 @@ impl<'a> ScriptManager<'a> { let script = &self.scripts[script_id]; match script { ScriptDeclaration::Static { .. } => (), - ScriptDeclaration::Wasm { path } => files.unload_file(path), + ScriptDeclaration::Wasm { path, .. } => files.unload_file(path), + } + } + + pub fn handle_queue( + &mut self, + context: &mut ScriptContext<'_>, + wasm_runner: &WasmRunner, + files: &mut FileManager, + ) { + for operation in &context.operation_queue { + match operation { + Operation::AddCell(cell) => context.buffer.cells.push(*cell), + Operation::PreloadScene(id) => { + preload_scene(&context.manifest.scenes, self, files, wasm_runner, *id) + } + Operation::SwitchScene(id) => switch_scenes( + self, + &mut ScriptContext { + manifest: context.manifest, + buffer: context.buffer, + input: context.input, + time: context.time, + operation_queue: Vec::new(), + }, + files, + wasm_runner, + *id, + ), + Operation::PreloadScript(id) => self.preload_script(*id, files, wasm_runner), + Operation::UnloadScript(id) => self.unload_script(*id, files), + Operation::ActivateScript(id) => self.activate_script( + *id, + &mut ScriptContext { + manifest: context.manifest, + buffer: context.buffer, + input: context.input, + time: context.time, + operation_queue: Vec::new(), + }, + wasm_runner, + files, + ), + Operation::DeactivateScript(id) => self.deactivate_script( + *id, + &mut ScriptContext { + manifest: context.manifest, + buffer: context.buffer, + input: context.input, + time: context.time, + operation_queue: Vec::new(), + }, + wasm_runner, + files, + ), + Operation::UpdateState { + script_id, + new_state, + } => *get_script_state(&mut self.script_states, *script_id) = new_state.clone(), + Operation::Exit => context.exit(), + } } + context.operation_queue.clear(); } pub fn activate_script( @@ -164,15 +294,22 @@ impl<'a> ScriptManager<'a> { ) }); } - ScriptDeclaration::Wasm { path } => { - let module = files - .load_file(path.clone(), &wasm_runner.engine) - .unwrap() - .unwrap_wasm_module(); - wasm_runner.run_function(module, context, "start"); + ScriptDeclaration::Wasm { path, .. } => { + files.load_file(path.clone(), &wasm_runner.engine).unwrap(); + let module = files.get_cached_file(path).unwrap().unwrap_wasm_module(); + wasm_runner.run_function( + script_id, + module, + self, + files, + wasm_runner, + context, + "start", + ); } } self.active_scripts.push(script_id); + self.handle_queue(context, wasm_runner, files); } pub fn deactivate_script( @@ -182,34 +319,41 @@ impl<'a> ScriptManager<'a> { wasm_runner: &WasmRunner, files: &mut FileManager, ) { - self.active_scripts.retain(|element| { - if *element != script_id { - let Some(script) = self.scripts.get(script_id) else { - unreachable!("The active script does not exist"); - }; - match script { - ScriptDeclaration::Static { end, .. } => { - end.map(|s| { - s( - context, - get_script_state(&mut self.script_states, script_id), - ); - }); - } - ScriptDeclaration::Wasm { path } => { - let module = files - .load_file(path.clone(), &wasm_runner.engine) - .unwrap() - .unwrap_wasm_module(); - wasm_runner.run_function(module, context, "end"); - } - }; - - true - } else { - false - } - }); + for script_id in self + .active_scripts + .iter() + .cloned() + .filter(|element| *element == script_id) + { + let Some(script) = self.scripts.get(script_id) else { + unreachable!("The active script does not exist"); + }; + match script { + ScriptDeclaration::Static { end, .. } => { + end.map(|s| { + s( + context, + get_script_state(&mut self.script_states, script_id), + ); + }); + } + ScriptDeclaration::Wasm { path, .. } => { + files.load_file(path.clone(), &wasm_runner.engine).unwrap(); + let module = files.get_cached_file(path).unwrap().unwrap_wasm_module(); + wasm_runner.run_function( + script_id, + module, + self, + files, + wasm_runner, + context, + "end", + ); + } + }; + } + self.active_scripts.retain(|element| *element != script_id); + self.handle_queue(context, wasm_runner, files); } pub fn run_scripts( @@ -232,17 +376,24 @@ impl<'a> ScriptManager<'a> { ) } } - ScriptDeclaration::Wasm { path } => { + ScriptDeclaration::Wasm { path, .. } => { let module = { puffin::profile_scope!("load module"); - files - .load_file(path.clone(), &wasm_runner.engine) - .unwrap() - .unwrap_wasm_module() + files.load_file(path.clone(), &wasm_runner.engine).unwrap(); + files.get_cached_file(path).unwrap().unwrap_wasm_module() }; - wasm_runner.run_function(module, context, "update"); + wasm_runner.run_function( + *script_id, + module, + self, + files, + wasm_runner, + context, + "update", + ); } } } + self.handle_queue(context, wasm_runner, files); } } diff --git a/src/script/wasm.rs b/src/script/wasm.rs index 96190f6..a1b923b 100644 --- a/src/script/wasm.rs +++ b/src/script/wasm.rs @@ -1,26 +1,155 @@ +use core::slice; use std::time::UNIX_EPOCH; -use crossterm::{event::KeyCode, style::ContentStyle}; +use crossterm::{ + event::KeyCode, + style::{Attribute, Attributes, Color, ContentStyle}, +}; use wasmi::{Caller, Engine, Linker}; -use crate::{buffer::Cell, script::ScriptContext}; +use crate::{buffer::Cell, script::FullScriptContext}; -fn add_cell(mut caller: Caller<&mut ScriptContext>, character: u32, location: u32, z: u32) { - caller.data_mut().buffer.cells.push(Cell { - character: char::from_u32(character).unwrap_or_default(), - style: ContentStyle::new(), - x: (location >> 16) as u16, - y: (location & 0xffff) as u16, - z: z as u8, - }); +use super::Operation; + +unsafe fn str_from_memory<'a>( + caller: &'a Caller<FullScriptContext<'_>>, + offset: u32, + length: u32, +) -> &'a str { + let memory = caller.get_export("memory").unwrap().into_memory().unwrap(); + unsafe { + str::from_utf8_unchecked(slice::from_raw_parts( + memory.data_ptr(caller).byte_add(offset as usize), + length as usize, + )) + } } -fn window_size(caller: Caller<&mut ScriptContext>) -> u32 { +#[allow(clippy::too_many_arguments)] +fn add_cell( + mut caller: Caller<FullScriptContext<'_>>, + character: u32, + location: u32, + z: u32, + foreground_color: u32, + background_color: u32, + underline_color: u32, + attributes: u32, +) { + const STANDARD_COLOR: u32 = 1 << 24; + const ANSI_COLOR: u32 = 2 << 24; + const RGB_COLOR: u32 = 3 << 24; + const UNSUPPORTED: u32 = 4 << 24; + + fn get_color(color: u32) -> Option<Color> { + match color { + 0 => None, + STANDARD_COLOR..ANSI_COLOR => Some(match color & 0xff { + 0 => Color::Reset, + 1 => Color::Black, + 2 => Color::DarkGrey, + 3 => Color::Red, + 4 => Color::DarkRed, + 5 => Color::Green, + 6 => Color::DarkGreen, + 7 => Color::Yellow, + 8 => Color::DarkYellow, + 9 => Color::Blue, + 10 => Color::DarkBlue, + 11 => Color::Magenta, + 12 => Color::DarkMagenta, + 13 => Color::Cyan, + 14 => Color::DarkCyan, + 15 => Color::White, + 16 => Color::Grey, + _ => Color::Reset, + }), + ANSI_COLOR..RGB_COLOR => Some(Color::AnsiValue((color & 0xff) as u8)), + RGB_COLOR..UNSUPPORTED => Some(Color::Rgb { + r: ((color >> 16) & 0xff) as u8, + g: ((color >> 8) & 0xff) as u8, + b: (color & 0xff) as u8, + }), + _ => None, + } + } + + let mut attribute_set = Attributes::none(); + if attributes & 1 != 0 { + attribute_set.set(Attribute::Bold); + } + if attributes & 2 != 0 { + attribute_set.set(Attribute::Dim); + } + if attributes & 4 != 0 { + attribute_set.set(Attribute::Italic); + } + if attributes & 8 != 0 { + attribute_set.set(Attribute::Underlined); + } + if attributes & 16 != 0 { + attribute_set.set(Attribute::DoubleUnderlined); + } + if attributes & 32 != 0 { + attribute_set.set(Attribute::Undercurled); + } + if attributes & 64 != 0 { + attribute_set.set(Attribute::Underdotted); + } + if attributes & 128 != 0 { + attribute_set.set(Attribute::Underdashed); + } + if attributes & 256 != 0 { + attribute_set.set(Attribute::SlowBlink); + } + if attributes & 512 != 0 { + attribute_set.set(Attribute::RapidBlink); + } + if attributes & 1024 != 0 { + attribute_set.set(Attribute::Reverse); + } + if attributes & 2048 != 0 { + attribute_set.set(Attribute::Hidden); + } + if attributes & 2048 != 0 { + attribute_set.set(Attribute::CrossedOut); + } + if attributes & 4096 != 0 { + attribute_set.set(Attribute::Fraktur); + } + if attributes & 8192 != 0 { + attribute_set.set(Attribute::Framed); + } + if attributes & 16384 != 0 { + attribute_set.set(Attribute::Encircled); + } + if attributes & 32768 != 0 { + attribute_set.set(Attribute::OverLined); + } + + caller + .data_mut() + .operation_queue + .push(Operation::AddCell(Cell { + character: char::from_u32(character).unwrap_or_default(), + style: ContentStyle { + foreground_color: get_color(foreground_color), + background_color: get_color(background_color), + underline_color: get_color(underline_color), + attributes: attribute_set, + }, + x: (location >> 16) as u16, + y: (location & 0xffff) as u16, + z: z as u8, + })); +} + +fn window_size(caller: Caller<FullScriptContext<'_>>) -> u32 { let window_size = &caller.data().buffer.window_size; ((window_size.rows as u32) << 16) | (window_size.columns as u32) } -fn is_key_pressed_this_frame(caller: Caller<&mut ScriptContext>, key_code: u32) -> u32 { +fn is_key_pressed_this_frame(caller: Caller<FullScriptContext<'_>>, key_code: u32) -> u32 { const BACKSPACE: u32 = 1 << 24; const ENTER: u32 = 2 << 24; const LEFT: u32 = 3 << 24; @@ -67,16 +196,142 @@ fn is_key_pressed_this_frame(caller: Caller<&mut ScriptContext>, key_code: u32) } } -fn exit(mut caller: Caller<&mut ScriptContext>) { - let _ = caller.data_mut().buffer.finish(); - std::process::exit(0); +fn log(caller: Caller<FullScriptContext<'_>>, severity: u32, offset: u32, length: u32) { + let memory = caller.get_export("memory").unwrap().into_memory().unwrap(); + let message = unsafe { + str::from_utf8_unchecked(slice::from_raw_parts( + memory.data_ptr(&caller).byte_add(offset as usize), + length as usize, + )) + }; + let severity = match severity { + 1 => log::Level::Error, + 2 => log::Level::Warn, + 3 => log::Level::Info, + 4 => log::Level::Debug, + 5 => log::Level::Trace, + _ => log::Level::Error, + }; + log::log!(severity, "{message}"); +} + +fn exit(mut caller: Caller<FullScriptContext<'_>>) { + caller.data_mut().operation_queue.push(Operation::Exit) } fn random_u64() -> u64 { getrandom::u64().unwrap_or(4) } -fn system_now(caller: Caller<&mut ScriptContext>) -> u64 { +fn scene_id_by_name(caller: Caller<FullScriptContext<'_>>, offset: u32, length: u32) -> u32 { + let name = unsafe { str_from_memory(&caller, offset, length) }; + crate::scene::scene_id_by_name(&caller.data().manifest.scenes, name) + .map(|u| u as u32) + .unwrap_or(u32::MAX) +} + +fn preload_scene(mut caller: Caller<FullScriptContext<'_>>, id: u32) { + caller + .data_mut() + .operation_queue + .push(Operation::PreloadScene(id as usize)) +} + +fn is_scene_loaded(caller: Caller<FullScriptContext<'_>>, id: u32) -> u32 { + let context = caller.data(); + crate::scene::is_scene_loaded( + &context.manifest.scenes, + context.scripts, + context.files, + id as usize, + ) as u32 +} + +fn switch_scene(mut caller: Caller<FullScriptContext<'_>>, id: u32) { + caller + .data_mut() + .operation_queue + .push(Operation::SwitchScene(id as usize)); +} + +fn script_id_by_name(caller: Caller<FullScriptContext<'_>>, offset: u32, length: u32) -> u32 { + let context = caller.data(); + let name = unsafe { str_from_memory(&caller, offset, length) }; + context + .scripts + .script_id_by_name(name) + .map(|u| u as u32) + .unwrap_or(u32::MAX) +} + +fn preload_script(mut caller: Caller<FullScriptContext<'_>>, id: u32) { + caller + .data_mut() + .operation_queue + .push(Operation::PreloadScript(id as usize)); +} + +fn is_script_loaded(caller: Caller<FullScriptContext<'_>>, id: u32) -> u32 { + let context = caller.data(); + context.scripts.is_script_loaded(id as usize, context.files) as u32 +} + +fn unload_script(mut caller: Caller<FullScriptContext<'_>>, id: u32) { + caller + .data_mut() + .operation_queue + .push(Operation::UnloadScript(id as usize)); +} + +fn activate_script(mut caller: Caller<FullScriptContext<'_>>, id: u32) { + caller + .data_mut() + .operation_queue + .push(Operation::ActivateScript(id as usize)); +} + +fn deactivate_script(mut caller: Caller<FullScriptContext<'_>>, id: u32) { + caller + .data_mut() + .operation_queue + .push(Operation::DeactivateScript(id as usize)); +} + +fn state_size(caller: Caller<FullScriptContext<'_>>) -> u32 { + let context = caller.data(); + context + .scripts + .script_states + .get(&context.script_id) + .map(|state| state.len()) + .unwrap_or_default() as u32 +} + +fn load_state(mut caller: Caller<FullScriptContext<'_>>, offset: u32) { + let memory = caller.get_export("memory").unwrap().into_memory().unwrap(); + let context = caller.data(); + let state = context + .scripts + .script_states + .get(&context.script_id) + .map(|state| &**state) + .unwrap_or(&[]); + memory.write(&mut caller, offset as usize, state).unwrap(); +} + +fn save_state(mut caller: Caller<FullScriptContext<'_>>, offset: u32, length: u32) { + let offset = offset as usize; + let length = length as usize; + let memory = caller.get_export("memory").unwrap().into_memory().unwrap(); + let new_state = Box::from(&memory.data(&caller)[offset..(offset + length)]); + let context = caller.data_mut(); + context.operation_queue.push(Operation::UpdateState { + script_id: context.script_id, + new_state, + }) +} + +fn system_now(caller: Caller<FullScriptContext<'_>>) -> u64 { caller .data() .time @@ -86,17 +341,17 @@ fn system_now(caller: Caller<&mut ScriptContext>) -> u64 { .as_nanos() as u64 } -fn monotonic_now(caller: Caller<&mut ScriptContext>) -> u64 { +fn monotonic_now(caller: Caller<FullScriptContext<'_>>) -> u64 { caller.data().time.monotonic_now().as_nanos() as u64 } -fn delta_time(caller: Caller<&mut ScriptContext>) -> u64 { +fn delta_time(caller: Caller<FullScriptContext<'_>>) -> u64 { caller.data().time.delta_time().as_nanos() as u64 } pub fn linker<'ctx>( engine: &Engine, -) -> Result<wasmi::Linker<&'ctx mut ScriptContext<'ctx>>, wasmi::Error> { +) -> Result<wasmi::Linker<FullScriptContext<'ctx>>, wasmi::Error> { macro_rules! add_fns { ($linker: expr, [$($func: ident),*]) => { $($linker.func_wrap("tortuise", stringify!($func), $func)?;)* @@ -110,8 +365,22 @@ pub fn linker<'ctx>( add_cell, window_size, is_key_pressed_this_frame, + log, exit, random_u64, + scene_id_by_name, + preload_scene, + switch_scene, + is_scene_loaded, + script_id_by_name, + preload_script, + is_script_loaded, + unload_script, + activate_script, + deactivate_script, + state_size, + load_state, + save_state, system_now, monotonic_now, delta_time |
