diff options
| author | Mica White <botahamec@outlook.com> | 2026-07-29 21:19:57 -0400 |
|---|---|---|
| committer | Mica White <botahamec@outlook.com> | 2026-07-29 21:19:57 -0400 |
| commit | c687e2aba30ca231b03288108b632f1bad464028 (patch) | |
| tree | 6134233d24211510eb674417d3374615a74b8268 /src/script | |
| parent | 20ecfc902cc5a654a079e9ba22c41179aca221bb (diff) | |
Diffstat (limited to 'src/script')
| -rw-r--r-- | src/script/lua.rs | 495 | ||||
| -rw-r--r-- | src/script/wasm.rs | 361 |
2 files changed, 716 insertions, 140 deletions
diff --git a/src/script/lua.rs b/src/script/lua.rs new file mode 100644 index 0000000..a5c7f6a --- /dev/null +++ b/src/script/lua.rs @@ -0,0 +1,495 @@ +use std::time::SystemTime; + +use crossterm::{ + event::KeyCode, + style::{Attribute, Attributes, Color, ContentStyle}, +}; +use hex_color::HexColor; +use mlua::{FromLua, IntoLua, Lua, LuaOptions, StdLib, Table, Value}; +use serde::{Deserialize, Serialize}; + +use crate::{ + buffer::Cell, + scene::scene_id_by_name, + script::{FullScriptContext, Operation}, +}; + +pub struct LuaModule { + interpreter: Lua, +} + +impl LuaModule { + pub fn new(data: &[u8]) -> mlua::Result<Self> { + macro_rules! add_fn { + ($lua: expr, $table: expr, $fn: ident) => { + $table.set(stringify!($fn), $lua.create_function($fn)?)?; + }; + } + + macro_rules! add_fns { + ($lua: expr, [$($fn: ident),*]) => { + let tortuise_module = $lua.create_table()?; + $(add_fn!($lua, tortuise_module, $fn);)* + let globals = $lua.globals(); + globals.set("tortuise", tortuise_module)?; + $lua.set_globals(globals)?; + }; + } + + let interpreter = Lua::new_with( + StdLib::TABLE | StdLib::STRING | StdLib::MATH, + LuaOptions::new().catch_rust_panics(true), + )?; + add_fns!( + interpreter, + [ + add_cell, + window_size, + is_key_pressed_this_frame, + log, + warn, + error, + exit, + is_scene_loaded, + preload_scene, + switch_scene, + is_script_loaded, + preload_script, + unload_script, + activate_script, + deactivate_script, + state, + set_state, + entity, + set_entity, + storage, + set_storage, + clock, + time, + delta_time + ] + ); + interpreter.load(data).exec()?; + Ok(Self { interpreter }) + } + + pub fn run_function(&self, func: &str, ctx: FullScriptContext) -> mlua::Result<()> { + unsafe { + let ctx = std::ptr::from_mut(Box::leak(Box::new(ctx))); + self.interpreter.set_app_data(ctx.expose_provenance()); + self.interpreter + .load(format!("if {func} then {func}() end")) + .exec()?; + drop(Box::from_raw(ctx)); + Ok(()) + } + } +} + +#[allow(clippy::mut_from_ref)] +unsafe fn context(lua: &Lua) -> &mut FullScriptContext<'_> { + unsafe { + std::ptr::with_exposed_provenance_mut::<FullScriptContext>(*lua.app_data_ref().unwrap()) + .as_mut_unchecked() + } +} + +fn add_cell(lua: &Lua, cell: Table) -> mlua::Result<()> { + fn parse_color(mut color: String) -> Option<Color> { + color.make_ascii_uppercase(); + color.retain(|c| c != '-' && c != ' ' && c != '_'); + + match &*color { + "RESET" => return Some(Color::Reset), + "BLACK" => return Some(Color::Black), + "DARKGREY" => return Some(Color::DarkGrey), + "DARKGRAY" => return Some(Color::DarkGrey), + "RED" => return Some(Color::Red), + "DARKRED" => return Some(Color::DarkRed), + "GREEN" => return Some(Color::Green), + "DARKGREEN" => return Some(Color::DarkGreen), + "YELLOW" => return Some(Color::Yellow), + "DARKYELLOW" => return Some(Color::DarkYellow), + "BLUE" => return Some(Color::Blue), + "DARKBLUE" => return Some(Color::DarkBlue), + "MAGENTA" => return Some(Color::Magenta), + "DARKMAGENTA" => return Some(Color::DarkMagenta), + "CYAN" => return Some(Color::Cyan), + "DARKCYAN" => return Some(Color::DarkCyan), + "WHITE" => return Some(Color::White), + "GREY" => return Some(Color::Grey), + "GRAY" => return Some(Color::Grey), + _ => (), + }; + if color.starts_with('#') { + let color = HexColor::parse(&color).ok()?; + return Some(Color::Rgb { + r: color.r, + g: color.g, + b: color.b, + }); + } + + None + } + + fn parse_attributes(value: Vec<String>) -> Attributes { + let mut attributes = Attributes::none(); + for mut value in value { + value.make_ascii_uppercase(); + value.retain(|c| c != '-' && c != ' ' && c != '_'); + match &*value { + "BOLD" => attributes.set(Attribute::Bold), + "DIM" => attributes.set(Attribute::Dim), + "ITALIC" => attributes.set(Attribute::Italic), + "UNDERLINED" => attributes.set(Attribute::Underlined), + "DOUBLEUNDERLINED" => attributes.set(Attribute::DoubleUnderlined), + "UNDERCURLED" => attributes.set(Attribute::Undercurled), + "UNDERDOTTED" => attributes.set(Attribute::Underdotted), + "SLOWBLINK" => attributes.set(Attribute::SlowBlink), + "RAPIDBLINK" => attributes.set(Attribute::RapidBlink), + "REVERSE" => attributes.set(Attribute::Reverse), + "HIDDEN" => attributes.set(Attribute::Hidden), + "CROSSEDOUT" => attributes.set(Attribute::CrossedOut), + "FRAKTUR" => attributes.set(Attribute::Fraktur), + "FRAMED" => attributes.set(Attribute::Framed), + "ENCIRCLED" => attributes.set(Attribute::Encircled), + "OVERLINED" => attributes.set(Attribute::OverLined), + _ => (), + } + } + + attributes + } + + puffin::profile_function!(); + unsafe { context(lua) } + .operation_queue + .push(Operation::AddCell(Cell { + character: cell.get("character")?, + style: ContentStyle { + foreground_color: parse_color(cell.get("foreground_color").unwrap_or_default()), + background_color: parse_color(cell.get("background_color").unwrap_or_default()), + underline_color: parse_color(cell.get("underline_color").unwrap_or_default()), + attributes: parse_attributes(cell.get("attributes").unwrap_or_default()), + }, + x: cell.get("x")?, + y: cell.get("y")?, + z: cell.get("z")?, + })); + Ok(()) +} + +fn window_size(lua: &Lua, _: ()) -> mlua::Result<Table> { + puffin::profile_function!(); + let window_size = &unsafe { context(lua) }.buffer.window_size; + let value = lua.create_table()?; + value.set("rows", window_size.rows)?; + value.set("columns", window_size.columns)?; + value.set("width", window_size.width)?; + value.set("height", window_size.height)?; + Ok(value) +} + +fn is_key_pressed_this_frame(lua: &Lua, mut key: String) -> mlua::Result<bool> { + puffin::profile_function!(); + key.make_ascii_uppercase(); + key.retain(|c| c != '-' && c != '_' && c != ' '); + let key = match &*key { + "BACKSPACE" => KeyCode::Backspace, + "ENTER" => KeyCode::Enter, + "LEFT" => KeyCode::Left, + "RIGHT" => KeyCode::Right, + "UP" => KeyCode::Up, + "DOWN" => KeyCode::Down, + "HOME" => KeyCode::Home, + "END" => KeyCode::End, + "PAGEUP" => KeyCode::PageUp, + "PAGEDOWN" => KeyCode::PageDown, + "TAB" => KeyCode::Tab, + "BACKTAB" => KeyCode::BackTab, + "DELETE" => KeyCode::Delete, + "INSERT" => KeyCode::Insert, + "ESC" => KeyCode::Esc, + "ESCAPE" => KeyCode::Esc, + f if f.starts_with('F') => KeyCode::F(f[1..].parse::<u8>().unwrap()), + c if c.len() == 1 => KeyCode::Char(c.chars().next().unwrap()), + _ => return Err(mlua::Error::runtime(format!("Invalid key code: {key}"))), + }; + + Ok(unsafe { context(lua).input.is_key_pressed_this_frame(key) }) +} + +fn log(_: &Lua, value: Value) -> mlua::Result<()> { + puffin::profile_function!(); + log::info!("{}", value.to_string()?); + Ok(()) +} + +fn warn(_: &Lua, value: Value) -> mlua::Result<()> { + puffin::profile_function!(); + log::warn!("{}", value.to_string()?); + Ok(()) +} + +fn error(_: &Lua, value: Value) -> mlua::Result<()> { + puffin::profile_function!(); + log::error!("{}", value.to_string()?); + Ok(()) +} + +fn exit(lua: &Lua, _: ()) -> mlua::Result<()> { + puffin::profile_function!(); + unsafe { context(lua) } + .operation_queue + .push(Operation::Exit); + Ok(()) +} + +fn is_scene_loaded(lua: &Lua, scene: String) -> mlua::Result<()> { + puffin::profile_function!(); + let context = unsafe { context(lua) }; + let scene = scene_id_by_name(&context.manifest.scenes, &scene) + .ok_or(mlua::Error::runtime("No scene with the given name found"))?; + crate::scene::is_scene_loaded( + &context.manifest.scenes, + context.scripts, + &context.files, + scene, + ); + Ok(()) +} + +fn preload_scene(lua: &Lua, scene: String) -> mlua::Result<()> { + puffin::profile_function!(); + let context = unsafe { context(lua) }; + let scene = scene_id_by_name(&context.manifest.scenes, &scene) + .ok_or(mlua::Error::runtime("No scene with the given name found"))?; + context.operation_queue.push(Operation::PreloadScene(scene)); + Ok(()) +} + +fn switch_scene(lua: &Lua, scene: String) -> mlua::Result<()> { + puffin::profile_function!(); + let context = unsafe { context(lua) }; + let scene = scene_id_by_name(&context.manifest.scenes, &scene) + .ok_or(mlua::Error::runtime("No scene with the given name found"))?; + context.operation_queue.push(Operation::SwitchScene(scene)); + Ok(()) +} + +fn is_script_loaded(lua: &Lua, script: String) -> mlua::Result<()> { + puffin::profile_function!(); + let context = unsafe { context(lua) }; + let script = context + .scripts + .script_id_by_name(&script) + .ok_or(mlua::Error::runtime("No script with the given name found"))?; + context.scripts.is_script_loaded(script, &context.files); + Ok(()) +} + +fn preload_script(lua: &Lua, script: String) -> mlua::Result<()> { + puffin::profile_function!(); + let context = unsafe { context(lua) }; + let script = context + .scripts + .script_id_by_name(&script) + .ok_or(mlua::Error::runtime("No script with the given name found"))?; + context + .operation_queue + .push(Operation::PreloadScript(script)); + Ok(()) +} + +fn unload_script(lua: &Lua, script: String) -> mlua::Result<()> { + puffin::profile_function!(); + let context = unsafe { context(lua) }; + let script = context + .scripts + .script_id_by_name(&script) + .ok_or(mlua::Error::runtime("No script with the given name found"))?; + context + .operation_queue + .push(Operation::UnloadScript(script)); + Ok(()) +} + +fn activate_script(lua: &Lua, script: String) -> mlua::Result<()> { + puffin::profile_function!(); + let context = unsafe { context(lua) }; + let script = context + .scripts + .script_id_by_name(&script) + .ok_or(mlua::Error::runtime("No script with the given name found"))?; + context + .operation_queue + .push(Operation::ActivateScript(script)); + Ok(()) +} + +fn deactivate_script(lua: &Lua, script: String) -> mlua::Result<()> { + puffin::profile_function!(); + let context = unsafe { context(lua) }; + let script = context + .scripts + .script_id_by_name(&script) + .ok_or(mlua::Error::runtime("No script with the given name found"))?; + context + .operation_queue + .push(Operation::DeactivateScript(script)); + Ok(()) +} + +fn state(lua: &Lua, default: MyLuaValue) -> mlua::Result<MyLuaValue> { + puffin::profile_function!(); + let context = unsafe { context(lua) }; + Ok(context + .scripts + .script_states + .get(&context.script_id) + .map(|state| postcard::from_bytes(state).unwrap()) + .unwrap_or(default)) +} + +fn set_state(lua: &Lua, new_state: MyLuaValue) -> mlua::Result<()> { + puffin::profile_function!(); + let context = unsafe { context(lua) }; + context.operation_queue.push(Operation::UpdateState { + script_id: context.script_id, + new_state: postcard::to_stdvec(&new_state).unwrap().into_boxed_slice(), + }); + Ok(()) +} + +fn entity(lua: &Lua, (name, default): (String, MyLuaValue)) -> mlua::Result<MyLuaValue> { + puffin::profile_function!(); + let context = unsafe { context(lua) }; + Ok(context + .scripts + .entities + .get(&*name) + .map(|state| postcard::from_bytes(state).unwrap()) + .unwrap_or(default)) +} + +fn set_entity(lua: &Lua, (name, new_state): (String, MyLuaValue)) -> mlua::Result<()> { + puffin::profile_function!(); + let context = unsafe { context(lua) }; + context.operation_queue.push(Operation::UpdateEntity { + name: name.into_boxed_str(), + new_state: postcard::to_stdvec(&new_state).unwrap().into_boxed_slice(), + }); + Ok(()) +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +enum MyLuaValue { + Nil, + Boolean(bool), + Number(f64), + String(String), + Table(Vec<(MyLuaValue, MyLuaValue)>), +} + +impl FromLua for MyLuaValue { + fn from_lua(value: Value, _: &Lua) -> mlua::Result<Self> { + Ok(match value { + Value::Nil => MyLuaValue::Nil, + Value::Boolean(b) => MyLuaValue::Boolean(b), + Value::Number(n) => MyLuaValue::Number(n), + Value::Integer(n) => MyLuaValue::Number(n as f64), + Value::String(s) => MyLuaValue::String(s.to_string_lossy()), + Value::Table(t) => { + let mut pairs = Vec::new(); + t.for_each(|k, v| { + pairs.push((k, v)); + Ok(()) + })?; + MyLuaValue::Table(pairs) + } + _ => return Err(mlua::Error::runtime("Invalid storage type")), + }) + } +} + +impl IntoLua for MyLuaValue { + fn into_lua(self, lua: &Lua) -> mlua::Result<Value> { + Ok(match self { + MyLuaValue::Nil => Value::Nil, + MyLuaValue::Boolean(b) => Value::Boolean(b), + MyLuaValue::Number(n) => Value::Number(n), + MyLuaValue::String(s) => Value::String(lua.create_string(s)?), + MyLuaValue::Table(items) => { + let table = lua.create_table()?; + for (k, v) in items { + table.set(k.into_lua(lua)?, v.into_lua(lua)?)?; + } + Value::Table(table) + } + }) + } +} + +fn storage(lua: &Lua, default: MyLuaValue) -> mlua::Result<MyLuaValue> { + puffin::profile_function!(); + let context = unsafe { context(lua) }; + Ok(context + .storage + .get() + .map(|state| postcard::from_bytes(state).unwrap()) + .unwrap_or(default)) +} + +fn set_storage(lua: &Lua, new_state: MyLuaValue) -> mlua::Result<()> { + puffin::profile_function!(); + let context = unsafe { context(lua) }; + context.operation_queue.push(Operation::UpdateStorage { + new_save: postcard::to_stdvec(&new_state).unwrap().into_boxed_slice(), + }); + Ok(()) +} + +fn clock(lua: &Lua, _: ()) -> mlua::Result<f64> { + puffin::profile_function!(); + let context = unsafe { context(lua) }; + Ok(context.time.monotonic_now().as_secs_f64()) +} + +fn time(lua: &Lua, input: Option<Table>) -> mlua::Result<f64> { + puffin::profile_function!(); + let context = unsafe { context(lua) }; + if let Some(input) = input { + let date = chrono::NaiveDate::from_ymd_opt( + input.get("year")?, + input.get("month")?, + input.get("day")?, + ) + .unwrap(); + let time = chrono::NaiveTime::from_hms_opt( + input.get("hour").unwrap_or_default(), + input.get("min").unwrap_or_default(), + input.get("sec").unwrap_or_default(), + ) + .unwrap(); + let datetime = chrono::NaiveDateTime::new(date, time) + .and_local_timezone(chrono::Local) + .unwrap(); + Ok(datetime + .signed_duration_since(chrono::DateTime::UNIX_EPOCH) + .as_seconds_f64()) + } else { + Ok(context + .time + .system_now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs_f64()) + } +} + +fn delta_time(lua: &Lua, _: ()) -> mlua::Result<f64> { + puffin::profile_function!(); + let context = unsafe { context(lua) }; + Ok(context.time.delta_time().as_secs_f64()) +} diff --git a/src/script/wasm.rs b/src/script/wasm.rs index 25632fa..9b9cd91 100644 --- a/src/script/wasm.rs +++ b/src/script/wasm.rs @@ -1,41 +1,54 @@ -use core::slice; use std::time::UNIX_EPOCH; use crossterm::{ event::KeyCode, style::{Attribute, Attributes, Color, ContentStyle}, }; -use wasmi::{Caller, Engine, Linker}; +use tinywasm::{FuncContext, HostFunction, Imports, ModuleInstance, Store}; use crate::{buffer::Cell, script::FullScriptContext}; 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(); +pub struct WasmModule { + pub instance: ModuleInstance, +} + +fn str_from_memory(caller: &FuncContext<'_>, offset: u32, length: u32) -> tinywasm::Result<String> { + let memory = caller.memory("memory").unwrap(); + memory.read_string(caller.store(), offset as usize, length as usize) +} + +fn context_mut<'b>(address: u64) -> &'b mut FullScriptContext<'b> { unsafe { - str::from_utf8_unchecked(slice::from_raw_parts( - memory.data_ptr(caller).byte_add(offset as usize), - length as usize, - )) + std::ptr::with_exposed_provenance_mut::<FullScriptContext<'b>>(address as usize) + .as_mut() + .unwrap_unchecked() + } +} + +fn context_ref<'b>(address: u64) -> &'b FullScriptContext<'b> { + unsafe { + std::ptr::with_exposed_provenance::<FullScriptContext<'b>>(address as usize) + .as_ref() + .unwrap_unchecked() } } -#[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, -) { + _caller: FuncContext<'_>, + ( + context, + character, + location, + z, + foreground_color, + background_color, + underline_color, + attributes, + ): (u64, u32, u32, u32, u32, u32, u32, u32), +) -> tinywasm::Result<()> { + puffin::profile_function!(); const STANDARD_COLOR: u32 = 1 << 24; const ANSI_COLOR: u32 = 2 << 24; const RGB_COLOR: u32 = 3 << 24; @@ -127,8 +140,7 @@ fn add_cell( attribute_set.set(Attribute::OverLined); } - caller - .data_mut() + context_mut(context) .operation_queue .push(Operation::AddCell(Cell { character: char::from_u32(character).unwrap_or_default(), @@ -142,14 +154,20 @@ fn add_cell( y: (location & 0xffff) as u16, z: z as u8, })); + Ok(()) } -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 window_size(_caller: FuncContext<'_>, context: u64) -> tinywasm::Result<u32> { + puffin::profile_function!(); + let window_size = &context_ref(context).buffer.window_size; + Ok(((window_size.rows as u32) << 16) | (window_size.columns as u32)) } -fn is_key_pressed_this_frame(caller: Caller<FullScriptContext<'_>>, key_code: u32) -> u32 { +fn is_key_pressed_this_frame( + _caller: FuncContext<'_>, + (context, key_code): (u64, u32), +) -> tinywasm::Result<u32> { + puffin::profile_function!(); const BACKSPACE: u32 = 1 << 24; const ENTER: u32 = 2 << 24; const LEFT: u32 = 3 << 24; @@ -189,21 +207,24 @@ fn is_key_pressed_this_frame(caller: Caller<FullScriptContext<'_>>, key_code: u3 _ => KeyCode::Null, }; - if caller.data().input.is_key_pressed_this_frame(key_code) { - 1 - } else { - 0 - } + Ok( + if context_ref(context) + .input + .is_key_pressed_this_frame(key_code) + { + 1 + } else { + 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, - )) - }; +fn log( + caller: FuncContext<'_>, + (severity, offset, length): (u32, u32, u32), +) -> tinywasm::Result<()> { + puffin::profile_function!(); + let message = str_from_memory(&caller, offset, length)?; let severity = match severity { 1 => log::Level::Error, 2 => log::Level::Warn, @@ -213,103 +234,129 @@ fn log(caller: Caller<FullScriptContext<'_>>, severity: u32, offset: u32, length _ => log::Level::Error, }; log::log!(severity, "{message}"); + Ok(()) } -fn exit(mut caller: Caller<FullScriptContext<'_>>) { - caller.data_mut().operation_queue.push(Operation::Exit) +fn exit(_caller: FuncContext<'_>, context: u64) -> tinywasm::Result<()> { + puffin::profile_function!(); + context_mut(context).operation_queue.push(Operation::Exit); + Ok(()) } -fn random_u64() -> u64 { - getrandom::u64().unwrap_or(4) +fn random_u64(_caller: FuncContext<'_>, (): ()) -> tinywasm::Result<u64> { + puffin::profile_function!(); + Ok(getrandom::u64().unwrap_or(4)) } -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 scene_id_by_name( + caller: FuncContext<'_>, + (context, offset, length): (u64, u32, u32), +) -> tinywasm::Result<u32> { + puffin::profile_function!(); + let name = str_from_memory(&caller, offset, length)?; + Ok( + crate::scene::scene_id_by_name(&context_ref(context).manifest.scenes, &name) + .map(|u| u as u32) + .unwrap_or(u32::MAX), + ) } -fn preload_scene(mut caller: Caller<FullScriptContext<'_>>, id: u32) { - caller - .data_mut() +fn preload_scene(_caller: FuncContext<'_>, (context, id): (u64, u32)) -> tinywasm::Result<()> { + puffin::profile_function!(); + context_mut(context) .operation_queue - .push(Operation::PreloadScene(id as usize)) + .push(Operation::PreloadScene(id as usize)); + Ok(()) } -fn is_scene_loaded(caller: Caller<FullScriptContext<'_>>, id: u32) -> u32 { - let context = caller.data(); - crate::scene::is_scene_loaded( +fn is_scene_loaded(_caller: FuncContext<'_>, (context, id): (u64, u32)) -> tinywasm::Result<u32> { + puffin::profile_function!(); + let context = context_ref(context); + Ok(crate::scene::is_scene_loaded( &context.manifest.scenes, context.scripts, - context.files, + &context.files, id as usize, - ) as u32 + ) as u32) } -fn switch_scene(mut caller: Caller<FullScriptContext<'_>>, id: u32) { - caller - .data_mut() +fn switch_scene(_caller: FuncContext<'_>, (context, id): (u64, u32)) -> tinywasm::Result<()> { + puffin::profile_function!(); + context_mut(context) .operation_queue .push(Operation::SwitchScene(id as usize)); + Ok(()) } -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 +fn script_id_by_name( + caller: FuncContext<'_>, + (context, offset, length): (u64, u32, u32), +) -> tinywasm::Result<u32> { + puffin::profile_function!(); + let name = str_from_memory(&caller, offset, length)?; + let context = context_ref(context); + Ok(context .scripts - .script_id_by_name(name) + .script_id_by_name(&name) .map(|u| u as u32) - .unwrap_or(u32::MAX) + .unwrap_or(u32::MAX)) } -fn preload_script(mut caller: Caller<FullScriptContext<'_>>, id: u32) { - caller - .data_mut() +fn preload_script(_caller: FuncContext<'_>, (context, id): (u64, u32)) -> tinywasm::Result<()> { + puffin::profile_function!(); + context_mut(context) .operation_queue .push(Operation::PreloadScript(id as usize)); + Ok(()) } -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 is_script_loaded(_caller: FuncContext<'_>, (context, id): (u64, u32)) -> tinywasm::Result<u32> { + puffin::profile_function!(); + let context = context_ref(context); + Ok(context + .scripts + .is_script_loaded(id as usize, &context.files) as u32) } -fn unload_script(mut caller: Caller<FullScriptContext<'_>>, id: u32) { - caller - .data_mut() +fn unload_script(_caller: FuncContext<'_>, (context, id): (u64, u32)) -> tinywasm::Result<()> { + puffin::profile_function!(); + context_mut(context) .operation_queue .push(Operation::UnloadScript(id as usize)); + Ok(()) } -fn activate_script(mut caller: Caller<FullScriptContext<'_>>, id: u32) { - caller - .data_mut() +fn activate_script(_caller: FuncContext<'_>, (context, id): (u64, u32)) -> tinywasm::Result<()> { + puffin::profile_function!(); + context_mut(context) .operation_queue .push(Operation::ActivateScript(id as usize)); + Ok(()) } -fn deactivate_script(mut caller: Caller<FullScriptContext<'_>>, id: u32) { - caller - .data_mut() +fn deactivate_script(_caller: FuncContext<'_>, (context, id): (u64, u32)) -> tinywasm::Result<()> { + puffin::profile_function!(); + context_mut(context) .operation_queue .push(Operation::DeactivateScript(id as usize)); + Ok(()) } -fn state_size(caller: Caller<FullScriptContext<'_>>) -> u32 { - let context = caller.data(); - context +fn state_size(_caller: FuncContext<'_>, context: u64) -> tinywasm::Result<u32> { + puffin::profile_function!(); + let context = context_ref(context); + Ok(context .scripts .script_states .get(&context.script_id) .map(|state| state.len()) - .unwrap_or_default() as u32 + .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(); +fn load_state(mut caller: FuncContext<'_>, (context, offset): (u64, u32)) -> tinywasm::Result<()> { + puffin::profile_function!(); + let memory = caller.memory("memory").unwrap(); + let context = context_ref(context); let state = context .scripts .script_states @@ -317,119 +364,153 @@ fn load_state(mut caller: Caller<FullScriptContext<'_>>, offset: u32) { .map(|state| &**state) .unwrap_or(&[]); memory.write(&mut caller, offset as usize, state).unwrap(); + Ok(()) } -fn save_state(mut caller: Caller<FullScriptContext<'_>>, offset: u32, length: u32) { +fn save_state( + mut caller: FuncContext<'_>, + (context, offset, length): (u64, u32, u32), +) -> tinywasm::Result<()> { + puffin::profile_function!(); 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(); + let memory = caller.memory("memory").unwrap(); + let new_state = memory + .read_vec(caller.store_mut(), offset, length) + .unwrap() + .into_boxed_slice(); + let context = context_mut(context); context.operation_queue.push(Operation::UpdateState { script_id: context.script_id, new_state, }); + Ok(()) } -fn entity_size(caller: Caller<FullScriptContext<'_>>, name: u32, name_len: u32) -> u32 { - let context = caller.data(); - let name = unsafe { str_from_memory(&caller, name, name_len) }; - context +fn entity_size( + caller: FuncContext<'_>, + (context, name, name_len): (u64, u32, u32), +) -> tinywasm::Result<u32> { + puffin::profile_function!(); + let name = str_from_memory(&caller, name, name_len)?; + let context = context_ref(context); + Ok(context .scripts .entities - .get(name) + .get(&*name) .map(|entity| entity.len()) - .unwrap_or_default() as u32 + .unwrap_or_default() as u32) } -fn load_entity(mut caller: Caller<FullScriptContext<'_>>, name: u32, name_len: u32, offset: u32) { - let memory = caller.get_export("memory").unwrap().into_memory().unwrap(); - let name = unsafe { str_from_memory(&caller, name, name_len) }; - let context = caller.data(); +fn load_entity( + mut caller: FuncContext<'_>, + (context, name, name_len, offset): (u64, u32, u32, u32), +) -> tinywasm::Result<()> { + puffin::profile_function!(); + let name = str_from_memory(&caller, name, name_len)?; + let memory = caller.memory("memory").unwrap(); + let context = context_ref(context); let entity = context .scripts .entities - .get(name) + .get(&*name) .map(|entity| &**entity) .unwrap_or(&[]); memory.write(&mut caller, offset as usize, entity).unwrap(); + Ok(()) } fn save_entity( - mut caller: Caller<FullScriptContext<'_>>, - name: u32, - name_len: u32, - offset: u32, - length: u32, -) { + mut caller: FuncContext<'_>, + (context, name, name_len, offset, length): (u64, u32, u32, u32, u32), +) -> tinywasm::Result<()> { + puffin::profile_function!(); 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 name = Box::from(unsafe { str_from_memory(&caller, name, name_len) }); - let context = caller.data_mut(); + let memory = caller.memory("memory").unwrap(); + let new_state = memory + .read_vec(caller.store_mut(), offset, length) + .unwrap() + .into_boxed_slice(); + let name = str_from_memory(&caller, name, name_len)?.into_boxed_str(); + let context = context_mut(context); context .operation_queue .push(Operation::UpdateEntity { name, new_state }); + Ok(()) } -fn storage_size(caller: Caller<FullScriptContext<'_>>) -> u32 { - caller - .data() +fn storage_size(_caller: FuncContext<'_>, context: u64) -> tinywasm::Result<u32> { + puffin::profile_function!(); + Ok(context_ref(context) .storage .get() .map(|data| data.len()) - .unwrap_or_default() as u32 + .unwrap_or_default() as u32) } -fn load_storage(mut caller: Caller<FullScriptContext<'_>>, offset: u32) { - let memory = caller.get_export("memory").unwrap().into_memory().unwrap(); - let context = caller.data(); +fn load_storage( + mut caller: FuncContext<'_>, + (context, offset): (u64, u32), +) -> tinywasm::Result<()> { + puffin::profile_function!(); + let memory = caller.memory("memory").unwrap(); + let context = context_ref(context); let state = context.storage.get().unwrap_or(&[]); memory.write(&mut caller, offset as usize, state).unwrap(); + Ok(()) } -fn save_storage(mut caller: Caller<FullScriptContext<'_>>, offset: u32, length: u32) { +fn save_storage( + mut caller: FuncContext<'_>, + (context, offset, length): (u64, u32, u32), +) -> tinywasm::Result<()> { + puffin::profile_function!(); let offset = offset as usize; let length = length as usize; - let memory = caller.get_export("memory").unwrap().into_memory().unwrap(); - let new_save = Box::from(&memory.data(&caller)[offset..(offset + length)]); - let context = caller.data_mut(); + let memory = caller.memory("memory").unwrap(); + let new_save = memory + .read_vec(caller.store_mut(), offset, length) + .unwrap() + .into_boxed_slice(); + let context = context_mut(context); context .operation_queue .push(Operation::UpdateStorage { new_save }); + Ok(()) } -fn system_now(caller: Caller<FullScriptContext<'_>>) -> u64 { - caller - .data() +fn system_now(_caller: FuncContext<'_>, context: u64) -> tinywasm::Result<u64> { + puffin::profile_function!(); + Ok(context_ref(context) .time .system_now() .duration_since(UNIX_EPOCH) .unwrap() - .as_nanos() as u64 + .as_nanos() as u64) } -fn monotonic_now(caller: Caller<FullScriptContext<'_>>) -> u64 { - caller.data().time.monotonic_now().as_nanos() as u64 +fn monotonic_now(_caller: FuncContext<'_>, context: u64) -> tinywasm::Result<u64> { + puffin::profile_function!(); + Ok(context_ref(context).time.monotonic_now().as_nanos() as u64) } -fn delta_time(caller: Caller<FullScriptContext<'_>>) -> u64 { - caller.data().time.delta_time().as_nanos() as u64 +fn delta_time(_caller: FuncContext<'_>, context: u64) -> tinywasm::Result<u64> { + puffin::profile_function!(); + Ok(context_ref(context).time.delta_time().as_nanos() as u64) } -pub fn linker<'ctx>( - engine: &Engine, -) -> Result<wasmi::Linker<FullScriptContext<'ctx>>, wasmi::Error> { +pub fn linker(store: &mut Store) -> Result<Imports, tinywasm::Error> { macro_rules! add_fns { - ($linker: expr, [$($func: ident),*]) => { - $($linker.func_wrap("tortuise", stringify!($func), $func)?;)* + ($linker: expr, $store: expr, [$($func: ident),*]) => { + $($linker.define("tortuise", stringify!($func), HostFunction::from($store, $func));)* }; } - let mut linker = Linker::new(engine); + let mut linker = Imports::new(); add_fns!( linker, + store, [ add_cell, window_size, |
