use core::slice; use std::time::UNIX_EPOCH; use crossterm::{ event::KeyCode, style::{Attribute, Attributes, Color, ContentStyle}, }; use wasmi::{Caller, Engine, Linker}; use crate::{buffer::Cell, script::FullScriptContext}; 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, )) } } #[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 & 4096 != 0 { attribute_set.set(Attribute::CrossedOut); } if attributes & 8192 != 0 { attribute_set.set(Attribute::Fraktur); } if attributes & 16384 != 0 { attribute_set.set(Attribute::Framed); } if attributes & 32768 != 0 { attribute_set.set(Attribute::Encircled); } if attributes & 65536 != 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>, key_code: u32) -> u32 { const BACKSPACE: u32 = 1 << 24; const ENTER: u32 = 2 << 24; const LEFT: u32 = 3 << 24; const RIGHT: u32 = 4 << 24; const UP: u32 = 5 << 24; const DOWN: u32 = 6 << 24; const HOME: u32 = 7 << 24; const END: u32 = 8 << 24; const PAGE_UP: u32 = 9 << 24; const PAGE_DOWN: u32 = 10 << 24; const TAB: u32 = 11 << 24; const BACK_TAB: u32 = 12 << 24; const DELETE: u32 = 13 << 24; const INSERT: u32 = 14 << 24; const F: u32 = 15 << 24; const CHAR: u32 = 16 << 24; const ESC: u32 = 17 << 24; let key_code = match key_code { 0 => KeyCode::Null, BACKSPACE => KeyCode::Backspace, ENTER => KeyCode::Enter, LEFT => KeyCode::Left, RIGHT => KeyCode::Right, UP => KeyCode::Up, DOWN => KeyCode::Down, HOME => KeyCode::Home, END => KeyCode::End, PAGE_UP => KeyCode::PageUp, PAGE_DOWN => KeyCode::PageDown, TAB => KeyCode::Tab, BACK_TAB => KeyCode::BackTab, DELETE => KeyCode::Delete, INSERT => KeyCode::Insert, F..CHAR => KeyCode::F((key_code & 0xff) as u8), CHAR..ESC => KeyCode::Char(char::from_u32(key_code & 0xffffff).unwrap_or_default()), ESC => KeyCode::Esc, _ => KeyCode::Null, }; if caller.data().input.is_key_pressed_this_frame(key_code) { 1 } else { 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 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 entity_size(caller: Caller>, name: u32, name_len: u32) -> u32 { let context = caller.data(); let name = unsafe { str_from_memory(&caller, name, name_len) }; context .scripts .entities .get(name) .map(|entity| entity.len()) .unwrap_or_default() as u32 } fn load_entity(mut caller: Caller>, 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(); let entity = context .scripts .entities .get(name) .map(|entity| &**entity) .unwrap_or(&[]); memory.write(&mut caller, offset as usize, entity).unwrap(); } fn save_entity( mut caller: Caller>, name: u32, name_len: u32, 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 name = Box::from(unsafe { str_from_memory(&caller, name, name_len) }); let context = caller.data_mut(); context .operation_queue .push(Operation::UpdateEntity { name, new_state }); } fn storage_size(caller: Caller>) -> u32 { caller .data() .storage .get() .map(|data| data.len()) .unwrap_or_default() as u32 } fn load_storage(mut caller: Caller>, offset: u32) { let memory = caller.get_export("memory").unwrap().into_memory().unwrap(); let context = caller.data(); let state = context.storage.get().unwrap_or(&[]); memory.write(&mut caller, offset as usize, state).unwrap(); } fn save_storage(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_save = Box::from(&memory.data(&caller)[offset..(offset + length)]); let context = caller.data_mut(); context .operation_queue .push(Operation::UpdateStorage { new_save }); } fn system_now(caller: Caller>) -> u64 { caller .data() .time .system_now() .duration_since(UNIX_EPOCH) .unwrap() .as_nanos() as u64 } fn monotonic_now(caller: Caller>) -> u64 { caller.data().time.monotonic_now().as_nanos() as 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> { macro_rules! add_fns { ($linker: expr, [$($func: ident),*]) => { $($linker.func_wrap("tortuise", stringify!($func), $func)?;)* }; } let mut linker = Linker::new(engine); add_fns!( linker, [ 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, entity_size, load_entity, save_entity, storage_size, load_storage, save_storage, system_now, monotonic_now, delta_time ] ); Ok(linker) }