summaryrefslogtreecommitdiff
path: root/src/script
diff options
context:
space:
mode:
Diffstat (limited to 'src/script')
-rw-r--r--src/script/wasm.rs307
1 files changed, 288 insertions, 19 deletions
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<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,
+ ))
+ }
}
-fn window_size(caller: Caller<&mut ScriptContext>) -> u32 {
+#[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<&mut ScriptContext>, key_code: u32) -> 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;
@@ -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<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 system_now(caller: Caller<&mut ScriptContext>) -> u64 {
+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
@@ -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<FullScriptContext<'_>>) -> u64 {
caller.data().time.monotonic_now().as_nanos() as u64
}
-fn delta_time(caller: Caller<&mut ScriptContext>) -> 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<&'ctx mut ScriptContext<'ctx>>, wasmi::Error> {
+) -> Result<wasmi::Linker<FullScriptContext<'ctx>>, 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