summaryrefslogtreecommitdiff
path: root/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'scripts')
-rw-r--r--scripts/Cargo.toml15
-rw-r--r--scripts/src/lib.rs318
-rw-r--r--scripts/src/libs/allocate.rs76
-rw-r--r--scripts/src/libs/buffer.rs182
-rw-r--r--scripts/src/libs/ctype.rs85
-rw-r--r--scripts/src/libs/math.rs398
-rw-r--r--scripts/src/libs/mod.rs19
-rw-r--r--scripts/src/libs/system.rs154
-rw-r--r--scripts/src/vtable.rs10
-rw-r--r--scripts/src/wasm.rs170
10 files changed, 1427 insertions, 0 deletions
diff --git a/scripts/Cargo.toml b/scripts/Cargo.toml
new file mode 100644
index 0000000..45dbd47
--- /dev/null
+++ b/scripts/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "scripts"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+wasmtime = "14"
+rayon = "1"
+thiserror = "1"
+rand = "0.8"
+chrono = "0.4"
+log = "0.4"
+libm = "0.2"
diff --git a/scripts/src/lib.rs b/scripts/src/lib.rs
new file mode 100644
index 0000000..214001c
--- /dev/null
+++ b/scripts/src/lib.rs
@@ -0,0 +1,318 @@
+use std::{collections::HashMap, path::Path};
+
+pub use vtable::VTableScript;
+use wasmtime::{Engine, IntoFunc, Linker, Module, Store};
+
+pub use wasm::InvalidWasmScript;
+use wasm::{WasmScript, WasmScriptState};
+
+mod libs;
+mod vtable;
+mod wasm;
+
+/// A sturcture to contain and use any needed scripts.
+///
+/// # Performance
+///
+/// V-Table scripts are faster than WebAssembly scripts. WebAssembly scripting
+/// is provided to make hot-reloading possible. Either way, scripts should be
+/// reasonably fast.
+///
+/// A single long WebAssembly script is typically faster than lots of small
+/// scripts. Try to keep the number of scripts small. No more than twenty
+/// scripts should be enabled at once.
+#[derive(Default)]
+pub struct ScriptManager {
+ script_map: HashMap<Box<str>, ScriptId>,
+
+ vtable_scripts: Vec<Script<VTableScript>>,
+ wasm_scripts: Vec<Script<WasmScript>>,
+
+ relink_every_frame: bool,
+ wasm_engine: Engine,
+ wasm_imports: Linker<WasmScriptState>,
+}
+
+impl ScriptManager {
+ /// Creates a new, empty script manager
+ pub fn new() -> Self {
+ let wasm_engine = Engine::default();
+ let mut wasm_imports = Linker::new(&wasm_engine);
+ wasm_imports.allow_shadowing(true);
+
+ let mut this = Self {
+ script_map: HashMap::new(),
+ vtable_scripts: Vec::new(),
+ wasm_scripts: Vec::new(),
+ relink_every_frame: false,
+ wasm_imports,
+ wasm_engine,
+ };
+ libs::add_alligator_library(&mut this);
+
+ this
+ }
+
+ /// If set to `true`, then every frame, the scripts will be relinked with
+ /// it's dependencies on each call.
+ ///
+ /// The main reason to do this would be to ensure that the state is reset
+ /// each frame. However, the link is slow, so it's not recommended to use
+ /// this setting for releasing a game. This is only recommended for
+ /// debugging.
+ pub fn set_relink_every_frame(&mut self, setting: bool) {
+ self.relink_every_frame = setting;
+ }
+
+ /// Returns `true` if the scripts are set to be relinked each frame. See
+ /// [`set_relink_every_frame`] for more details.
+ pub fn relink_every_frame(&self) -> bool {
+ self.relink_every_frame
+ }
+
+ /// Adds a script to this manager.
+ ///
+ /// By default, all scripts are disabled. To enable it, call
+ /// [`enable_script`].
+ pub fn add_vtable_script(&mut self, name: Box<str>, script: VTableScript) -> ScriptId {
+ let idx = self.vtable_scripts.len();
+ self.vtable_scripts.push(Script::new(script));
+ let id = ScriptId {
+ kind: ScriptKind::VTable,
+ idx,
+ };
+ self.script_map.insert(name, id);
+ id
+ }
+
+ /// Adds a WebAssembly script to this manager.
+ ///
+ /// By default, all scripts are disabled. To enable it, call
+ /// [`enable_script`].
+ ///
+ /// # Errors
+ ///
+ /// This function returns an error if it could not use the given script.
+ pub fn add_wasm_script(
+ &mut self,
+ name: Box<str>,
+ path: impl AsRef<Path>,
+ trusted: bool,
+ ) -> Result<ScriptId, InvalidWasmScript> {
+ let idx = self.vtable_scripts.len();
+ let script = WasmScript::new(
+ path.as_ref(),
+ &self.wasm_engine,
+ &self.wasm_imports,
+ trusted,
+ )?;
+ self.wasm_scripts.push(Script::new(script));
+ let id = ScriptId {
+ kind: ScriptKind::Wasm,
+ idx,
+ };
+ self.script_map.insert(name, id);
+ Ok(id)
+ }
+
+ /// Add a WebAssembly module which can be called by other WebAssembly
+ /// scripts.
+ ///
+ /// If there are two libraries with the same name, only the newest module
+ /// is used. This feature can be used to allow libraries to be hot
+ /// reloaded.
+ ///
+ /// The `trusted` parameter refers to whether this library can call
+ /// functions which require trust.
+ ///
+ /// # Performance
+ ///
+ /// Running this function relinks all WebAssembly scripts. For best
+ /// performance, all WebAssembly libraries should be added before any
+ /// scripts are loaded.
+ pub fn add_wasm_library(
+ &mut self,
+ name: &str,
+ path: impl AsRef<Path>,
+ trusted: bool,
+ ) -> Result<(), InvalidWasmScript> {
+ let module = Module::from_file(&self.wasm_engine, path)?;
+ let mut store = Store::new(&self.wasm_engine, WasmScriptState::new(trusted));
+ let instance = self.wasm_imports.instantiate(&mut store, &module)?;
+ self.wasm_imports.instance(store, name, instance)?;
+
+ for script in &mut self.wasm_scripts {
+ if let Err(e) = script.inner.relink(&self.wasm_engine, &self.wasm_imports) {
+ log::error!("{e}");
+ }
+ }
+
+ Ok(())
+ }
+
+ /// Reloads a WebAssembly script from disk.
+ pub fn reload_script(&mut self, name: &str) -> Option<Result<(), InvalidWasmScript>> {
+ let id = self.script_map.get(name).unwrap();
+ let result = match id.kind {
+ ScriptKind::Wasm => self
+ .wasm_scripts
+ .get_mut(id.idx)
+ .unwrap()
+ .inner
+ .reload(&self.wasm_engine, &self.wasm_imports),
+ _ => return None,
+ };
+ Some(result)
+ }
+
+ /// Adds a library to the manager
+ pub fn add_library_function<Params, Args>(
+ &mut self,
+ module: &str,
+ name: &str,
+ func: impl IntoFunc<WasmScriptState, Params, Args> + Clone,
+ ) -> Option<()> {
+ self.wasm_imports.func_wrap(module, name, func).ok()?;
+ Some(())
+ }
+
+ /// Checks if the script with the given name is enabled
+ pub fn is_script_enabled(&self, name: &str) -> Option<bool> {
+ let id = self.script_map.get(name)?;
+ let enabled = match id.kind {
+ ScriptKind::VTable => self.vtable_scripts.get(id.idx)?.is_enabled(),
+ ScriptKind::Wasm => self.wasm_scripts.get(id.idx)?.is_enabled(),
+ };
+ Some(enabled)
+ }
+
+ /// Checks if the script with the given name is trusted
+ pub fn is_script_trusted(&self, name: &str) -> Option<bool> {
+ let id = self.script_map.get(name)?;
+ let trusted = match id.kind {
+ ScriptKind::VTable => true,
+ ScriptKind::Wasm => self.wasm_scripts.get(id.idx)?.inner.is_trusted(),
+ };
+ Some(trusted)
+ }
+
+ /// This enables the script, allowing it to run each frame.
+ ///
+ /// Calling this automatically calls the `begin` function for the script.
+ pub fn enable_script(&mut self, name: &str) -> Option<()> {
+ let id = self.script_map.get(name)?;
+ match id.kind {
+ ScriptKind::VTable => {
+ let script = self.vtable_scripts.get_mut(id.idx)?;
+ if script.is_enabled() {
+ // avoid calling `begin` if the script is already enabled
+ return Some(());
+ }
+
+ script.enable();
+ script.inner.begin();
+ }
+ ScriptKind::Wasm => {
+ let script = self.wasm_scripts.get_mut(id.idx)?;
+ if script.is_enabled() {
+ return Some(());
+ }
+
+ script.enable();
+ if let Err(e) = script.inner.begin() {
+ log::error!("{}", e);
+ }
+ }
+ };
+ Some(())
+ }
+
+ /// This disables the script, preventing it from running
+ pub fn disable_script(&mut self, name: &str) -> Option<()> {
+ let id = self.script_map.get(name)?;
+ match id.kind {
+ ScriptKind::VTable => self.vtable_scripts.get_mut(id.idx)?.disable(),
+ ScriptKind::Wasm => self.wasm_scripts.get_mut(id.idx)?.disable(),
+ };
+ Some(())
+ }
+
+ /// This allows the script to run functions which require trust
+ pub fn trust_script(&mut self, name: &str) -> Option<()> {
+ let id = self.script_map.get(name)?;
+ if id.kind == ScriptKind::Wasm {
+ self.wasm_scripts.get_mut(id.idx)?.inner.trust();
+ }
+ Some(())
+ }
+
+ /// This prevents the script from running functions which require trust
+ pub fn untrust_script(&mut self, name: &str) -> Option<()> {
+ let id = self.script_map.get(name)?;
+ if id.kind == ScriptKind::Wasm {
+ self.wasm_scripts.get_mut(id.idx)?.inner.untrust();
+ }
+ Some(())
+ }
+
+ /// Runs the `update` function for all enabled scripts
+ pub fn run_update_scripts(&mut self) {
+ for script in &mut self.vtable_scripts {
+ if script.is_enabled() {
+ script.inner.update();
+ }
+ }
+
+ for script in &mut self.wasm_scripts {
+ if script.is_enabled() {
+ if self.relink_every_frame {
+ if let Err(e) = script.inner.relink(&self.wasm_engine, &self.wasm_imports) {
+ log::error!("{e}");
+ }
+ }
+ if let Err(e) = script.inner.update() {
+ log::error!("{}", e);
+ }
+ }
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+enum ScriptKind {
+ VTable,
+ Wasm,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct ScriptId {
+ kind: ScriptKind,
+ idx: usize,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+struct Script<S> {
+ inner: S,
+ enabled: bool,
+}
+
+impl<S> Script<S> {
+ fn new(inner: S) -> Self {
+ Self {
+ inner,
+ enabled: true,
+ }
+ }
+
+ fn is_enabled(&self) -> bool {
+ self.enabled
+ }
+
+ fn enable(&mut self) {
+ self.enabled = true;
+ }
+
+ fn disable(&mut self) {
+ self.enabled = false;
+ }
+}
diff --git a/scripts/src/libs/allocate.rs b/scripts/src/libs/allocate.rs
new file mode 100644
index 0000000..7f2825d
--- /dev/null
+++ b/scripts/src/libs/allocate.rs
@@ -0,0 +1,76 @@
+use wasmtime::Caller;
+
+use crate::{ScriptManager, WasmScriptState};
+
+use super::{
+ system::{_alloc, _get_memory},
+ LIBRARY_NAME,
+};
+
+fn allocate_zeroed(mut caller: Caller<'_, WasmScriptState>, size: u32, align: u32) -> u32 {
+ let Some((ptr, data)) = _alloc(&mut caller, size, align) else {
+ return 0;
+ };
+
+ data.fill(0);
+ ptr
+}
+
+fn reallocate(
+ mut caller: Caller<'_, WasmScriptState>,
+ ptr: u32,
+ size: u32,
+ new_size: u32,
+ align: u32,
+) -> u32 {
+ if new_size < size {
+ return ptr;
+ }
+
+ let bump_pointer = &mut caller.data_mut().bump_pointer;
+ if *bump_pointer == ptr + size {
+ *bump_pointer = ptr;
+ let Some((new_ptr, _)) = _alloc(&mut caller, new_size, align) else {
+ return 4;
+ };
+ return new_ptr;
+ }
+
+ let (_, original) = _get_memory(&mut caller);
+ let Some(original) = original.get(ptr as usize..(ptr + size) as usize) else {
+ return 4;
+ };
+ let original = original.to_vec();
+
+ let Some((new_ptr, data)) = _alloc(&mut caller, new_size, align) else {
+ return 0;
+ };
+
+ data.get_mut(..size as usize)
+ .unwrap()
+ .clone_from_slice(&original);
+ new_ptr
+}
+
+fn duplicate(mut caller: Caller<'_, WasmScriptState>, ptr: u32, size: u32, align: u32) -> u32 {
+ let (_, original) = _get_memory(&mut caller);
+ let Some(original) = original.get(ptr as usize..(ptr + size) as usize) else {
+ return 4;
+ };
+ let original = original.to_vec();
+
+ let Some((new_ptr, data)) = _alloc(&mut caller, size, align) else {
+ return 0;
+ };
+
+ data.clone_from_slice(&original);
+ new_ptr
+}
+
+pub fn library(manager: &mut ScriptManager) -> Option<()> {
+ manager.add_library_function(LIBRARY_NAME, "calloc", allocate_zeroed)?;
+ manager.add_library_function(LIBRARY_NAME, "realloc", reallocate)?;
+ manager.add_library_function(LIBRARY_NAME, "memdup", duplicate)?;
+
+ Some(())
+}
diff --git a/scripts/src/libs/buffer.rs b/scripts/src/libs/buffer.rs
new file mode 100644
index 0000000..b4cee77
--- /dev/null
+++ b/scripts/src/libs/buffer.rs
@@ -0,0 +1,182 @@
+use wasmtime::Caller;
+
+use crate::{ScriptManager, WasmScriptState};
+
+use super::{system::_get_memory, LIBRARY_NAME};
+
+fn memory_search(mut caller: Caller<'_, WasmScriptState>, ptr: u32, ch: u32, len: u32) -> u32 {
+ let (_, mem_ptr) = _get_memory(&mut caller);
+ let len = len as usize;
+ let ptr = ptr as usize;
+ let ch = ch as u8;
+ if (len + ptr) > mem_ptr.len() {
+ return 4;
+ }
+
+ for i in 0..len {
+ if mem_ptr[ptr + i] == ch {
+ return (ptr + i) as u32;
+ }
+ }
+
+ 0
+}
+
+fn memory_search_last(mut caller: Caller<'_, WasmScriptState>, ptr: u32, ch: u32, len: u32) -> u32 {
+ let (_, mem_ptr) = _get_memory(&mut caller);
+ let len = len as usize;
+ let ptr = ptr as usize;
+ let ch = ch as u8;
+ if (len + ptr) > mem_ptr.len() {
+ return 4;
+ }
+
+ for i in 0..len {
+ let index = ptr + len - i - 1;
+ if mem_ptr[index] == ch {
+ return index as u32;
+ }
+ }
+
+ 0
+}
+
+fn memory_compare(
+ mut caller: Caller<'_, WasmScriptState>,
+ lhs: u32,
+ rhs: u32,
+ len: u32,
+) -> (u32, u32) {
+ let (_, mem_ptr) = _get_memory(&mut caller);
+ let len = len as usize;
+ let lhs = lhs as usize;
+ let rhs = rhs as usize;
+ if (len + lhs) > mem_ptr.len() || (len + rhs) > mem_ptr.len() {
+ return (4, 0);
+ }
+
+ for i in 0..len {
+ let diff = mem_ptr[lhs + i] - mem_ptr[rhs + i];
+ if diff != 0 {
+ return (0, diff as u32);
+ }
+ }
+
+ (0, 0)
+}
+
+fn memory_fill(mut caller: Caller<'_, WasmScriptState>, dest: u32, ch: u32, len: u32) -> u32 {
+ let (_, mem_ptr) = _get_memory(&mut caller);
+ let len = len as usize;
+ let dest = dest as usize;
+ let ch = ch as u8;
+
+ if (len + dest) > mem_ptr.len() {
+ return 4;
+ }
+
+ for i in 0..len {
+ mem_ptr[dest + i] = ch;
+ }
+
+ 0
+}
+
+fn memory_copy(mut caller: Caller<'_, WasmScriptState>, dest: u32, src: u32, len: u32) -> u32 {
+ let (_, mem_ptr) = _get_memory(&mut caller);
+ let len = len as usize;
+ let dest = dest as usize;
+ let src = src as usize;
+ if (len + dest) > mem_ptr.len() || (len + src) > mem_ptr.len() {
+ return 4;
+ }
+
+ // check for overlap
+ if (dest < src && dest + len > src) || (src < dest && src + len > dest) {
+ let src = mem_ptr[src..src + len].to_vec();
+ let dest = &mut mem_ptr[dest..dest + len];
+ dest.clone_from_slice(&src);
+ } else {
+ for i in 0..len {
+ mem_ptr[dest + i] = mem_ptr[src + i];
+ }
+ }
+
+ 0
+}
+
+fn memory_copy_until(
+ mut caller: Caller<'_, WasmScriptState>,
+ dest: u32,
+ src: u32,
+ len: u32,
+ delimiter: u32,
+) -> u32 {
+ let (_, mem_ptr) = _get_memory(&mut caller);
+ let len = len as usize;
+ let dest = dest as usize;
+ let src = src as usize;
+ let delimiter = delimiter as u8;
+ if (len + dest) > mem_ptr.len() || (len + src) > mem_ptr.len() {
+ return 4;
+ }
+
+ // check for overlap
+ if (dest < src && dest + len > src) || (src < dest && src + len > dest) {
+ let cloned_src = mem_ptr[src..src + len].to_vec();
+ let dest = &mut mem_ptr[dest..dest + len];
+ for i in 0..len {
+ let ch = cloned_src[i];
+ dest[i] = ch;
+ if ch == delimiter {
+ return (src + i + 1) as u32;
+ }
+ }
+ } else {
+ for i in 0..len {
+ let ch = mem_ptr[src + i];
+ mem_ptr[dest + i] = ch;
+ if ch == delimiter {
+ return (src + i + 1) as u32;
+ }
+ }
+ }
+
+ 0
+}
+
+fn memory_concatenate(
+ mut caller: Caller<'_, WasmScriptState>,
+ dest: u32,
+ src: u32,
+ dest_len: u32,
+ src_len: u32,
+) -> u32 {
+ let (_, mem_ptr) = _get_memory(&mut caller);
+ let dest = dest as usize;
+ let src = src as usize;
+ let dest_len = dest_len as usize;
+ let src_len = src_len as usize;
+ if (dest_len + src_len + dest) > mem_ptr.len() || (src_len + src) > mem_ptr.len() {
+ return 4;
+ }
+
+ let src = mem_ptr[src..src + src_len].to_vec();
+ let dest_offset = dest + dest_len;
+ let dest = &mut mem_ptr[dest_offset..dest_offset + src_len];
+ dest.clone_from_slice(&src);
+
+ 0
+}
+
+pub fn library(manager: &mut ScriptManager) -> Option<()> {
+ manager.add_library_function(LIBRARY_NAME, "memchr", memory_search)?;
+ manager.add_library_function(LIBRARY_NAME, "memrchr", memory_search_last)?;
+ manager.add_library_function(LIBRARY_NAME, "memcmp", memory_compare)?;
+ manager.add_library_function(LIBRARY_NAME, "memset", memory_fill)?;
+ manager.add_library_function(LIBRARY_NAME, "memcpy", memory_copy)?;
+ manager.add_library_function(LIBRARY_NAME, "memccpy", memory_copy_until)?;
+ manager.add_library_function(LIBRARY_NAME, "memcat", memory_concatenate)?;
+
+ Some(())
+}
diff --git a/scripts/src/libs/ctype.rs b/scripts/src/libs/ctype.rs
new file mode 100644
index 0000000..ca4f326
--- /dev/null
+++ b/scripts/src/libs/ctype.rs
@@ -0,0 +1,85 @@
+use crate::{libs::LIBRARY_NAME, ScriptManager};
+
+fn char_is(func: impl Fn(char) -> bool, ch: u32) -> u32 {
+ let Some(ch) = char::from_u32(ch) else {
+ return 0;
+ };
+
+ func(ch) as u32
+}
+
+macro_rules! ctype {
+ (fn $fn_name: ident => $fn: expr;) => {
+ fn $fn_name(ch: u32) -> u32 {
+ char_is($fn, ch)
+ }
+ };
+ (fn $fn_name: ident => $fn: expr; $(fn $fn_name2: ident => $fn2: expr;)*) => {
+ fn $fn_name(ch: u32) -> u32 {
+ char_is($fn, ch)
+ }
+ ctype!($(fn $fn_name2 => $fn2;)*);
+ };
+}
+
+ctype!(
+ fn is_alphanumeric => |ch| ch.is_alphanumeric();
+ fn is_alphabetic => |ch| ch.is_alphabetic();
+ fn is_lowercase => |ch| ch.is_lowercase();
+ fn is_uppercase => |ch| ch.is_uppercase();
+ fn is_control => |ch| ch.is_control();
+ fn is_whitespace => |ch| ch.is_whitespace();
+ fn is_ascii_alphanumeric => |ch| ch.is_ascii_alphanumeric();
+ fn is_ascii_alphabetic => |ch| ch.is_ascii_alphabetic();
+ fn is_ascii_lowercase => |ch| ch.is_ascii_lowercase();
+ fn is_ascii_uppercase => |ch| ch.is_ascii_lowercase();
+ fn is_ascii_digit => |ch| ch.is_ascii_digit();
+ fn is_ascii_hexdigit => |ch| ch.is_ascii_hexdigit();
+ fn is_ascii_control => |ch| ch.is_ascii_control();
+ fn is_ascii_graphical => |ch| ch.is_ascii_graphic();
+ fn is_ascii_whitespace => |ch| ch.is_ascii_whitespace();
+ fn is_ascii_blank => |ch| ch == ' ' || ch == '\t';
+ fn is_ascii_printable => |ch| ch.is_alphanumeric() || ch.is_ascii_punctuation();
+ fn is_ascii_punctuation => |ch| ch.is_ascii_punctuation();
+);
+
+fn to_ascii_lower(ch: u32) -> u32 {
+ let Some(ch) = char::from_u32(ch) else {
+ return ch;
+ };
+
+ ch.to_ascii_lowercase() as u32
+}
+
+fn to_ascii_upper(ch: u32) -> u32 {
+ let Some(ch) = char::from_u32(ch) else {
+ return ch;
+ };
+
+ ch.to_ascii_uppercase() as u32
+}
+
+pub fn library(manager: &mut ScriptManager) -> Option<()> {
+ manager.add_library_function(LIBRARY_NAME, "iswalnum", is_alphanumeric)?;
+ manager.add_library_function(LIBRARY_NAME, "iswalpha", is_alphabetic)?;
+ manager.add_library_function(LIBRARY_NAME, "iswlower", is_lowercase)?;
+ manager.add_library_function(LIBRARY_NAME, "iswupper", is_uppercase)?;
+ manager.add_library_function(LIBRARY_NAME, "iswcntrl", is_control)?;
+ manager.add_library_function(LIBRARY_NAME, "iswspace", is_whitespace)?;
+ manager.add_library_function(LIBRARY_NAME, "isalnum", is_ascii_alphanumeric)?;
+ manager.add_library_function(LIBRARY_NAME, "isalpha", is_ascii_alphabetic)?;
+ manager.add_library_function(LIBRARY_NAME, "islower", is_ascii_lowercase)?;
+ manager.add_library_function(LIBRARY_NAME, "isupper", is_ascii_uppercase)?;
+ manager.add_library_function(LIBRARY_NAME, "isdigit", is_ascii_digit)?;
+ manager.add_library_function(LIBRARY_NAME, "isxdigit", is_ascii_hexdigit)?;
+ manager.add_library_function(LIBRARY_NAME, "iscntrl", is_ascii_control)?;
+ manager.add_library_function(LIBRARY_NAME, "isgraph", is_ascii_graphical)?;
+ manager.add_library_function(LIBRARY_NAME, "isspace", is_ascii_whitespace)?;
+ manager.add_library_function(LIBRARY_NAME, "isblank", is_ascii_blank)?;
+ manager.add_library_function(LIBRARY_NAME, "isprint", is_ascii_printable)?;
+ manager.add_library_function(LIBRARY_NAME, "ispunct", is_ascii_punctuation)?;
+ manager.add_library_function(LIBRARY_NAME, "tolower", to_ascii_lower)?;
+ manager.add_library_function(LIBRARY_NAME, "toupper", to_ascii_upper)?;
+
+ Some(())
+}
diff --git a/scripts/src/libs/math.rs b/scripts/src/libs/math.rs
new file mode 100644
index 0000000..4aae3c3
--- /dev/null
+++ b/scripts/src/libs/math.rs
@@ -0,0 +1,398 @@
+use crate::ScriptManager;
+
+use super::LIBRARY_NAME;
+
+fn abs_i32(n: i32) -> i32 {
+ n.abs()
+}
+
+fn abs_i64(n: i64) -> i64 {
+ n.abs()
+}
+
+fn mod_f32(x: f32, y: f32) -> f32 {
+ libm::fmodf(x, y)
+}
+
+fn mod_f64(x: f64, y: f64) -> f64 {
+ libm::fmod(x, y)
+}
+
+fn remainder_f32(x: f32, y: f32) -> f32 {
+ libm::remainderf(x, y)
+}
+
+fn remainder_f64(x: f64, y: f64) -> f64 {
+ libm::remainder(x, y)
+}
+
+fn mul_add_f32(x: f32, y: f32, z: f32) -> f32 {
+ x.mul_add(y, z)
+}
+
+fn mul_add_f64(x: f64, y: f64, z: f64) -> f64 {
+ x.mul_add(y, z)
+}
+
+fn fdim_f32(x: f32, y: f32) -> f32 {
+ libm::fdimf(x, y)
+}
+
+fn fdim_f64(x: f64, y: f64) -> f64 {
+ libm::fdim(x, y)
+}
+
+fn exp_f32(x: f32) -> f32 {
+ x.exp()
+}
+
+fn exp_f64(x: f64) -> f64 {
+ x.exp()
+}
+
+fn exp2_f32(x: f32) -> f32 {
+ x.exp2()
+}
+
+fn exp2_f64(x: f64) -> f64 {
+ x.exp2()
+}
+
+fn expm1_f32(x: f32) -> f32 {
+ x.exp_m1()
+}
+
+fn expm1_f64(x: f64) -> f64 {
+ x.exp_m1()
+}
+
+fn ln_f32(x: f32) -> f32 {
+ x.ln()
+}
+
+fn ln_f64(x: f64) -> f64 {
+ x.ln()
+}
+
+fn log10_f32(x: f32) -> f32 {
+ x.log10()
+}
+
+fn log10_f64(x: f64) -> f64 {
+ x.log10()
+}
+
+fn log2_f32(x: f32) -> f32 {
+ x.log2()
+}
+
+fn log2_f64(x: f64) -> f64 {
+ x.log2()
+}
+
+fn ln1p_f32(x: f32) -> f32 {
+ x.ln_1p()
+}
+
+fn ln1p_f64(x: f64) -> f64 {
+ x.ln_1p()
+}
+
+fn pow_f32(base: f32, exponent: f32) -> f32 {
+ base.powf(exponent)
+}
+
+fn pow_f64(base: f64, exponent: f64) -> f64 {
+ base.powf(exponent)
+}
+
+fn cbrt_f32(x: f32) -> f32 {
+ x.cbrt()
+}
+
+fn cbrt_f64(x: f64) -> f64 {
+ x.cbrt()
+}
+
+fn hypotenuse_f32(x: f32, y: f32) -> f32 {
+ x.hypot(y)
+}
+
+fn hypotenuse_f64(x: f64, y: f64) -> f64 {
+ x.hypot(y)
+}
+
+fn sin_f32(x: f32) -> f32 {
+ x.sin()
+}
+
+fn sin_f64(x: f64) -> f64 {
+ x.sin()
+}
+
+fn cos_f32(x: f32) -> f32 {
+ x.cos()
+}
+
+fn cos_f64(x: f64) -> f64 {
+ x.cos()
+}
+
+fn tan_f32(x: f32) -> f32 {
+ x.tan()
+}
+
+fn tan_f64(x: f64) -> f64 {
+ x.tan()
+}
+
+fn asin_f32(x: f32) -> f32 {
+ x.asin()
+}
+
+fn asin_f64(x: f64) -> f64 {
+ x.asin()
+}
+
+fn acos_f32(x: f32) -> f32 {
+ x.acos()
+}
+
+fn acos_f64(x: f64) -> f64 {
+ x.acos()
+}
+
+fn atan_f32(x: f32) -> f32 {
+ x.atan()
+}
+
+fn atan_f64(x: f64) -> f64 {
+ x.atan()
+}
+
+fn sinh_f32(x: f32) -> f32 {
+ x.sinh()
+}
+
+fn sinh_f64(x: f64) -> f64 {
+ x.sinh()
+}
+
+fn cosh_f32(x: f32) -> f32 {
+ x.cosh()
+}
+
+fn cosh_f64(x: f64) -> f64 {
+ x.cosh()
+}
+
+fn tanh_f32(x: f32) -> f32 {
+ x.tanh()
+}
+
+fn tanh_f64(x: f64) -> f64 {
+ x.tanh()
+}
+
+fn asinh_f32(x: f32) -> f32 {
+ x.asinh()
+}
+
+fn asinh_f64(x: f64) -> f64 {
+ x.asinh()
+}
+
+fn acosh_f32(x: f32) -> f32 {
+ x.acosh()
+}
+
+fn acosh_f64(x: f64) -> f64 {
+ x.acosh()
+}
+
+fn atanh_f32(x: f32) -> f32 {
+ x.atanh()
+}
+
+fn atanh_f64(x: f64) -> f64 {
+ x.atanh()
+}
+
+fn erf_f32(x: f32) -> f32 {
+ libm::erff(x)
+}
+
+fn erf_f64(x: f64) -> f64 {
+ libm::erf(x)
+}
+
+fn erfc_f32(x: f32) -> f32 {
+ libm::erfcf(x)
+}
+
+fn erfc_f64(x: f64) -> f64 {
+ libm::erfc(x)
+}
+
+fn gamma_f32(x: f32) -> f32 {
+ libm::tgammaf(x)
+}
+
+fn gamma_f64(x: f64) -> f64 {
+ libm::tgamma(x)
+}
+
+fn ln_gamma_f32(x: f32) -> f32 {
+ libm::lgammaf(x)
+}
+
+fn ln_gamma_f64(x: f64) -> f64 {
+ libm::lgamma(x)
+}
+
+fn round_f32_i32(x: f32) -> i32 {
+ x.round() as i32
+}
+
+fn round_f64_i32(x: f64) -> i32 {
+ x.round() as i32
+}
+
+fn round_f32_i64(x: f32) -> i64 {
+ x.round() as i64
+}
+
+fn round_f64_i64(x: f64) -> i64 {
+ x.round() as i64
+}
+
+fn frexp_f32(x: f32) -> (f32, i32) {
+ libm::frexpf(x)
+}
+
+fn frexp_f64(x: f64) -> (f64, i32) {
+ libm::frexp(x)
+}
+
+fn ldexp_f32(x: f32, exp: i32) -> f32 {
+ libm::ldexpf(x, exp)
+}
+
+fn ldexp_f64(x: f64, exp: i32) -> f64 {
+ libm::ldexp(x, exp)
+}
+
+fn modf_f32(x: f32) -> (f32, f32) {
+ libm::modff(x)
+}
+
+fn modf_f64(x: f64) -> (f64, f64) {
+ libm::modf(x)
+}
+
+fn logb_f32(x: f32) -> i32 {
+ libm::ilogbf(x)
+}
+
+fn logb_f64(x: f64) -> i32 {
+ libm::ilogb(x)
+}
+
+fn next_after_f32(from: f32, to: f32) -> f32 {
+ libm::nextafterf(from, to)
+}
+
+fn next_after_f64(from: f64, to: f64) -> f64 {
+ libm::nextafter(from, to)
+}
+
+fn copy_sign_f32(x: f32, y: f32) -> f32 {
+ libm::copysignf(x, y)
+}
+
+fn copy_sign_f64(x: f64, y: f64) -> f64 {
+ libm::copysign(x, y)
+}
+
+pub fn library(manager: &mut ScriptManager) -> Option<()> {
+ manager.add_library_function(LIBRARY_NAME, "abs", abs_i32)?;
+ manager.add_library_function(LIBRARY_NAME, "labs", abs_i64)?;
+ manager.add_library_function(LIBRARY_NAME, "modf", mod_f32)?;
+ manager.add_library_function(LIBRARY_NAME, "mod", mod_f64)?;
+ manager.add_library_function(LIBRARY_NAME, "remf", remainder_f32)?;
+ manager.add_library_function(LIBRARY_NAME, "rem", remainder_f64)?;
+ manager.add_library_function(LIBRARY_NAME, "fmaf", mul_add_f32)?;
+ manager.add_library_function(LIBRARY_NAME, "fma", mul_add_f64)?;
+ manager.add_library_function(LIBRARY_NAME, "fdimf", fdim_f32)?;
+ manager.add_library_function(LIBRARY_NAME, "fdim", fdim_f64)?;
+ manager.add_library_function(LIBRARY_NAME, "expf", exp_f32)?;
+ manager.add_library_function(LIBRARY_NAME, "exp", exp_f64)?;
+ manager.add_library_function(LIBRARY_NAME, "exp2f", exp2_f32)?;
+ manager.add_library_function(LIBRARY_NAME, "exp2", exp2_f64)?;
+ manager.add_library_function(LIBRARY_NAME, "expm1f", expm1_f32)?;
+ manager.add_library_function(LIBRARY_NAME, "expm1", expm1_f64)?;
+ manager.add_library_function(LIBRARY_NAME, "lnf", ln_f32)?;
+ manager.add_library_function(LIBRARY_NAME, "ln", ln_f64)?;
+ manager.add_library_function(LIBRARY_NAME, "log2f", log2_f32)?;
+ manager.add_library_function(LIBRARY_NAME, "log2", log2_f64)?;
+ manager.add_library_function(LIBRARY_NAME, "log10f", log10_f32)?;
+ manager.add_library_function(LIBRARY_NAME, "log10", log10_f64)?;
+ manager.add_library_function(LIBRARY_NAME, "ln1pf", ln1p_f32)?;
+ manager.add_library_function(LIBRARY_NAME, "ln1p", ln1p_f64)?;
+ manager.add_library_function(LIBRARY_NAME, "powf", pow_f32)?;
+ manager.add_library_function(LIBRARY_NAME, "pow", pow_f64)?;
+ manager.add_library_function(LIBRARY_NAME, "cbrtf", cbrt_f32)?;
+ manager.add_library_function(LIBRARY_NAME, "cbrt", cbrt_f64)?;
+ manager.add_library_function(LIBRARY_NAME, "hypotf", hypotenuse_f32)?;
+ manager.add_library_function(LIBRARY_NAME, "hypot", hypotenuse_f64)?;
+ manager.add_library_function(LIBRARY_NAME, "sinf", sin_f32)?;
+ manager.add_library_function(LIBRARY_NAME, "sin", sin_f64)?;
+ manager.add_library_function(LIBRARY_NAME, "cosf", cos_f32)?;
+ manager.add_library_function(LIBRARY_NAME, "cos", cos_f64)?;
+ manager.add_library_function(LIBRARY_NAME, "tanf", tan_f32)?;
+ manager.add_library_function(LIBRARY_NAME, "tan", tan_f64)?;
+ manager.add_library_function(LIBRARY_NAME, "asinf", asin_f32)?;
+ manager.add_library_function(LIBRARY_NAME, "asin", asin_f64)?;
+ manager.add_library_function(LIBRARY_NAME, "acosf", acos_f32)?;
+ manager.add_library_function(LIBRARY_NAME, "acos", acos_f64)?;
+ manager.add_library_function(LIBRARY_NAME, "atanf", atan_f32)?;
+ manager.add_library_function(LIBRARY_NAME, "atan", atan_f64)?;
+ manager.add_library_function(LIBRARY_NAME, "sinhf", sinh_f32)?;
+ manager.add_library_function(LIBRARY_NAME, "sinh", sinh_f64)?;
+ manager.add_library_function(LIBRARY_NAME, "coshf", cosh_f32)?;
+ manager.add_library_function(LIBRARY_NAME, "cosh", cosh_f64)?;
+ manager.add_library_function(LIBRARY_NAME, "tanhf", tanh_f32)?;
+ manager.add_library_function(LIBRARY_NAME, "tanh", tanh_f64)?;
+ manager.add_library_function(LIBRARY_NAME, "asinhf", asinh_f32)?;
+ manager.add_library_function(LIBRARY_NAME, "asinh", asinh_f64)?;
+ manager.add_library_function(LIBRARY_NAME, "acoshf", acosh_f32)?;
+ manager.add_library_function(LIBRARY_NAME, "acosh", acosh_f64)?;
+ manager.add_library_function(LIBRARY_NAME, "atanhf", atanh_f32)?;
+ manager.add_library_function(LIBRARY_NAME, "atanh", atanh_f64)?;
+ manager.add_library_function(LIBRARY_NAME, "erff", erf_f32)?;
+ manager.add_library_function(LIBRARY_NAME, "erf", erf_f64)?;
+ manager.add_library_function(LIBRARY_NAME, "erfcf", erfc_f32)?;
+ manager.add_library_function(LIBRARY_NAME, "erfc", erfc_f64)?;
+ manager.add_library_function(LIBRARY_NAME, "gammaf", gamma_f32)?;
+ manager.add_library_function(LIBRARY_NAME, "gamma", gamma_f64)?;
+ manager.add_library_function(LIBRARY_NAME, "lgammaf", ln_gamma_f32)?;
+ manager.add_library_function(LIBRARY_NAME, "lgamma", ln_gamma_f64)?;
+ manager.add_library_function(LIBRARY_NAME, "lroundf", round_f32_i32)?;
+ manager.add_library_function(LIBRARY_NAME, "lround", round_f64_i32)?;
+ manager.add_library_function(LIBRARY_NAME, "llroundf", round_f32_i64)?;
+ manager.add_library_function(LIBRARY_NAME, "llround", round_f64_i64)?;
+ manager.add_library_function(LIBRARY_NAME, "frexpf", frexp_f32)?;
+ manager.add_library_function(LIBRARY_NAME, "frexp", frexp_f64)?;
+ manager.add_library_function(LIBRARY_NAME, "ldexpf", ldexp_f32)?;
+ manager.add_library_function(LIBRARY_NAME, "ldexp", ldexp_f64)?;
+ manager.add_library_function(LIBRARY_NAME, "modff", modf_f32)?;
+ manager.add_library_function(LIBRARY_NAME, "modf", modf_f64)?;
+ manager.add_library_function(LIBRARY_NAME, "ilogbf", logb_f32)?;
+ manager.add_library_function(LIBRARY_NAME, "ilogb", logb_f64)?;
+ manager.add_library_function(LIBRARY_NAME, "nextafterf", next_after_f32)?;
+ manager.add_library_function(LIBRARY_NAME, "nextafter", next_after_f64)?;
+ manager.add_library_function(LIBRARY_NAME, "copysignf", copy_sign_f32)?;
+ manager.add_library_function(LIBRARY_NAME, "copysign", copy_sign_f64)?;
+
+ Some(())
+}
diff --git a/scripts/src/libs/mod.rs b/scripts/src/libs/mod.rs
new file mode 100644
index 0000000..c6b1ffc
--- /dev/null
+++ b/scripts/src/libs/mod.rs
@@ -0,0 +1,19 @@
+use crate::ScriptManager;
+
+mod allocate;
+mod buffer;
+mod ctype;
+mod math;
+mod system;
+
+const LIBRARY_NAME: &str = "alligator";
+
+pub fn add_alligator_library(manager: &mut ScriptManager) -> Option<()> {
+ allocate::library(manager)?;
+ buffer::library(manager)?;
+ ctype::library(manager)?;
+ math::library(manager)?;
+ system::library(manager)?;
+
+ Some(())
+}
diff --git a/scripts/src/libs/system.rs b/scripts/src/libs/system.rs
new file mode 100644
index 0000000..e2d0ddb
--- /dev/null
+++ b/scripts/src/libs/system.rs
@@ -0,0 +1,154 @@
+use std::mem::align_of;
+use std::{borrow::Cow, process::Command};
+
+use chrono::Offset;
+use wasmtime::{Caller, Memory};
+
+use crate::{ScriptManager, WasmScriptState};
+
+use super::LIBRARY_NAME;
+
+pub fn _get_memory<'a>(caller: &'a mut Caller<WasmScriptState>) -> (Memory, &'a mut [u8]) {
+ let memory = caller.get_export("memory").unwrap().into_memory().unwrap();
+ let mem_ptr = memory.data_mut(caller);
+ (memory, mem_ptr)
+}
+
+fn _get_string<'a>(
+ caller: &'a mut Caller<WasmScriptState>,
+ offset: u32,
+ length: u32,
+) -> Option<Cow<'a, str>> {
+ let (_, mem_ptr) = _get_memory(caller);
+ if (offset as usize + length as usize) > mem_ptr.len() {
+ return None;
+ }
+
+ Some(String::from_utf8_lossy(
+ &mem_ptr[offset as usize..length as usize],
+ ))
+}
+
+fn _log(caller: &mut Caller<WasmScriptState>, level: log::Level, message: u32, length: u32) -> u32 {
+ let Some(message) = _get_string(caller, message, length) else {
+ return 4;
+ };
+ log::log!(level, "{}", message);
+ 0
+}
+
+pub fn _alloc<'a>(
+ mut caller: &'a mut Caller<WasmScriptState>,
+ size: u32,
+ align: u32,
+) -> Option<(u32, &'a mut [u8])> {
+ // find bumper location in memory
+ let bump_offset = caller.data().bump_pointer;
+ let (memory, memory_ptr) = _get_memory(caller);
+ let bump_ptr = &memory_ptr[bump_offset as usize..];
+
+ // calculate pointer to the new allocation
+ let offset = bump_ptr.as_ptr().align_offset(align as usize);
+ let new_offset = bump_offset + offset as u32;
+
+ // update bump pointer
+ let bump_offset = &mut caller.data_mut().bump_pointer;
+ *bump_offset = new_offset + size;
+
+ // grow memory if necessary
+ let bump_offset = *bump_offset;
+ if memory.data_size(&caller) < bump_offset as usize {
+ let delta = (size + offset as u32) / (64 * 1024) + 1;
+ if memory.grow(&mut caller, delta as u64).is_err() {
+ return None;
+ }
+ }
+
+ let new_ptr = &mut _get_memory(caller).1[new_offset as usize..size as usize];
+ Some((new_offset, new_ptr))
+}
+
+fn allocate(mut caller: Caller<WasmScriptState>, size: u32, align: u32) -> u32 {
+ match _alloc(&mut caller, size, align) {
+ Some((ptr, _)) => ptr,
+ None => 0,
+ }
+}
+
+fn free_sized(mut caller: Caller<'_, WasmScriptState>, ptr: u32, size: u32) -> u32 {
+ let bump_offset = &mut caller.data_mut().bump_pointer;
+ if ptr + size == *bump_offset {
+ *bump_offset = ptr;
+ }
+
+ 0
+}
+
+fn free_aligned_sized(
+ caller: Caller<'_, WasmScriptState>,
+ ptr: u32,
+ _alignment: u32,
+ size: u32,
+) -> u32 {
+ free_sized(caller, ptr, size)
+}
+
+fn env_var(mut caller: Caller<'_, WasmScriptState>, name: u32, length: u32) -> u32 {
+ let Some(name) = _get_string(&mut caller, name, length) else {
+ return 4;
+ };
+ let Ok(value) = std::env::var(name.to_string()) else {
+ return 1;
+ };
+
+ let Some((offset, ptr)) = _alloc(&mut caller, value.len() as u32, align_of::<u8>() as u32)
+ else {
+ return 0;
+ };
+ ptr.clone_from_slice(value.as_bytes());
+
+ offset
+}
+
+fn random_number() -> u64 {
+ rand::random()
+}
+
+fn current_time_utc() -> (i64, u32) {
+ let duration = chrono::Utc::now() - chrono::DateTime::<chrono::Utc>::MIN_UTC;
+ (
+ duration.num_seconds(),
+ duration.to_std().unwrap().subsec_nanos(),
+ )
+}
+
+fn local_offset() -> i32 {
+ chrono::Local::now().offset().fix().local_minus_utc()
+}
+
+fn log(mut caller: Caller<'_, WasmScriptState>, message: u32, length: u32) {
+ _log(&mut caller, log::Level::Info, message, length);
+}
+
+fn log_warn(mut caller: Caller<'_, WasmScriptState>, message: u32, length: u32) {
+ _log(&mut caller, log::Level::Warn, message, length);
+}
+
+fn log_error(mut caller: Caller<'_, WasmScriptState>, message: u32, length: u32) {
+ _log(&mut caller, log::Level::Error, message, length);
+}
+
+pub fn library(manager: &mut ScriptManager) -> Option<()> {
+ manager.add_library_function(LIBRARY_NAME, "aligned_alloc", allocate)?;
+ manager.add_library_function(LIBRARY_NAME, "free_sized", free_sized)?;
+ manager.add_library_function(LIBRARY_NAME, "free_aligned_sized", free_aligned_sized)?;
+ manager.add_library_function(LIBRARY_NAME, "env_var", env_var)?;
+ manager.add_library_function(LIBRARY_NAME, "random_number", random_number)?;
+ manager.add_library_function(LIBRARY_NAME, "current_time_utc", current_time_utc)?;
+ manager.add_library_function(LIBRARY_NAME, "local_offset", local_offset)?;
+ manager.add_library_function(LIBRARY_NAME, "log", log)?;
+ manager.add_library_function(LIBRARY_NAME, "log_warn", log_warn)?;
+ manager.add_library_function(LIBRARY_NAME, "log_error", log_error)?;
+
+ Some(())
+}
diff --git a/scripts/src/vtable.rs b/scripts/src/vtable.rs
new file mode 100644
index 0000000..0fbb324
--- /dev/null
+++ b/scripts/src/vtable.rs
@@ -0,0 +1,10 @@
+/// A trait for scripts
+pub trait ScriptTable: Send + Sync {
+ /// This is called whenever the script is enabled
+ fn begin(&mut self);
+
+ /// This is called every frame
+ fn update(&mut self);
+}
+
+pub type VTableScript = &'static mut dyn ScriptTable;
diff --git a/scripts/src/wasm.rs b/scripts/src/wasm.rs
new file mode 100644
index 0000000..7ac9dae
--- /dev/null
+++ b/scripts/src/wasm.rs
@@ -0,0 +1,170 @@
+use std::path::Path;
+
+use thiserror::Error;
+use wasmtime::{Engine, Instance, Linker, Module, Mutability, Store, ValType};
+
+// It's a bad idea to have the memory allocator return a number that could also
+// indicate an error, so this is set to be at least eight.
+const MIN_BUMP_POINTER: u32 = 8;
+
+/// Information about the script which can be used by functions it calls
+pub struct WasmScriptState {
+ pub bump_pointer: u32,
+ pub trusted: bool,
+}
+
+impl WasmScriptState {
+ pub const fn new(trusted: bool) -> Self {
+ Self {
+ bump_pointer: MIN_BUMP_POINTER,
+ trusted,
+ }
+ }
+}
+
+/// A script, its path in the filesystem, and some metadata
+pub struct WasmScript {
+ path: Box<Path>,
+ module: Module,
+ store: Store<WasmScriptState>,
+ instance: Instance,
+ trusted: bool,
+ //state: Value,
+}
+
+#[derive(Debug, Error)]
+pub enum InvalidWasmScript {
+ #[error("There is no exported memory called 'memory', which is required")]
+ NoExportedMemory,
+ #[error("The exported symbol, 'memory' must be a memory")]
+ MemoryIsNotAMemory,
+ #[error("The memory must be 32-bit, not 64-bit")]
+ MemoryTooLarge,
+ #[error("There is no exported global called '__heap_base', which is required")]
+ NoHeapBase,
+ #[error("The exported symbol, '__heap_base' must be a constant global")]
+ HeapBaseIsNotGlobal,
+ #[error("The exported global, '__heap_base' must be an i32")]
+ HeapBaseMustBeI32,
+ #[error("The exported global, '__heap_base' must be a constant")]
+ HeapBaseMustBeConstant,
+ #[error("{}", .0)]
+ CompilerError(#[from] wasmtime::Error),
+}
+
+/// Confirms that the module can be used as a script
+fn validate_module(module: &Module) -> Result<(), InvalidWasmScript> {
+ // verify that memory is exported from this module and is valid
+ let Some(export) = module.get_export("memory") else {
+ return Err(InvalidWasmScript::NoExportedMemory);
+ };
+ let Some(memory) = export.memory() else {
+ return Err(InvalidWasmScript::MemoryIsNotAMemory);
+ };
+ if memory.is_64() {
+ return Err(InvalidWasmScript::MemoryTooLarge);
+ }
+
+ // verify __heap_base global
+ let Some(export) = module.get_export("__heap_base") else {
+ return Err(InvalidWasmScript::NoHeapBase);
+ };
+ let Some(heap_base) = export.global() else {
+ return Err(InvalidWasmScript::HeapBaseIsNotGlobal);
+ };
+ if *heap_base.content() != ValType::I32 {
+ return Err(InvalidWasmScript::HeapBaseMustBeI32);
+ }
+ if heap_base.mutability() != Mutability::Const {
+ return Err(InvalidWasmScript::HeapBaseMustBeConstant);
+ }
+
+ Ok(())
+}
+
+impl WasmScript {
+ pub fn new(
+ path: &Path,
+ engine: &Engine,
+ linker: &Linker<WasmScriptState>,
+ trusted: bool,
+ ) -> Result<Self, InvalidWasmScript> {
+ let module = Module::from_file(engine, path)?;
+ validate_module(&module)?;
+ let mut store = Store::new(engine, WasmScriptState::new(trusted));
+ let instance = linker.instantiate(&mut store, &module)?;
+
+ Ok(Self {
+ path: path.into(),
+ module,
+ store,
+ instance,
+ trusted,
+ })
+ }
+
+ /// Reload from the filesystem
+ pub fn reload(
+ &mut self,
+ engine: &Engine,
+ linker: &Linker<WasmScriptState>,
+ ) -> Result<(), InvalidWasmScript> {
+ let module = Module::from_file(engine, &self.path)?;
+ validate_module(&module)?;
+ self.store = Store::new(engine, WasmScriptState::new(self.trusted));
+ self.instance = linker.instantiate(&mut self.store, &module)?;
+
+ Ok(())
+ }
+
+ /// Re-links the module. This doesn't load the module from the filesystem.
+ pub fn relink(
+ &mut self,
+ engine: &Engine,
+ linker: &Linker<WasmScriptState>,
+ ) -> Result<(), InvalidWasmScript> {
+ self.store = Store::new(engine, WasmScriptState::new(self.trusted));
+ self.instance = linker.instantiate(&mut self.store, &self.module)?;
+ Ok(())
+ }
+
+ pub fn is_trusted(&self) -> bool {
+ self.trusted
+ }
+
+ pub fn trust(&mut self) {
+ self.trusted = true;
+ }
+
+ pub fn untrust(&mut self) {
+ self.trusted = false;
+ }
+
+ fn run_function_if_exists(&mut self, name: &str) -> wasmtime::Result<()> {
+ // set bump pointer to the start of the heap
+ let heap_base = self
+ .instance
+ .get_global(&mut self.store, "__heap_base")
+ .unwrap()
+ .get(&mut self.store)
+ .unwrap_i32();
+ let bump_ptr = &mut self.store.data_mut().bump_pointer;
+ *bump_ptr = (heap_base as u32).max(MIN_BUMP_POINTER);
+
+ // call the given function
+ let func = self.instance.get_func(&mut self.store, name);
+ if let Some(func) = func {
+ func.call(&mut self.store, &[], &mut [])?;
+ }
+
+ Ok(())
+ }
+
+ pub fn begin(&mut self) -> wasmtime::Result<()> {
+ self.run_function_if_exists("begin")
+ }
+
+ pub fn update(&mut self) -> wasmtime::Result<()> {
+ self.run_function_if_exists("update")
+ }
+}