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<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,
))
}
}
#[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<FullScriptContext<'_>>, 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<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 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
.system_now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos() as u64
}
fn monotonic_now(caller: Caller<FullScriptContext<'_>>) -> u64 {
caller.data().time.monotonic_now().as_nanos() as 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<FullScriptContext<'ctx>>, 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,
system_now,
monotonic_now,
delta_time
]
);
Ok(linker)
}
|