From a9977e7fabeb06a6f21824cc4a41b36ea39f6799 Mon Sep 17 00:00:00 2001 From: Mica White Date: Tue, 7 Jul 2026 22:45:22 -0400 Subject: Full host environment --- src/script/wasm.rs | 307 +++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 288 insertions(+), 19 deletions(-) (limited to 'src/script') 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>, + 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>, + 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 { + 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>) -> 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>, 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>, 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>) { + 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>, 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>, id: u32) { + caller + .data_mut() + .operation_queue + .push(Operation::PreloadScene(id as usize)) +} + +fn is_scene_loaded(caller: Caller>, 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>, id: u32) { + caller + .data_mut() + .operation_queue + .push(Operation::SwitchScene(id as usize)); +} + +fn script_id_by_name(caller: Caller>, 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>, id: u32) { + caller + .data_mut() + .operation_queue + .push(Operation::PreloadScript(id as usize)); +} + +fn is_script_loaded(caller: Caller>, 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>, id: u32) { + caller + .data_mut() + .operation_queue + .push(Operation::UnloadScript(id as usize)); +} + +fn activate_script(mut caller: Caller>, id: u32) { + caller + .data_mut() + .operation_queue + .push(Operation::ActivateScript(id as usize)); +} + +fn deactivate_script(mut caller: Caller>, id: u32) { + caller + .data_mut() + .operation_queue + .push(Operation::DeactivateScript(id as usize)); +} + +fn state_size(caller: Caller>) -> 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>, 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>, 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>) -> 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>) -> u64 { caller.data().time.monotonic_now().as_nanos() as u64 } -fn delta_time(caller: Caller<&mut ScriptContext>) -> u64 { +fn delta_time(caller: Caller>) -> u64 { caller.data().time.delta_time().as_nanos() as u64 } pub fn linker<'ctx>( engine: &Engine, -) -> Result>, wasmi::Error> { +) -> Result>, 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 -- cgit v1.2.3