use std::time::UNIX_EPOCH;
use crossterm::{
event::KeyCode,
style::{Attribute, Attributes, Color, ContentStyle},
};
use tinywasm::{FuncContext, HostFunction, Imports, ModuleInstance, Store};
use crate::{buffer::Cell, script::FullScriptContext};
use super::Operation;
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 {
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()
}
}
fn add_cell(
_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;
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 & 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);
}
context_mut(context)
.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,
}));
Ok(())
}
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: 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;
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,
};
Ok(
if context_ref(context)
.input
.is_key_pressed_this_frame(key_code)
{
1
} else {
0
},
)
}
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,
3 => log::Level::Info,
4 => log::Level::Debug,
5 => log::Level::Trace,
_ => log::Level::Error,
};
log::log!(severity, "{message}");
Ok(())
}
fn exit(_caller: FuncContext<'_>, context: u64) -> tinywasm::Result<()> {
puffin::profile_function!();
context_mut(context).operation_queue.push(Operation::Exit);
Ok(())
}
fn random_u64(_caller: FuncContext<'_>, (): ()) -> tinywasm::Result<u64> {
puffin::profile_function!();
Ok(getrandom::u64().unwrap_or(4))
}
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(_caller: FuncContext<'_>, (context, id): (u64, u32)) -> tinywasm::Result<()> {
puffin::profile_function!();
context_mut(context)
.operation_queue
.push(Operation::PreloadScene(id as usize));
Ok(())
}
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,
id as usize,
) as u32)
}
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: 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)
.map(|u| u as u32)
.unwrap_or(u32::MAX))
}
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: 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(_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(_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(_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: 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)
}
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
.get(&context.script_id)
.map(|state| &**state)
.unwrap_or(&[]);
memory.write(&mut caller, offset as usize, state).unwrap();
Ok(())
}
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.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: 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)
.map(|entity| entity.len())
.unwrap_or_default() as u32)
}
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)
.map(|entity| &**entity)
.unwrap_or(&[]);
memory.write(&mut caller, offset as usize, entity).unwrap();
Ok(())
}
fn save_entity(
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.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: FuncContext<'_>, context: u64) -> tinywasm::Result<u32> {
puffin::profile_function!();
Ok(context_ref(context)
.storage
.get()
.map(|data| data.len())
.unwrap_or_default() as u32)
}
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: 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.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: 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)
}
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: FuncContext<'_>, context: u64) -> tinywasm::Result<u64> {
puffin::profile_function!();
Ok(context_ref(context).time.delta_time().as_nanos() as u64)
}
pub fn linker(store: &mut Store) -> Result<Imports, tinywasm::Error> {
macro_rules! add_fns {
($linker: expr, $store: expr, [$($func: ident),*]) => {
$($linker.define("tortuise", stringify!($func), HostFunction::from($store, $func));)*
};
}
let mut linker = Imports::new();
add_fns!(
linker,
store,
[
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)
}
|