diff --git a/.gitignore b/.gitignore index c78e860..651e8f7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ /target **/captures +*.crab *.csv *.log @@ -14,4 +15,4 @@ webui/dist *-wal .DS_Store -.idea \ No newline at end of file +.idea diff --git a/Cargo.lock b/Cargo.lock index 8d27e52..9895776 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -608,6 +608,16 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "animation-wrapper" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", + "tar", + "thiserror 2.0.3", +] + [[package]] name = "animations" version = "0.1.0" @@ -1923,9 +1933,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.17" +version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" +checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" dependencies = [ "clap_builder", "clap_derive", @@ -1933,9 +1943,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.17" +version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" +checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" dependencies = [ "anstream", "anstyle", @@ -1945,9 +1955,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.13" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ "heck", "proc-macro2", @@ -2926,6 +2936,18 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "filetime" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +dependencies = [ + "cfg-if", + "libc", + "libredox 0.1.3", + "windows-sys 0.59.0", +] + [[package]] name = "fixedbitset" version = "0.4.2" @@ -4341,6 +4363,7 @@ checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags 2.6.0", "libc", + "redox_syscall 0.5.3", ] [[package]] @@ -6202,6 +6225,7 @@ version = "0.1.0" dependencies = [ "animation-api 0.1.0", "animation-wasm-bindings 0.1.0", + "animation-wrapper", "async-trait", "chrono", "clap", @@ -7030,6 +7054,17 @@ dependencies = [ "winx", ] +[[package]] +name = "tar" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c65998313f8e17d0d553d28f91a0df93e4dbbbf770279c7bc21ca0f09ea1a1f6" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "target-lexicon" version = "0.12.16" @@ -8928,6 +8963,17 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec107c4503ea0b4a98ef47356329af139c0a4f7750e621cf2973cd3385ebcb3d" +[[package]] +name = "xattr" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f" +dependencies = [ + "libc", + "linux-raw-sys", + "rustix", +] + [[package]] name = "xkbcommon-dl" version = "0.4.2" diff --git a/Cargo.toml b/Cargo.toml index d22c067..d2fb7a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ members = [ "animation-template-wasm", "animation-utils", "animation-wasm-bindings", + "animation-wrapper", "animations", "light-client", "configurator", diff --git a/animation-template-wasm/Cargo.toml b/animation-template-wasm/Cargo.toml index 6e0dab5..2cdda42 100644 --- a/animation-template-wasm/Cargo.toml +++ b/animation-template-wasm/Cargo.toml @@ -3,6 +3,9 @@ name = "animation-template-wasm" version = "0.1.0" edition = "2021" +[lib] +crate-type = ["cdylib"] + [dependencies] animation-utils = { git = "https://github.com/mrozycki/rustmas", rev = "835ad9e" } animation-api = { git = "https://github.com/mrozycki/rustmas", rev = "835ad9e" } diff --git a/animation-wasm-bindings/src/host.rs b/animation-wasm-bindings/src/host.rs index 3829724..c609ea2 100644 --- a/animation-wasm-bindings/src/host.rs +++ b/animation-wasm-bindings/src/host.rs @@ -1,4 +1,9 @@ -use std::{collections::HashMap, path::Path}; +use std::{ + collections::HashMap, + fs::File, + io::{BufReader, Read}, + path::Path, +}; use animation_api::{event::Event, schema}; use exports::guest::animation::plugin::{Color, Position}; @@ -33,6 +38,9 @@ impl WasiView for State { pub enum HostedPluginError { #[error("wasmtime returned error: {0}")] WasmtimeError(#[from] wasmtime::Error), + + #[error("cannot open plugin: {0}")] + PluginOpenError(#[from] std::io::Error), } type Result = std::result::Result; @@ -44,10 +52,18 @@ pub struct HostedPlugin { impl HostedPlugin { pub async fn new(executable_path: &Path, points: Vec<(f64, f64, f64)>) -> Result { + let reader = BufReader::new(File::open(executable_path)?); + Self::from_reader(reader, points).await + } + + pub async fn from_reader(mut reader: R, points: Vec<(f64, f64, f64)>) -> Result { + let mut data = Vec::new(); + reader.read_to_end(&mut data)?; + let mut config = Config::new(); config.async_support(true); let engine = Engine::new(&config)?; - let component = Component::from_file(&engine, executable_path)?; + let component = Component::from_binary(&engine, &data)?; let mut linker = Linker::new(&engine); wasmtime_wasi::add_to_linker_async(&mut linker)?; diff --git a/animation-wrapper/Cargo.toml b/animation-wrapper/Cargo.toml new file mode 100644 index 0000000..a4fa7d1 --- /dev/null +++ b/animation-wrapper/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "animation-wrapper" +version = "0.1.0" +edition = "2021" + +[dependencies] +serde = { version = "1.0.215", features = ["derive"] } +serde_json = "1.0.133" +tar = "0.4.43" +thiserror = "2.0.3" + +[features] +default = ["wrap", "unwrap"] +wrap = [] +unwrap = [] diff --git a/animation-wrapper/README.md b/animation-wrapper/README.md new file mode 100644 index 0000000..a7cdeaa --- /dev/null +++ b/animation-wrapper/README.md @@ -0,0 +1,14 @@ +Rustmas Animation Wrapper +========================= + +This crate contains utilities for wrapping animations into Compiled Rustmas +Animation Bundles (yes, we worked very hard on this acronym, glad you noticed). + +Additionally, an executable `crabwrap` is provided to help create animation +plugin files from animation code. In order to create an animation plugin file, +run `crabwrap` from the root directory of your animation. Before that, +you might need to install the `wasm32-wasip2` target for your Rust toolchain: + +``` +rustup target add wasm32-wasip2 +``` \ No newline at end of file diff --git a/animation-wrapper/src/bin/crabwrap.rs b/animation-wrapper/src/bin/crabwrap.rs new file mode 100644 index 0000000..0de9667 --- /dev/null +++ b/animation-wrapper/src/bin/crabwrap.rs @@ -0,0 +1,95 @@ +use std::{ + fs::read_dir, + path::PathBuf, + process::{Command, ExitStatus}, +}; + +fn build_plugin() -> std::io::Result { + let mut cmd = Command::new("cargo") + .args(["build", "--target", "wasm32-wasip2", "--release"]) + .spawn()?; + cmd.wait() +} + +fn find_animation_id() -> std::io::Result { + std::env::current_dir()? + .components() + .last() + .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::NotFound, "Invalid project path")) + .map(|c| c.as_os_str().to_string_lossy().to_string()) + .map(|s| s.replace('-', "_")) +} + +fn find_manifest() -> std::io::Result { + let manifest_path = std::env::current_dir()?.join("manifest.json"); + + if !manifest_path.exists() { + Err(std::io::Error::new( + std::io::ErrorKind::NotFound, + "Could not find manifest.json", + )) + } else { + Ok(manifest_path) + } +} + +fn find_animation_executable() -> std::io::Result { + let project_root = find_project_root()?; + let animation_id = find_animation_id()?; + + let executable_path = project_root + .join("target/wasm32-wasip2/release") + .join(format!("{animation_id}.wasm")); + + if !executable_path.exists() { + Err(std::io::Error::new( + std::io::ErrorKind::NotFound, + "Could not find animation executable", + )) + } else { + Ok(executable_path) + } +} + +fn get_output_path() -> std::io::Result { + let animation_id = find_animation_id()?; + Ok(std::env::current_dir()? + .as_path() + .join(format!("{animation_id}.crab"))) +} + +fn find_project_root() -> std::io::Result { + std::env::current_dir()? + .as_path() + .ancestors() + .find(|p| { + let Ok(dir) = read_dir(p) else { + return false; + }; + dir.into_iter() + .filter_map(|p| p.ok()) + .map(|p| p.file_name().to_string_lossy().to_string()) + .any(|p| p == "Cargo.lock") + }) + .map(PathBuf::from) + .ok_or_else(|| { + std::io::Error::new( + std::io::ErrorKind::NotFound, + "Could not find Cargo.lock until filesystem root", + ) + }) +} + +fn main() { + let manifest_path = find_manifest().expect("Could not find manifest.json file"); + + build_plugin().expect("Failed to build the plugin"); + + let executable_path = find_animation_executable().expect("Could not find animation executable"); + let output_path = get_output_path().expect("Could not generate output path"); + + animation_wrapper::wrap::wrap_plugin(&output_path, &executable_path, &manifest_path) + .expect("Failed to wrap the plugin"); + + println!("Plugin ready at {}", output_path.to_string_lossy()); +} diff --git a/animation-wrapper/src/config.rs b/animation-wrapper/src/config.rs new file mode 100644 index 0000000..8317156 --- /dev/null +++ b/animation-wrapper/src/config.rs @@ -0,0 +1,108 @@ +use std::{ + path::{Path, PathBuf}, + process::{Command, Stdio}, +}; + +use serde::Deserialize; + +use crate::unwrap::PluginUnwrapError; + +#[derive(Debug, thiserror::Error)] +pub enum PluginConfigError { + #[error("Failed to parse manifest: {reason}")] + InvalidManifest { reason: String }, + + #[error("Failed to unwrap plugin")] + InvalidCrab(#[from] PluginUnwrapError), + + #[error("Directory containing plugin has non UTF-8 name")] + NonUtf8DirectoryName, +} + +#[derive(Debug, Clone, Copy, Default, PartialEq, Deserialize)] +#[serde(rename_all = "snake_case")] +pub enum PluginType { + #[default] + Native, + Wasm, +} + +#[derive(Debug, Clone, Deserialize)] +pub struct PluginManifest { + display_name: String, + #[serde(default)] + plugin_type: PluginType, +} + +#[derive(Debug, Clone)] +pub struct PluginConfig { + pub(crate) animation_id: String, + pub(crate) manifest: PluginManifest, + pub(crate) path: PathBuf, +} + +impl PluginConfig { + pub fn new(path: PathBuf) -> Result { + let manifest: PluginManifest = + serde_json::from_slice(&std::fs::read(path.join("manifest.json")).map_err(|e| { + PluginConfigError::InvalidManifest { + reason: format!("IO error: {}", e), + } + })?) + .map_err(|e| PluginConfigError::InvalidManifest { + reason: e.to_string(), + })?; + + let animation_id = path + .file_name() + .unwrap() + .to_str() + .ok_or(PluginConfigError::NonUtf8DirectoryName)? + .to_owned(); + + Ok(Self { + animation_id, + manifest, + path, + }) + } + + pub fn animation_id(&self) -> &str { + &self.animation_id + } + + pub fn animation_name(&self) -> &str { + &self.manifest.display_name + } + + pub fn plugin_type(&self) -> PluginType { + self.manifest.plugin_type + } + + pub fn path(&self) -> &Path { + self.path.as_path() + } + + pub fn executable_path(&self) -> PathBuf { + let executable_name = if self.manifest.plugin_type == PluginType::Wasm { + "plugin.wasm" + } else if cfg!(windows) { + "plugin.exe" + } else { + "plugin" + }; + + self.path.join(executable_name) + } + + pub fn is_executable(&self) -> bool { + match self.manifest.plugin_type { + PluginType::Native => Command::new(self.executable_path()) + .stdout(Stdio::null()) + .stdin(Stdio::null()) + .spawn() + .is_ok(), + PluginType::Wasm => self.executable_path().exists(), + } + } +} diff --git a/animation-wrapper/src/lib.rs b/animation-wrapper/src/lib.rs new file mode 100644 index 0000000..54fbf76 --- /dev/null +++ b/animation-wrapper/src/lib.rs @@ -0,0 +1,7 @@ +pub mod config; + +#[cfg(feature = "wrap")] +pub mod wrap; + +#[cfg(feature = "unwrap")] +pub mod unwrap; diff --git a/animation-wrapper/src/unwrap.rs b/animation-wrapper/src/unwrap.rs new file mode 100644 index 0000000..b3ddc4f --- /dev/null +++ b/animation-wrapper/src/unwrap.rs @@ -0,0 +1,128 @@ +use std::{ + fs::File, + io::{BufReader, Read, Seek}, + path::Path, +}; + +use tar::Archive; + +use crate::config::{PluginConfig, PluginManifest}; + +#[derive(Debug, thiserror::Error)] +pub enum PluginUnwrapError { + #[error("Cannot open plugin file: {0}")] + CannotOpenFile(#[from] std::io::Error), + + #[error("CRAB is missing manifest.json entry")] + MissingManifest, + + #[error("Invalid manifest: {0}")] + InvalidManifest(#[from] serde_json::error::Error), + + #[error("CRAB is missing plugin.wasm entry")] + MissingWasm, + + #[error("Invalid CRAB file name")] + InvalidFilename, +} + +fn manifest_from_crab(path: &Path) -> Result { + let reader = BufReader::new(File::open(path)?); + let mut archive = Archive::new(reader); + let mut entries = archive.entries_with_seek()?; + + let entry_reader = entries + .find(|e| { + e.as_ref().is_ok_and(|e| { + e.path() + .is_ok_and(|p| p.to_str().is_some_and(|p| p == "manifest.json")) + }) + }) + .ok_or(PluginUnwrapError::MissingManifest)??; + + Ok(serde_json::from_reader(entry_reader)?) +} + +fn animation_id_from_crab(path: &Path) -> Result { + Ok(path + .file_name() + .ok_or(PluginUnwrapError::InvalidFilename)? + .to_string_lossy() + .trim_end_matches(".crab") + .to_string()) +} + +pub fn unwrap_plugin>(path: &P) -> Result { + fn inner(path: &Path) -> Result { + let animation_id = animation_id_from_crab(path)?; + let manifest = manifest_from_crab(path)?; + let path = path.to_owned(); + + Ok(PluginConfig { + animation_id, + manifest, + path, + }) + } + inner(path.as_ref()) +} + +pub fn reader_from_crab>(path: &P) -> Result { + let (start, size) = { + let reader = BufReader::new(File::open(path)?); + let mut archive = Archive::new(reader); + let mut entries = archive.entries_with_seek()?; + + let wasm_entry = entries + .find(|e| { + e.as_ref().is_ok_and(|e| { + e.path() + .is_ok_and(|p| p.to_str().is_some_and(|p| p == "plugin.wasm")) + }) + }) + .ok_or(PluginUnwrapError::MissingWasm)??; + + (wasm_entry.raw_file_position(), wasm_entry.size()) + }; + + let mut reader = BufReader::new(File::open(path)?); + reader.seek(std::io::SeekFrom::Start(start))?; + + Ok(LimitedReader::new(reader, size as usize)) +} + +pub struct LimitedReader +where + R: Read, +{ + inner: R, + limit: usize, + position: usize, +} + +impl LimitedReader +where + R: Read, +{ + fn new(reader: R, limit: usize) -> Self { + Self { + inner: reader, + limit, + position: 0, + } + } +} + +impl Read for LimitedReader +where + R: Read, +{ + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + let left = self.limit - self.position; + let to_read = left.min(buf.len()); + let buf = &mut buf[0..to_read]; + let read = self.inner.read(buf)?; + self.position += read; + Ok(read) + } +} diff --git a/animation-wrapper/src/wrap.rs b/animation-wrapper/src/wrap.rs new file mode 100644 index 0000000..0a76b42 --- /dev/null +++ b/animation-wrapper/src/wrap.rs @@ -0,0 +1,34 @@ +use std::{ + fs::File, + io::{BufWriter, Write}, + path::Path, +}; + +pub fn wrap_plugin( + output_path: P, + executable_path: Q, + manifest_path: R, +) -> std::io::Result<()> +where + P: AsRef, + Q: AsRef, + R: AsRef, +{ + fn inner( + output_path: &Path, + executable_path: &Path, + manifest_path: &Path, + ) -> std::io::Result<()> { + let mut archive = tar::Builder::new(Vec::new()); + archive.append_path_with_name(manifest_path, "manifest.json")?; + archive.append_path_with_name(executable_path, "plugin.wasm")?; + let archive_data = archive.into_inner()?; + + BufWriter::new(File::create(output_path)?).write_all(&archive_data) + } + inner( + output_path.as_ref(), + executable_path.as_ref(), + manifest_path.as_ref(), + ) +} diff --git a/animator/Cargo.toml b/animator/Cargo.toml index ab02c08..ac2abc0 100644 --- a/animator/Cargo.toml +++ b/animator/Cargo.toml @@ -5,6 +5,9 @@ edition = "2021" [dependencies] rustmas-light-client = { path = "../light-client", default-features = false } +animation-wrapper = { path = "../animation-wrapper", default-features = false, features = [ + "unwrap", +] } lightfx = { path = "../lightfx" } animation-api = { path = "../animation-api" } events = { path = "../events", default-features = false } diff --git a/animator/src/controller.rs b/animator/src/controller.rs index 6c90fad..9e79564 100644 --- a/animator/src/controller.rs +++ b/animator/src/controller.rs @@ -5,6 +5,7 @@ use std::sync::Arc; use animation_api::event::Event; use animation_api::schema::{Configuration, ParameterValue}; +use animation_wrapper::config::PluginConfig; use chrono::{DateTime, Duration, Utc}; use client::combined::{CombinedLightClient, CombinedLightClientBuilder}; #[cfg(feature = "audio")] @@ -22,7 +23,7 @@ use tokio::sync::{mpsc, Mutex}; use tokio::task::JoinHandle; use crate::factory::{AnimationFactory, AnimationFactoryError}; -use crate::plugin::{AnimationPluginError, Plugin, PluginConfig}; +use crate::plugin::{AnimationPluginError, Plugin}; use crate::ControllerConfig; #[derive(Debug, thiserror::Error)] diff --git a/animator/src/factory.rs b/animator/src/factory.rs index 1239084..1b3b25d 100644 --- a/animator/src/factory.rs +++ b/animator/src/factory.rs @@ -3,11 +3,15 @@ use std::{ path::{Path, PathBuf}, }; +use animation_wrapper::{ + config::{PluginConfig, PluginConfigError, PluginType}, + unwrap, +}; use log::warn; use crate::{ jsonrpc::JsonRpcPlugin, - plugin::{AnimationPluginError, Plugin, PluginConfig, PluginConfigError, PluginType}, + plugin::{AnimationPluginError, Plugin}, wasm::WasmPlugin, }; @@ -75,7 +79,19 @@ impl AnimationFactory { warn!("Discovered {} plugins which are not executable. Please make sure the animations were built and have correct permissions.", invalid_plugins.len()); } + let crab_plugins = self + .plugin_dir + .read_dir() + .map_err(|e| AnimationFactoryError::InternalError { + reason: format!("Failed to read plugin directory: {e}"), + })? + .filter_map(|d| d.ok()) + .filter(|d| d.file_name().to_str().is_some_and(|d| d.ends_with(".crab"))) + .filter_map(|d| unwrap::unwrap_plugin(&d.path()).ok()) + .map(|p| (p.animation_id().to_owned(), p)); + self.available_plugins = valid_plugins; + self.available_plugins.extend(crab_plugins); Ok(()) } diff --git a/animator/src/jsonrpc.rs b/animator/src/jsonrpc.rs index 89f2b6a..4656acc 100644 --- a/animator/src/jsonrpc.rs +++ b/animator/src/jsonrpc.rs @@ -10,12 +10,13 @@ use animation_api::{ schema::{Configuration, ConfigurationSchema, ParameterValue}, AnimationError, JsonRpcMessage, JsonRpcMethod, JsonRpcResponse, JsonRpcResult, }; +use animation_wrapper::config::PluginConfig; use async_trait::async_trait; use log::error; use serde::de::DeserializeOwned; use tokio::sync::Mutex; -use crate::plugin::{AnimationPluginError, Plugin, PluginConfig}; +use crate::plugin::{AnimationPluginError, Plugin}; #[derive(Debug, thiserror::Error)] pub enum JsonRpcEndpointError { diff --git a/animator/src/plugin.rs b/animator/src/plugin.rs index 2927db2..5f7ef28 100644 --- a/animator/src/plugin.rs +++ b/animator/src/plugin.rs @@ -1,109 +1,11 @@ -use std::{ - collections::HashMap, - error::Error, - path::PathBuf, - process::{Command, Stdio}, -}; +use std::{collections::HashMap, error::Error}; use animation_api::{ schema::{Configuration, ConfigurationSchema, ParameterValue}, AnimationError, }; +use animation_wrapper::config::PluginConfig; use async_trait::async_trait; -use serde::Deserialize; - -#[derive(Debug, thiserror::Error)] -pub enum PluginConfigError { - #[error("Failed to parse manifest: {reason}")] - InvalidManifest { reason: String }, - - #[error("Directory containing plugin has non UTF-8 name")] - NonUtf8DirectoryName, -} - -#[derive(Debug, Clone, Copy, Default, PartialEq, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum PluginType { - #[default] - Native, - Wasm, -} - -#[derive(Debug, Clone, Deserialize)] -pub struct PluginManifest { - display_name: String, - #[serde(default)] - plugin_type: PluginType, -} - -#[derive(Debug, Clone)] -pub struct PluginConfig { - animation_id: String, - manifest: PluginManifest, - path: PathBuf, -} - -impl PluginConfig { - pub fn new(path: PathBuf) -> Result { - let manifest: PluginManifest = - serde_json::from_slice(&std::fs::read(path.join("manifest.json")).map_err(|e| { - PluginConfigError::InvalidManifest { - reason: format!("IO error: {}", e), - } - })?) - .map_err(|e| PluginConfigError::InvalidManifest { - reason: e.to_string(), - })?; - - let animation_id = path - .file_name() - .unwrap() - .to_str() - .ok_or(PluginConfigError::NonUtf8DirectoryName)? - .to_owned(); - - Ok(Self { - animation_id, - manifest, - path, - }) - } - - pub fn animation_id(&self) -> &str { - &self.animation_id - } - - pub fn animation_name(&self) -> &str { - &self.manifest.display_name - } - - pub fn plugin_type(&self) -> PluginType { - self.manifest.plugin_type - } - - pub fn executable_path(&self) -> PathBuf { - let executable_name = if self.manifest.plugin_type == PluginType::Wasm { - "plugin.wasm" - } else if cfg!(windows) { - "plugin.exe" - } else { - "plugin" - }; - - self.path.join(executable_name) - } - - pub fn is_executable(&self) -> bool { - match self.manifest.plugin_type { - PluginType::Native => Command::new(self.executable_path()) - .stdout(Stdio::null()) - .stdin(Stdio::null()) - .spawn() - .is_ok(), - PluginType::Wasm => self.executable_path().exists(), - } - } -} #[derive(Debug, thiserror::Error)] pub enum AnimationPluginError { diff --git a/animator/src/wasm.rs b/animator/src/wasm.rs index bab2177..13e967f 100644 --- a/animator/src/wasm.rs +++ b/animator/src/wasm.rs @@ -2,10 +2,11 @@ use std::collections::HashMap; use animation_api::{event::Event, schema}; use animation_wasm_bindings::host::HostedPlugin; +use animation_wrapper::{config::PluginConfig, unwrap}; use async_trait::async_trait; use tokio::sync::Mutex; -use crate::plugin::{AnimationPluginError, Plugin, PluginConfig}; +use crate::plugin::{AnimationPluginError, Plugin}; pub struct WasmPlugin { inner: Mutex, @@ -17,12 +18,22 @@ impl WasmPlugin { config: PluginConfig, points: Vec<(f64, f64, f64)>, ) -> Result { + let plugin = if config + .path() + .extension() + .and_then(|ext| ext.to_str()) + .is_some_and(|ext| ext == "crab") + { + let reader = unwrap::reader_from_crab(&config.path()) + .map_err(|e| AnimationPluginError::CommunicationError(Box::new(e)))?; + HostedPlugin::from_reader(reader, points).await + } else { + HostedPlugin::new(&config.executable_path(), points).await + } + .map_err(|e| AnimationPluginError::CommunicationError(Box::new(e)))?; + Ok(Self { - inner: Mutex::new( - HostedPlugin::new(&config.executable_path(), points) - .await - .map_err(|e| AnimationPluginError::CommunicationError(Box::new(e)))?, - ), + inner: Mutex::new(plugin), plugin_config: config, }) }