summaryrefslogtreecommitdiff
path: root/scripts/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/src/lib.rs')
-rw-r--r--scripts/src/lib.rs318
1 files changed, 318 insertions, 0 deletions
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;
+ }
+}