diff --git a/Cargo.lock b/Cargo.lock index 0099ea7..6e2f00f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -540,18 +540,7 @@ dependencies = [ ] [[package]] -name = "animation-template-native" -version = "0.1.0" -dependencies = [ - "animation-api 0.1.0 (git+https://github.com/mrozycki/rustmas?rev=835ad9e)", - "animation-utils 0.1.0 (git+https://github.com/mrozycki/rustmas?rev=835ad9e)", - "lightfx 0.1.0 (git+https://github.com/mrozycki/rustmas?rev=835ad9e)", - "serde", - "serde_json", -] - -[[package]] -name = "animation-template-wasm" +name = "animation-template" version = "0.1.0" dependencies = [ "animation-api 0.1.0 (git+https://github.com/mrozycki/rustmas?rev=835ad9e)", @@ -594,6 +583,7 @@ name = "animation-wasm-bindings" version = "0.1.0" dependencies = [ "animation-api 0.1.0", + "animation-wrapper", "itertools 0.13.0", "lightfx 0.1.0", "log", @@ -626,6 +616,7 @@ dependencies = [ name = "animation-wrapper" version = "0.1.0" dependencies = [ + "animation-api 0.1.0", "serde", "serde_json", "tar", @@ -6978,6 +6969,7 @@ dependencies = [ "actix-multipart", "actix-web", "actix-web-actors", + "animation-api 0.1.0", "animation-wrapper", "anyhow", "async-stream", diff --git a/Cargo.toml b/Cargo.toml index 97fadb3..dfee617 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,8 +2,7 @@ members = [ "animation-api", "animation-macros", - "animation-template-native", - "animation-template-wasm", + "animation-template", "animation-utils", "animation-wasm-bindings", "animation-wrapper", diff --git a/animation-api/src/api.rs b/animation-api/src/api.rs index 8bb2942..a24b8e8 100644 --- a/animation-api/src/api.rs +++ b/animation-api/src/api.rs @@ -1,8 +1,6 @@ -use std::collections::HashMap; - use serde::{Deserialize, Serialize, de::DeserializeOwned}; -use crate::schema::{ConfigurationSchema, GetEnumOptions, GetSchema, ParameterValue}; +use crate::schema::{ConfigurationSchema, GetEnumOptions, GetSchema}; #[derive(Serialize, Deserialize, Debug, thiserror::Error)] #[error("animation error: {message}")] @@ -50,24 +48,3 @@ pub trait Animation { fn render(&self) -> lightfx::Frame; } - -#[derive(Serialize, Deserialize)] -#[serde(tag = "method", content = "params")] -pub enum JsonRpcMethod { - Initialize { - points: Vec<(f64, f64, f64)>, - }, - ParameterSchema, - SetParameters { - params: HashMap, - }, - GetParameters, - GetFps, - Update { - time_delta: f64, - }, - OnEvent { - event: crate::event::Event, - }, - Render, -} diff --git a/animation-api/src/lib.rs b/animation-api/src/lib.rs index 2cb260b..c830d82 100644 --- a/animation-api/src/lib.rs +++ b/animation-api/src/lib.rs @@ -1,14 +1,7 @@ mod api; pub mod event; -mod msg; +pub mod plugin_config; pub mod schema; pub use api::Animation; pub use api::AnimationError; -pub use api::JsonRpcMethod; - -pub use msg::ErrorType; -pub use msg::JsonRpcError; -pub use msg::JsonRpcMessage; -pub use msg::JsonRpcResponse; -pub use msg::JsonRpcResult; diff --git a/animation-api/src/msg.rs b/animation-api/src/msg.rs deleted file mode 100644 index 8ee9498..0000000 --- a/animation-api/src/msg.rs +++ /dev/null @@ -1,42 +0,0 @@ -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize)] -pub struct JsonRpcMessage { - pub id: Option, - - #[serde(flatten)] - pub payload: T, -} - -#[derive(Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub enum JsonRpcResult { - Result(T), - Error(JsonRpcError), -} - -#[derive(Serialize, Deserialize)] -pub struct JsonRpcResponse { - pub id: usize, - #[serde(flatten)] - pub result: JsonRpcResult, -} - -#[repr(i16)] -#[non_exhaustive] -#[derive(Serialize, Deserialize, Debug)] -pub enum ErrorType { - ParseError = -32700, - InvalidRequest = -32600, - MethodNotFound = -32601, - InvalidParams = -32602, - InternalError = -32603, - AnimationError = -32000, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct JsonRpcError { - pub code: ErrorType, - pub message: String, - pub data: T, -} diff --git a/animation-api/src/plugin_config.rs b/animation-api/src/plugin_config.rs new file mode 100644 index 0000000..b00475a --- /dev/null +++ b/animation-api/src/plugin_config.rs @@ -0,0 +1,25 @@ +use std::path::PathBuf; + +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] +pub enum PluginApiVersion { + #[serde(rename = "0.9")] + V0_9, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PluginManifest { + pub id: String, + pub display_name: String, + pub author: String, + pub api_version: PluginApiVersion, + pub version: String, + pub tags: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct PluginConfig { + pub manifest: PluginManifest, + pub path: PathBuf, +} diff --git a/animation-macros/src/lib.rs b/animation-macros/src/lib.rs index dfe173c..67b6887 100644 --- a/animation-macros/src/lib.rs +++ b/animation-macros/src/lib.rs @@ -19,140 +19,6 @@ fn generate_wasm_plugin(ast: syn::ItemStruct) -> proc_macro2::TokenStream { } } -#[proc_macro_attribute] -pub fn plugin(_attr: TokenStream, item: TokenStream) -> TokenStream { - let ast: syn::ItemStruct = syn::parse2(item.into()).unwrap(); - generate_native_plugin(ast).into() -} - -fn generate_native_plugin(ast: syn::ItemStruct) -> proc_macro2::TokenStream { - let name = &ast.ident; - quote! { - #ast - - fn main() -> std::result::Result<(), Box> { - use std::{ - error::Error, - io::{BufRead, BufReader}, - }; - - use animation_api::{JsonRpcMessage, JsonRpcMethod, JsonRpcError, ErrorType, AnimationError}; - use serde::Serialize; - use serde_json::json; - - fn receive( - reader: &mut impl BufRead, - ) -> Result>, Box> { - let mut buffer = String::new(); - if reader.read_line(&mut buffer)? == 0 { - Ok(None) - } else { - Ok(Some(serde_json::from_str(&buffer)?)) - } - } - - fn respond(id: Option, payload: T) - where - T: Serialize, - { - let Some(id) = id else { return; }; - - println!( - "{}", - json!({ - "id": id, - "result": payload, - }) - ); - } - - fn error(id: Option, message: String) - { - let Some(id) = id else { return; }; - - println!( - "{}", - json!({ - "id": id, - "error": JsonRpcError { - code: ErrorType::AnimationError, - message: "Animation Error".into(), - data: AnimationError { - message - } - }, - }) - ); - } - - let mut animation = None; - let mut stdin = BufReader::new(std::io::stdin()); - - loop { - match receive(&mut stdin) { - Ok(Some(message)) => match message.payload { - JsonRpcMethod::Initialize { points } => { - animation = None; - animation = Some(<#name>::new_wrapped(points)); - respond(message.id, ()); - } - JsonRpcMethod::ParameterSchema => { - if let Some(animation) = animation.as_ref() { - respond(message.id, animation.get_schema()); - } - }, - JsonRpcMethod::SetParameters { params } => { - if let Some(mut animation) = animation.as_mut() { - match serde_json::from_value(serde_json::json!(params)) { - Ok(params) => { - animation.set_parameters(params); - respond(message.id, ()); - } - Err(e) => error(message.id, e.to_string()) - } - } - }, - JsonRpcMethod::GetParameters => { - if let Some(animation) = animation.as_ref() { - respond(message.id, serde_json::json!(animation.get_parameters())); - } - }, - JsonRpcMethod::GetFps => { - if let Some(animation) = animation.as_ref() { - respond(message.id, animation.get_fps()); - } - }, - JsonRpcMethod::Update { time_delta } => { - if let Some(mut animation) = animation.as_mut() { - animation.update(time_delta); - } - }, - JsonRpcMethod::OnEvent { event } => { - if let Some(mut animation) = animation.as_mut() { - animation.on_event(event); - } - respond(message.id, ()); - }, - JsonRpcMethod::Render => { - if let Some(animation) = animation.as_ref() { - respond(message.id, animation.render()); - } - }, - }, - Ok(None) => { - break; - } - Err(err) => { - eprintln!("Animation error: {:?}", err); - break; - } - } - } - Ok(()) - } - } -} - #[derive(Debug, Clone, FromMeta)] struct Number { min: f64, diff --git a/animation-template-native/Cargo.toml b/animation-template-native/Cargo.toml deleted file mode 100644 index 806adb7..0000000 --- a/animation-template-native/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "animation-template-native" -version = "0.1.0" -edition = "2024" - -[dependencies] -animation-utils = { git = "https://github.com/mrozycki/rustmas", rev = "835ad9e" } -animation-api = { git = "https://github.com/mrozycki/rustmas", rev = "835ad9e" } -lightfx = { git = "https://github.com/mrozycki/rustmas", rev = "835ad9e" } - -serde = { version = "1.0.152", features = ["derive"] } -serde_json = "1.0.93" diff --git a/animation-template-native/README.md b/animation-template-native/README.md deleted file mode 100644 index 209f4c3..0000000 --- a/animation-template-native/README.md +++ /dev/null @@ -1,21 +0,0 @@ -Animation template (Native) -=========================== - -This is an animation template for a native animation plugin. It will compile -to native machine code, which will have to be recompiled for any new target -architecture you want to run it on. While this is an older, and probably more -stable, solution, for new animations we recommend using the Wasm plugin template. - -Make a clone of this crate to create your own animation. Everything you -need is provided for you. For a simple animation, you will need to -provide your own implementation of the `new`, `update`, and `render`. - -Optionally, you can add support for parameters by adding your own parameters -to the `Parameters` structure. You might add extra logic to `set_parameters` -if you need to do any pre-processing on new parameter values. For some common -paramters, like speed or brightness, you can specify a `Wrapped` type with -appropriate decorators. - -You can also handle events by providing your implementation of the `on_event` -method. - diff --git a/animation-template-native/manifest.json b/animation-template-native/manifest.json deleted file mode 100644 index 2ac6ebd..0000000 --- a/animation-template-native/manifest.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "id": "io.rustmas.template.native", - "display_name": "Animation Template (Native)", - "plugin_type": "native", - "author": "Mariusz Różycki ", - "api_version": "0.9", - "version": "1.0", - "tags": [ - "2d", - "3d", - "audio" - ] -} \ No newline at end of file diff --git a/animation-template-native/src/main.rs b/animation-template-native/src/main.rs deleted file mode 100644 index 3e3e4b9..0000000 --- a/animation-template-native/src/main.rs +++ /dev/null @@ -1,90 +0,0 @@ -use animation_api::{Animation, event::Event}; -use animation_utils::{ - Schema, - decorators::{BrightnessControlled, SpeedControlled}, -}; -use serde::{Deserialize, Serialize}; -use serde_json::json; - -#[derive(Clone, Serialize, Deserialize, Schema)] -pub struct Parameters { - // TODO: Define your animation's parameters - #[schema_field(name = "Tail length", number(min = 1.0, max = 10.0, step = 1.0))] - tail_length: usize, -} - -impl Default for Parameters { - fn default() -> Self { - Self { tail_length: 3 } - } -} - -#[animation_utils::plugin] -pub struct MyAnimation { - // TODO: Define your animation's state - points_count: usize, - time: f64, - parameters: Parameters, -} - -impl Animation for MyAnimation { - type Parameters = Parameters; - type Wrapped = SpeedControlled>; - - fn new(points: Vec<(f64, f64, f64)>) -> Self { - // TODO: Initialize animation state from a set of light locations - Self { - points_count: points.len(), - time: 0.0, - parameters: Default::default(), - } - } - - fn update(&mut self, time_delta: f64) { - // TODO: Update your animation state by time_delta seconds - self.time += time_delta; - } - - fn on_event(&mut self, event: Event) { - // TODO: React to selected types of events by matching on `event` parameters - // Other event types are available. - match event { - Event::MidiEvent(_midi_msg) => (), - Event::MouseMove { - ray_origin: _, - ray_direction: _, - } => (), - _ => (), - } - } - - fn render(&self) -> lightfx::Frame { - // TODO: Render a frame of your animation - let index = ((self.time * 8.0) % self.points_count as f64) as usize; - - (0..self.points_count) - .map(|i| { - if i + self.parameters.tail_length > index && i <= index { - lightfx::Color::white() - } else { - lightfx::Color::black() - } - }) - .into() - } - - fn get_fps(&self) -> f64 { - // TODO: Return the FPS of your animation - 8.0 - } - - fn get_parameters(&self) -> Self::Parameters { - self.parameters.clone() - } - - fn set_parameters(&mut self, parameters: Self::Parameters) { - // You might need to reset the state of your animation in some cases. - // Otherwise there's nothing to do here. - self.parameters = parameters; - } -} diff --git a/animation-template-wasm/.cargo/config.toml b/animation-template/.cargo/config.toml similarity index 100% rename from animation-template-wasm/.cargo/config.toml rename to animation-template/.cargo/config.toml diff --git a/animation-template-wasm/Cargo.toml b/animation-template/Cargo.toml similarity index 94% rename from animation-template-wasm/Cargo.toml rename to animation-template/Cargo.toml index 1e1c4aa..0e214c1 100644 --- a/animation-template-wasm/Cargo.toml +++ b/animation-template/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "animation-template-wasm" +name = "animation-template" version = "0.1.0" edition = "2024" diff --git a/animation-template-wasm/README.md b/animation-template/README.md similarity index 95% rename from animation-template-wasm/README.md rename to animation-template/README.md index 9e14d61..31a1fbf 100644 --- a/animation-template-wasm/README.md +++ b/animation-template/README.md @@ -1,5 +1,5 @@ -Animation template (Wasm) -========================= +Animation template +================== This is an animation template for a Wasm animation plugin. It will produce a cross-platform plugin, which can be compiled once and then used on any diff --git a/animation-template-wasm/manifest.json b/animation-template/manifest.json similarity index 60% rename from animation-template-wasm/manifest.json rename to animation-template/manifest.json index 3cae5b7..91b300e 100644 --- a/animation-template-wasm/manifest.json +++ b/animation-template/manifest.json @@ -1,7 +1,6 @@ { - "id": "io.rustmas.template.wasm", - "display_name": "Animation Template (Wasm)", - "plugin_type": "wasm", + "id": "io.rustmas.template", + "display_name": "Animation Template", "author": "Mariusz Różycki ", "api_version": "0.9", "version": "1.0", diff --git a/animation-template-wasm/src/lib.rs b/animation-template/src/lib.rs similarity index 100% rename from animation-template-wasm/src/lib.rs rename to animation-template/src/lib.rs diff --git a/animation-utils/src/decorators/brightness_controlled.rs b/animation-utils/src/decorators/brightness_controlled.rs index 5fe4195..de582b3 100644 --- a/animation-utils/src/decorators/brightness_controlled.rs +++ b/animation-utils/src/decorators/brightness_controlled.rs @@ -61,7 +61,7 @@ where .render() .pixels_iter() .map(|x| x.dim(self.parameters.brightness_factor)) - .into() + .collect() } fn set_parameters(&mut self, parameters: Self::Parameters) { diff --git a/animation-utils/src/decorators/off_switch.rs b/animation-utils/src/decorators/off_switch.rs index eab0365..bcdc3b2 100644 --- a/animation-utils/src/decorators/off_switch.rs +++ b/animation-utils/src/decorators/off_switch.rs @@ -102,7 +102,7 @@ where .render() .pixels_iter() .map(|x| x.dim(self.energy)) - .into() + .collect() } fn set_parameters(&mut self, parameters: Self::Parameters) { diff --git a/animation-utils/src/lib.rs b/animation-utils/src/lib.rs index 3d5d4eb..64383e5 100644 --- a/animation-utils/src/lib.rs +++ b/animation-utils/src/lib.rs @@ -2,7 +2,7 @@ pub mod decorators; use std::f64::consts::TAU; -pub use animation_macros::{EnumSchema, Schema, plugin, wasm_plugin}; +pub use animation_macros::{EnumSchema, Schema, wasm_plugin}; use nalgebra::{Rotation3, Unit, Vector3}; use rand::Rng; diff --git a/animation-wasm-bindings/Cargo.toml b/animation-wasm-bindings/Cargo.toml index 1f7dbde..ba82f5d 100644 --- a/animation-wasm-bindings/Cargo.toml +++ b/animation-wasm-bindings/Cargo.toml @@ -5,6 +5,7 @@ edition = "2024" [dependencies] animation-api = { path = "../animation-api" } +animation-wrapper = { path = "../animation-wrapper" } lightfx = { path = "../lightfx" } itertools = "0.13.0" diff --git a/animation-wasm-bindings/src/host.rs b/animation-wasm-bindings/src/host.rs index 92265f9..62ee74d 100644 --- a/animation-wasm-bindings/src/host.rs +++ b/animation-wasm-bindings/src/host.rs @@ -1,12 +1,8 @@ -use std::{ - collections::HashMap, - fs::File, - io::{BufReader, Read}, - path::Path, -}; +use std::{collections::HashMap, io::Read, path::Path}; -use animation_api::{event::Event, schema}; -use exports::guest::animation::plugin::{Color, Position}; +use animation_api::{event::Event, plugin_config::PluginManifest, schema}; +use animation_wrapper::unwrap::{self, PluginUnwrapError}; +use exports::guest::animation::plugin::Position; use itertools::Itertools; use tokio::sync::Mutex; use wasmtime::{ @@ -40,28 +36,30 @@ impl IoView for State { } #[derive(Debug, thiserror::Error)] -pub enum HostedPluginError { +pub enum AnimationPluginError { #[error("wasmtime returned error: {0}")] WasmtimeError(#[from] wasmtime::Error), #[error("cannot open plugin: {0}")] PluginOpenError(#[from] std::io::Error), + + #[error("bundle error: {0}")] + BundleError(#[from] PluginUnwrapError), } -type Result = std::result::Result; +type Result = std::result::Result; -pub struct HostedPlugin { +pub struct AnimationPlugin { store: Mutex>, bindings: Animation, handle: ResourceAny, + manifest: PluginManifest, } -impl HostedPlugin { +impl AnimationPlugin { 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 - } + let manifest = unwrap::unwrap_plugin(executable_path)?; - pub async fn from_reader(mut reader: R, points: Vec<(f64, f64, f64)>) -> Result { + let mut reader = unwrap::reader_from_crab(executable_path)?; let mut data = Vec::new(); reader.read_to_end(&mut data)?; @@ -99,6 +97,20 @@ impl HostedPlugin { store: Mutex::new(store), bindings, handle, + manifest, + }) + } + + pub fn manifest(&self) -> &PluginManifest { + &self.manifest + } + + pub async fn configuration(&self) -> Result { + Ok(schema::Configuration { + id: self.manifest.id.to_owned(), + name: self.manifest.display_name.to_owned(), + schema: self.get_schema().await?, + values: self.get_parameters().await?, }) } @@ -112,7 +124,7 @@ impl HostedPlugin { .map_err(Into::into) } - pub async fn render(&self) -> Result> { + pub async fn render(&self) -> Result { let mut store = self.store.lock().await; self.bindings .guest_animation_plugin() @@ -120,6 +132,12 @@ impl HostedPlugin { .call_render(store.as_context_mut(), self.handle) .await .map_err(Into::into) + .map(|pixels| { + pixels + .into_iter() + .map(|c| lightfx::Color::rgb(c.r, c.g, c.b)) + .collect() + }) } pub async fn get_schema(&self) -> Result { diff --git a/animation-wrapper/Cargo.toml b/animation-wrapper/Cargo.toml index a153576..57b9d17 100644 --- a/animation-wrapper/Cargo.toml +++ b/animation-wrapper/Cargo.toml @@ -4,6 +4,8 @@ version = "0.1.0" edition = "2024" [dependencies] +animation-api = { path = "../animation-api" } + serde = { version = "1.0.215", features = ["derive"] } serde_json = "1.0.133" tar = "0.4.43" diff --git a/animation-wrapper/src/config.rs b/animation-wrapper/src/config.rs deleted file mode 100644 index 0c749c2..0000000 --- a/animation-wrapper/src/config.rs +++ /dev/null @@ -1,107 +0,0 @@ -use std::{ - path::{Path, PathBuf}, - process::{Command, Stdio}, -}; - -use serde::{Deserialize, Serialize}; - -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, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum PluginType { - #[default] - Native, - Wasm, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -pub enum PluginApiVersion { - #[serde(rename = "0.9")] - V0_9, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct PluginManifest { - pub id: String, - pub display_name: String, - pub author: String, - #[serde(default)] - pub plugin_type: PluginType, - pub api_version: PluginApiVersion, - pub version: String, - pub tags: Vec, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub struct PluginConfig { - pub animation_id: String, - pub manifest: PluginManifest, - pub path: PathBuf, -} - -impl PluginConfig { - pub fn from_path(path: &Path) -> 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: path.to_owned(), - }) - } - - pub fn new>(path: P) -> Result { - Self::from_path(path.as_ref()) - } - - 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 index 54fbf76..bb39f72 100644 --- a/animation-wrapper/src/lib.rs +++ b/animation-wrapper/src/lib.rs @@ -1,7 +1,17 @@ -pub mod config; - #[cfg(feature = "wrap")] pub mod wrap; #[cfg(feature = "unwrap")] pub mod unwrap; + +#[derive(Debug, thiserror::Error)] +pub enum PluginConfigError { + #[error("Failed to parse manifest: {reason}")] + InvalidManifest { reason: String }, + + #[error("Failed to unwrap plugin")] + InvalidCrab(#[from] unwrap::PluginUnwrapError), + + #[error("Directory containing plugin has non UTF-8 name")] + NonUtf8DirectoryName, +} diff --git a/animation-wrapper/src/unwrap.rs b/animation-wrapper/src/unwrap.rs index febefc5..1e75778 100644 --- a/animation-wrapper/src/unwrap.rs +++ b/animation-wrapper/src/unwrap.rs @@ -4,10 +4,9 @@ use std::{ path::Path, }; +use animation_api::plugin_config::PluginManifest; use tar::Archive; -use crate::config::PluginManifest; - #[derive(Debug, thiserror::Error)] pub enum PluginUnwrapError { #[error("Cannot open plugin file: {0}")] @@ -47,9 +46,9 @@ pub fn unwrap_plugin>(path: P) -> Result>(path: &P) -> Result { +pub fn reader_from_crab>(path: P) -> Result { let (start, size) = { - let reader = BufReader::new(File::open(path)?); + let reader = BufReader::new(File::open(path.as_ref())?); let mut archive = Archive::new(reader); let mut entries = archive.entries_with_seek()?; @@ -65,7 +64,7 @@ pub fn reader_from_crab>(path: &P) -> Result *Animations* in WebUI. Modifying an existing animation will -require reloading it by either clicking *Reload* in WebUI, or switching -to a different animation and back. \ No newline at end of file +Running this utility will produce a `.crab` file, which can then be installed through the WebUI +by going to Settings, Animations and uploading the file through the form at the top of the list. \ No newline at end of file diff --git a/animations/audio-boom/src/lib.rs b/animations/audio-boom/src/lib.rs index e0f96ed..0bf03bd 100644 --- a/animations/audio-boom/src/lib.rs +++ b/animations/audio-boom/src/lib.rs @@ -116,7 +116,7 @@ impl Animation for AudioBoom { }) .sorted_by(|(i0, _), (i1, _)| i0.cmp(i1)) .map(|(_, c)| c) - .into() + .collect() } fn set_parameters(&mut self, parameters: Self::Parameters) { diff --git a/animations/audio-visualizer/src/lib.rs b/animations/audio-visualizer/src/lib.rs index edbe6a8..10b2067 100644 --- a/animations/audio-visualizer/src/lib.rs +++ b/animations/audio-visualizer/src/lib.rs @@ -100,7 +100,7 @@ impl Animation for AudioVisualizer { Color::black() } }) - .into() + .collect() } fn set_parameters(&mut self, parameters: Self::Parameters) { diff --git a/animations/audio-wave/src/lib.rs b/animations/audio-wave/src/lib.rs index c0942c4..2e09255 100644 --- a/animations/audio-wave/src/lib.rs +++ b/animations/audio-wave/src/lib.rs @@ -103,7 +103,7 @@ impl Animation for AudioWave { Color::black() } }) - .into() + .collect() } fn on_event(&mut self, _event: animation_api::event::Event) { diff --git a/animations/barber-pole/src/lib.rs b/animations/barber-pole/src/lib.rs index 5a20943..ba5c255 100644 --- a/animations/barber-pole/src/lib.rs +++ b/animations/barber-pole/src/lib.rs @@ -81,7 +81,7 @@ impl Animation for BarberPole { ) } }) - .into() + .collect() } fn set_parameters(&mut self, parameters: Self::Parameters) { diff --git a/animations/beats/src/lib.rs b/animations/beats/src/lib.rs index f0f450d..839f7b3 100644 --- a/animations/beats/src/lib.rs +++ b/animations/beats/src/lib.rs @@ -106,7 +106,7 @@ impl Animation for Beats { c.dim(self.parameters.base_dim) } }) - .into() + .collect() } fn set_parameters(&mut self, parameters: Self::Parameters) { diff --git a/animations/circle-boom/src/lib.rs b/animations/circle-boom/src/lib.rs index faca954..3b44447 100644 --- a/animations/circle-boom/src/lib.rs +++ b/animations/circle-boom/src/lib.rs @@ -79,7 +79,7 @@ impl Animation for CircleBoom { lightfx::Color::black() } }) - .into() + .collect() } fn set_parameters(&mut self, parameters: Self::Parameters) { diff --git a/animations/circle-grid/src/lib.rs b/animations/circle-grid/src/lib.rs index d9c7157..d594662 100644 --- a/animations/circle-grid/src/lib.rs +++ b/animations/circle-grid/src/lib.rs @@ -121,7 +121,7 @@ impl Animation for CircleGrid { Color::black() } }) - .into() + .collect() } fn set_parameters(&mut self, parameters: Self::Parameters) { diff --git a/animations/circle-wave/src/lib.rs b/animations/circle-wave/src/lib.rs index e9fbc24..a34275c 100644 --- a/animations/circle-wave/src/lib.rs +++ b/animations/circle-wave/src/lib.rs @@ -95,7 +95,7 @@ impl Animation for CircleWave { .dim((((self.time - d) * TAU / self.parameters.radius).sin() + 1.0) / 2.0) } }) - .into() + .collect() } fn set_parameters(&mut self, parameters: Self::Parameters) { diff --git a/animations/classic/src/lib.rs b/animations/classic/src/lib.rs index bde4e9c..e5b5c5c 100644 --- a/animations/classic/src/lib.rs +++ b/animations/classic/src/lib.rs @@ -99,7 +99,7 @@ impl Animation for Classic { base.into_iter() .zip(mask) .map(|(color, level)| color.dim(level)) - .into() + .collect() } fn get_parameters(&self) -> Self::Parameters { diff --git a/animations/doom-fire/src/lib.rs b/animations/doom-fire/src/lib.rs index c258fd8..8181985 100644 --- a/animations/doom-fire/src/lib.rs +++ b/animations/doom-fire/src/lib.rs @@ -159,7 +159,7 @@ impl Animation for DoomFireAnimation { .iter() .map(|v| rotation * v) .map(|v| self.fire.sample(v.x, v.y)) - .into() + .collect() } fn get_parameters(&self) -> Self::Parameters { diff --git a/animations/heartbeat/src/lib.rs b/animations/heartbeat/src/lib.rs index 1af9e91..03bdd45 100644 --- a/animations/heartbeat/src/lib.rs +++ b/animations/heartbeat/src/lib.rs @@ -94,7 +94,7 @@ impl Animation for Heartbeat { Color::black() } }) - .into() + .collect() } fn set_parameters(&mut self, parameters: Self::Parameters) { diff --git a/animations/lightspeed/src/lib.rs b/animations/lightspeed/src/lib.rs index bfa7ec3..41852dd 100644 --- a/animations/lightspeed/src/lib.rs +++ b/animations/lightspeed/src/lib.rs @@ -166,7 +166,7 @@ impl Animation for Lightspeed { .apply_alpha() .dim((x.powi(2) + y.powi(2)).sqrt() / self.parameters.dim_distance) }) - .into() + .collect() } fn get_parameters(&self) -> Self::Parameters { diff --git a/animations/midi-wave/src/lib.rs b/animations/midi-wave/src/lib.rs index 093abe2..14f9e21 100644 --- a/animations/midi-wave/src/lib.rs +++ b/animations/midi-wave/src/lib.rs @@ -102,7 +102,7 @@ impl Animation for MidiWave { }) .sorted_by(|(i0, _), (i1, _)| i0.cmp(i1)) .map(|(_, c)| c) - .into() + .collect() } fn get_parameters(&self) -> Self::Parameters { diff --git a/animations/moon/src/lib.rs b/animations/moon/src/lib.rs index d40b2b3..6eca8d7 100644 --- a/animations/moon/src/lib.rs +++ b/animations/moon/src/lib.rs @@ -100,7 +100,7 @@ impl Animation for Moon { self.parameters.color.dim(halo_strength * sun_strength) } }) - .into() + .collect() } fn set_parameters(&mut self, parameters: Self::Parameters) { diff --git a/animations/particle-fire/src/lib.rs b/animations/particle-fire/src/lib.rs index 55db971..d7684db 100644 --- a/animations/particle-fire/src/lib.rs +++ b/animations/particle-fire/src/lib.rs @@ -225,7 +225,7 @@ impl Animation for ParticleFire { .apply_alpha() } }) - .into() + .collect() } fn get_parameters(&self) -> Self::Parameters { diff --git a/animations/pillars/src/lib.rs b/animations/pillars/src/lib.rs index cb254c2..f7b9bad 100644 --- a/animations/pillars/src/lib.rs +++ b/animations/pillars/src/lib.rs @@ -213,7 +213,7 @@ impl Animation for Pillars { ) .apply_alpha() }) - .into() + .collect() } fn get_parameters(&self) -> Self::Parameters { diff --git a/animations/present/src/lib.rs b/animations/present/src/lib.rs index a43f30b..69c2bae 100644 --- a/animations/present/src/lib.rs +++ b/animations/present/src/lib.rs @@ -85,7 +85,7 @@ impl Animation for Present { self.parameters.color_wrap } }) - .into() + .collect() } fn set_parameters(&mut self, parameters: Self::Parameters) { diff --git a/animations/rainbow-cable/src/lib.rs b/animations/rainbow-cable/src/lib.rs index a1e4cbb..daf9573 100644 --- a/animations/rainbow-cable/src/lib.rs +++ b/animations/rainbow-cable/src/lib.rs @@ -48,7 +48,7 @@ impl Animation for RainbowCable { 1.0, ) }) - .into() + .collect() } fn get_parameters(&self) -> Self::Parameters { diff --git a/animations/rainbow-cylinder/src/lib.rs b/animations/rainbow-cylinder/src/lib.rs index 7e0c8f1..86dcdd2 100644 --- a/animations/rainbow-cylinder/src/lib.rs +++ b/animations/rainbow-cylinder/src/lib.rs @@ -53,7 +53,7 @@ impl Animation for RainbowCylinder { 1.0, ) }) - .into() + .collect() } fn get_parameters(&self) -> Self::Parameters { diff --git a/animations/rainbow-halves/src/lib.rs b/animations/rainbow-halves/src/lib.rs index ed28b63..b313b5a 100644 --- a/animations/rainbow-halves/src/lib.rs +++ b/animations/rainbow-halves/src/lib.rs @@ -72,7 +72,7 @@ impl Animation for RainbowHalves { .clamp(0.0, 1.0), ) }) - .into() + .collect() } fn set_parameters(&mut self, parameters: Self::Parameters) { diff --git a/animations/rainbow-sphere/src/lib.rs b/animations/rainbow-sphere/src/lib.rs index 830bebb..73aa43f 100644 --- a/animations/rainbow-sphere/src/lib.rs +++ b/animations/rainbow-sphere/src/lib.rs @@ -67,7 +67,7 @@ impl Animation for RainbowSphere { self.points_radius .iter() .map(|r| lightfx::Color::hsv(r / 2.0 * self.parameters.density + self.time, 1.0, 1.0)) - .into() + .collect() } fn set_parameters(&mut self, parameters: Parameters) { diff --git a/animations/rainbow-spiral/src/lib.rs b/animations/rainbow-spiral/src/lib.rs index 6019f04..063d598 100644 --- a/animations/rainbow-spiral/src/lib.rs +++ b/animations/rainbow-spiral/src/lib.rs @@ -51,7 +51,7 @@ impl Animation for RainbowSpiral { 1.0, ) }) - .into() + .collect() } fn get_parameters(&self) -> Self::Parameters { diff --git a/animations/rainbow-waterfall/src/lib.rs b/animations/rainbow-waterfall/src/lib.rs index 81bd100..77f5a2e 100644 --- a/animations/rainbow-waterfall/src/lib.rs +++ b/animations/rainbow-waterfall/src/lib.rs @@ -46,7 +46,7 @@ impl Animation for RainbowWaterfall { self.points_height .iter() .map(|h| lightfx::Color::hsv(h * self.parameters.density + self.time, 1.0, 1.0)) - .into() + .collect() } fn set_parameters(&mut self, parameters: Self::Parameters) { diff --git a/animations/random-sweep/src/lib.rs b/animations/random-sweep/src/lib.rs index 4177a56..6890120 100644 --- a/animations/random-sweep/src/lib.rs +++ b/animations/random-sweep/src/lib.rs @@ -120,7 +120,7 @@ impl Animation for RandomSweep { lightfx::Color::black() } }) - .into() + .collect() } fn set_parameters(&mut self, parameters: Self::Parameters) { diff --git a/animations/random-wipe/src/lib.rs b/animations/random-wipe/src/lib.rs index 2713eed..f97debf 100644 --- a/animations/random-wipe/src/lib.rs +++ b/animations/random-wipe/src/lib.rs @@ -148,7 +148,7 @@ impl Animation for RandomSweep { lightfx::Color::black() } }) - .into() + .collect() } fn set_parameters(&mut self, parameters: Self::Parameters) { diff --git a/animations/spinning-halves/src/lib.rs b/animations/spinning-halves/src/lib.rs index 309be24..a664ff9 100644 --- a/animations/spinning-halves/src/lib.rs +++ b/animations/spinning-halves/src/lib.rs @@ -87,7 +87,7 @@ impl Animation for SpinningHalves { .clamp(0.0, 1.0), ) }) - .into() + .collect() } fn set_parameters(&mut self, parameters: Self::Parameters) { diff --git a/animations/test-indexing/src/lib.rs b/animations/test-indexing/src/lib.rs index 5e1142b..311be18 100644 --- a/animations/test-indexing/src/lib.rs +++ b/animations/test-indexing/src/lib.rs @@ -51,7 +51,7 @@ impl Animation for Indexing { 0 => lightfx::Color::black(), _ => lightfx::Color::white(), }) - .into() + .collect() } fn get_fps(&self) -> f64 { diff --git a/animations/test-manual-sweep/src/lib.rs b/animations/test-manual-sweep/src/lib.rs index 20b6217..df0a6b3 100644 --- a/animations/test-manual-sweep/src/lib.rs +++ b/animations/test-manual-sweep/src/lib.rs @@ -124,7 +124,7 @@ impl Animation for ManualSweep { lightfx::Color::black() } }) - .into() + .collect() } fn get_fps(&self) -> f64 { diff --git a/animations/waterfall/src/lib.rs b/animations/waterfall/src/lib.rs index c745060..7f2eb26 100644 --- a/animations/waterfall/src/lib.rs +++ b/animations/waterfall/src/lib.rs @@ -66,7 +66,7 @@ impl Animation for RainbowWaterfall { .blend(&self.parameters.color_b.with_alpha(1.0)) .apply_alpha() }) - .into() + .collect() } fn set_parameters(&mut self, parameters: Self::Parameters) { diff --git a/animator/src/controller.rs b/animator/src/controller.rs index 2908041..b7eb743 100644 --- a/animator/src/controller.rs +++ b/animator/src/controller.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use animation_api::event::Event; use animation_api::schema::{Configuration, ConfigurationSchema, ParameterValue}; -use animation_wrapper::config::PluginConfig; +use animation_wasm_bindings::host::{AnimationPlugin, AnimationPluginError}; use chrono::{DateTime, Duration, Utc}; use client::combined::CombinedLightClient; #[cfg(feature = "audio")] @@ -23,7 +23,6 @@ use tokio::task::JoinHandle; use crate::ControllerConfig; use crate::factory::AnimationFactoryError; -use crate::plugin::{AnimationPluginError, Plugin}; #[derive(Debug, thiserror::Error)] pub enum ControllerError { @@ -41,7 +40,7 @@ pub enum ControllerError { } struct ControllerState { - animation: Option>, + animation: Option, last_frame: DateTime, next_frame: DateTime, fps: f64, @@ -51,7 +50,7 @@ struct ControllerState { impl ControllerState { async fn set_animation( &mut self, - animation: Option>, + animation: Option, ) -> Result<(), ControllerError> { let now = Utc::now(); self.fps = if let Some(animation) = &animation { @@ -106,13 +105,13 @@ impl Controller { } } - pub async fn current_animation(&self) -> Option { + pub async fn current_animation_id(&self) -> Option { self.state .lock() .await .animation .as_ref() - .map(|animation| animation.plugin_config().clone()) + .map(|animation| animation.manifest().id.clone()) } #[allow(unused_variables)] @@ -290,7 +289,7 @@ impl Controller { pub async fn switch_animation( &self, - animation: Box, + animation: AnimationPlugin, ) -> Result { let configuration = animation.configuration().await?; let mut state = self.state.lock().await; diff --git a/animator/src/factory.rs b/animator/src/factory.rs index 23f5a20..cabc643 100644 --- a/animator/src/factory.rs +++ b/animator/src/factory.rs @@ -3,19 +3,13 @@ use std::{ path::{Path, PathBuf}, }; -use animation_wrapper::{ - config::{PluginConfig, PluginConfigError, PluginType}, - unwrap, -}; +use animation_api::plugin_config::PluginConfig; +use animation_wasm_bindings::host::{AnimationPlugin, AnimationPluginError}; +use animation_wrapper::{PluginConfigError, unwrap}; use itertools::Itertools; -use log::{info, warn}; +use log::info; -use crate::{ - ControllerConfig, - jsonrpc::JsonRpcPlugin, - plugin::{AnimationPluginError, Plugin}, - wasm::WasmPlugin, -}; +use crate::ControllerConfig; #[derive(Debug, thiserror::Error)] pub enum AnimationFactoryError { @@ -67,40 +61,7 @@ impl AnimationFactory { } pub fn discover(&self) -> Result, AnimationFactoryError> { - let (mut valid_plugins, invalid_plugins) = glob::glob(&format!( - "{}/*/manifest.json", - self.plugin_dir - .to_str() - .ok_or(AnimationFactoryError::InternalError { - reason: "Plugins directory path is not valid UTF-8".into() - })? - )) - .map_err(|e| AnimationFactoryError::InternalError { - reason: format!("Pattern error: {e}"), - })? - .map(|path| { - path.map_err(|e| AnimationFactoryError::InternalError { - reason: format!("Glob error: {e}"), - }) - .and_then(|path| -> Result<_, AnimationFactoryError> { - let path = path.parent().unwrap().to_owned(); - let plugin = PluginConfig::new(path)?; - let id = plugin.animation_id.clone(); - Ok((id, plugin)) - }) - }) - .collect::, _>>()? - .into_iter() - .partition::, _>(|(_id, plugin)| plugin.is_executable()); - - if !invalid_plugins.is_empty() { - 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 + let plugins = self .plugin_dir .read_dir() .map_err(|e| AnimationFactoryError::InternalError { @@ -109,51 +70,17 @@ impl AnimationFactory { .filter_map(|d| d.ok()) .filter(|d| d.file_name().to_str().is_some_and(|d| d.ends_with(".crab"))) .filter_map(|d| Some(d.path().to_owned()).zip(unwrap::unwrap_plugin(d.path()).ok())) - .map(|(path, manifest)| { - ( - manifest.id.clone(), - PluginConfig { - animation_id: manifest.id.clone(), - manifest, - path, - }, - ) - }); - - valid_plugins.extend(crab_plugins); - - Ok(valid_plugins) + .map(|(path, manifest)| (manifest.id.clone(), PluginConfig { manifest, path })) + .collect(); + + Ok(plugins) } pub async fn make_from_path( &self, path: &Path, - ) -> Result, AnimationFactoryError> { - let config = if path - .file_name() - .and_then(|f| f.to_str()) - .is_some_and(|f| f.ends_with(".crab")) - { - let manifest = unwrap::unwrap_plugin(path).map_err(|e| { - AnimationFactoryError::InvalidPlugin(PluginConfigError::InvalidCrab(e)) - })?; - PluginConfig { - animation_id: manifest.id.clone(), - manifest, - path: path.to_owned(), - } - } else { - PluginConfig::new(path).map_err(AnimationFactoryError::InvalidPlugin)? - }; - - match config.manifest.plugin_type { - PluginType::Native => Ok(Box::new( - JsonRpcPlugin::new(config, self.points.clone()).await?, - )), - PluginType::Wasm => Ok(Box::new( - WasmPlugin::new(config, self.points.clone()).await?, - )), - } + ) -> Result { + Ok(AnimationPlugin::new(path, self.points.clone()).await?) } pub async fn install(&self, path: &Path) -> Result { @@ -164,7 +91,6 @@ impl AnimationFactory { tokio::fs::rename(path, &new_path).await?; Ok(PluginConfig { - animation_id: manifest.id.clone(), manifest, path: new_path, }) diff --git a/animator/src/jsonrpc.rs b/animator/src/jsonrpc.rs deleted file mode 100644 index 66525f1..0000000 --- a/animator/src/jsonrpc.rs +++ /dev/null @@ -1,245 +0,0 @@ -use std::{ - collections::HashMap, - ffi::OsStr, - io::{BufRead, BufReader, BufWriter, Write}, - process::{Child, Command, Stdio}, - result::Result, -}; - -use animation_api::{ - AnimationError, JsonRpcMessage, JsonRpcMethod, JsonRpcResponse, JsonRpcResult, - schema::{Configuration, ConfigurationSchema, ParameterValue}, -}; -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}; - -#[derive(Debug, thiserror::Error)] -pub enum JsonRpcEndpointError { - #[error("endpoint process exited")] - ProcessExited, - - #[error("endpoint returned invalid response: {0}")] - InvalidResponse(String), -} - -pub struct JsonRpcEndpoint { - child_process: Mutex, - id: Mutex, -} - -impl JsonRpcEndpoint { - pub fn new>(executable_path: P) -> std::io::Result { - let mut command = Command::new(executable_path); - - let child_process = command - .stdout(Stdio::piped()) - .stdin(Stdio::piped()) - .spawn()?; - - Ok(Self { - child_process: Mutex::new(child_process), - id: Mutex::new(0), - }) - } - - fn send( - writer: &mut impl std::io::Write, - message: JsonRpcMessage, - ) -> std::io::Result<()> { - let mut writer = BufWriter::new(writer); - serde_json::to_writer(&mut writer, &message)?; - writer.write_all(b"\n")?; - Ok(()) - } - - fn receive( - reader: &mut impl std::io::Read, - ) -> Result, JsonRpcEndpointError> - where - Res: DeserializeOwned, - { - let mut reader = BufReader::new(reader); - let mut buffer = String::new(); - reader - .read_line(&mut buffer) - .map_err(|e| JsonRpcEndpointError::InvalidResponse(e.to_string())) - .and_then(|count| { - if count == 0 { - Err(JsonRpcEndpointError::ProcessExited) - } else { - Ok(()) - } - })?; - serde_json::from_str(&buffer) - .map_err(|e| JsonRpcEndpointError::InvalidResponse(e.to_string())) - } - - pub async fn send_message( - &self, - payload: JsonRpcMethod, - ) -> Result, JsonRpcEndpointError> - where - Res: DeserializeOwned, - { - let mut child = self.child_process.lock().await; - - let mut id = self.id.lock().await; - *id += 1; - Self::send( - child.stdin.as_mut().unwrap(), - JsonRpcMessage { - id: Some(*id), - payload, - }, - ) - .map_err(|_| JsonRpcEndpointError::ProcessExited)?; - - Self::receive(child.stdout.as_mut().unwrap()).map(|response| response.result) - } - - pub async fn send_notification( - &self, - payload: JsonRpcMethod, - ) -> Result<(), JsonRpcEndpointError> { - let mut child = self.child_process.lock().await; - Self::send( - child.stdin.as_mut().unwrap(), - JsonRpcMessage { id: None, payload }, - ) - .map_err(|_| JsonRpcEndpointError::ProcessExited) - } -} - -pub struct JsonRpcPlugin { - plugin_config: PluginConfig, - endpoint: JsonRpcEndpoint, -} - -impl JsonRpcPlugin { - pub async fn new( - config: PluginConfig, - points: Vec<(f64, f64, f64)>, - ) -> Result { - let endpoint = JsonRpcEndpoint::new(config.executable_path()) - .map_err(|e| AnimationPluginError::CommunicationError(Box::new(e)))?; - - match endpoint - .send_message::<()>(JsonRpcMethod::Initialize { points }) - .await - { - Ok(JsonRpcResult::Result(_)) => Ok(Self { - endpoint, - plugin_config: config, - }), - Ok(JsonRpcResult::Error(e)) => Err(AnimationPluginError::AnimationError(e.data)), - Err(e) => Err(AnimationPluginError::CommunicationError(Box::new(e))), - } - } -} - -#[async_trait] -impl Plugin for JsonRpcPlugin { - fn plugin_config(&self) -> &PluginConfig { - &self.plugin_config - } - - async fn configuration(&self) -> Result { - Ok(Configuration { - id: self.plugin_config().animation_id.to_owned(), - name: self.plugin_config().manifest.display_name.to_owned(), - schema: self.get_schema().await?, - values: self.get_parameters().await?, - }) - } - - async fn update(&mut self, time_delta: f64) -> Result<(), AnimationPluginError> { - if let Err(e) = self - .endpoint - .send_notification(JsonRpcMethod::Update { time_delta }) - .await - { - Err(AnimationPluginError::CommunicationError(Box::new(e))) - } else { - Ok(()) - } - } - - async fn render(&self) -> Result { - match self.endpoint.send_message(JsonRpcMethod::Render).await { - Ok(JsonRpcResult::Result(frame)) => Ok(frame), - Ok(JsonRpcResult::Error(e)) => Err(AnimationPluginError::AnimationError(e.data)), - Err(e) => Err(AnimationPluginError::CommunicationError(Box::new(e))), - } - } - - async fn get_schema(&self) -> Result { - match self - .endpoint - .send_message(JsonRpcMethod::ParameterSchema) - .await - { - Ok(JsonRpcResult::Result(schema)) => Ok(schema), - Ok(JsonRpcResult::Error(e)) => Err(AnimationPluginError::AnimationError(e.data)), - Err(e) => Err(AnimationPluginError::CommunicationError(Box::new(e))), - } - } - - async fn set_parameters( - &mut self, - params: &HashMap, - ) -> Result<(), AnimationPluginError> { - match self - .endpoint - .send_message(JsonRpcMethod::SetParameters { - params: params.clone(), - }) - .await - { - Ok(JsonRpcResult::Result(())) => Ok(()), - Ok(JsonRpcResult::Error(e)) => Err(AnimationPluginError::AnimationError(e.data)), - Err(e) => Err(AnimationPluginError::CommunicationError(Box::new(e))), - } - } - - async fn get_parameters( - &self, - ) -> Result, AnimationPluginError> { - match self - .endpoint - .send_message(JsonRpcMethod::GetParameters) - .await - { - Ok(JsonRpcResult::Result(parameters)) => Ok(parameters), - Ok(JsonRpcResult::Error(e)) => Err(AnimationPluginError::AnimationError(e.data)), - Err(e) => Err(AnimationPluginError::CommunicationError(Box::new(e))), - } - } - - async fn get_fps(&self) -> Result { - match self.endpoint.send_message(JsonRpcMethod::GetFps).await { - Ok(JsonRpcResult::Result(fps)) => Ok(fps), - Ok(JsonRpcResult::Error(e)) => Err(AnimationPluginError::AnimationError(e.data)), - Err(e) => Err(AnimationPluginError::CommunicationError(Box::new(e))), - } - } - - async fn send_event( - &self, - event: animation_api::event::Event, - ) -> Result<(), AnimationPluginError> { - match self - .endpoint - .send_message(JsonRpcMethod::OnEvent { event }) - .await - { - Ok(JsonRpcResult::Result(())) => Ok(()), - Ok(JsonRpcResult::Error(e)) => Err(AnimationPluginError::AnimationError(e.data)), - Err(e) => Err(AnimationPluginError::CommunicationError(Box::new(e))), - } - } -} diff --git a/animator/src/lib.rs b/animator/src/lib.rs index 54f067b..5f56ca0 100644 --- a/animator/src/lib.rs +++ b/animator/src/lib.rs @@ -1,9 +1,6 @@ mod config; mod controller; mod factory; -mod jsonrpc; -mod plugin; -mod wasm; pub use config::ControllerConfig; pub use controller::{Controller, ControllerError}; diff --git a/animator/src/plugin.rs b/animator/src/plugin.rs deleted file mode 100644 index a67cac1..0000000 --- a/animator/src/plugin.rs +++ /dev/null @@ -1,37 +0,0 @@ -use std::{collections::HashMap, error::Error}; - -use animation_api::{ - AnimationError, - schema::{Configuration, ConfigurationSchema, ParameterValue}, -}; -use animation_wrapper::config::PluginConfig; -use async_trait::async_trait; - -#[derive(Debug, thiserror::Error)] -pub enum AnimationPluginError { - #[error("animation error: {0}")] - AnimationError(#[from] AnimationError), - - #[error("Communication error: {0}")] - CommunicationError(#[from] Box), -} - -#[async_trait] -pub trait Plugin: Send + Sync { - fn plugin_config(&self) -> &PluginConfig; - async fn configuration(&self) -> Result; - async fn update(&mut self, time_delta: f64) -> Result<(), AnimationPluginError>; - async fn render(&self) -> Result; - async fn get_schema(&self) -> Result; - async fn set_parameters( - &mut self, - params: &HashMap, - ) -> Result<(), AnimationPluginError>; - async fn get_parameters(&self) - -> Result, AnimationPluginError>; - async fn get_fps(&self) -> Result; - async fn send_event( - &self, - event: animation_api::event::Event, - ) -> Result<(), AnimationPluginError>; -} diff --git a/animator/src/wasm.rs b/animator/src/wasm.rs deleted file mode 100644 index a54ff24..0000000 --- a/animator/src/wasm.rs +++ /dev/null @@ -1,130 +0,0 @@ -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}; - -pub struct WasmPlugin { - inner: Mutex, - plugin_config: PluginConfig, -} - -impl WasmPlugin { - pub async fn new( - 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(plugin), - plugin_config: config, - }) - } -} - -#[async_trait] -impl Plugin for WasmPlugin { - fn plugin_config(&self) -> &PluginConfig { - &self.plugin_config - } - - async fn configuration(&self) -> Result { - Ok(schema::Configuration { - id: self.plugin_config.animation_id.to_owned(), - name: self.plugin_config.manifest.display_name.to_owned(), - schema: self.get_schema().await?, - values: self.get_parameters().await?, - }) - } - - async fn update(&mut self, time_delta: f64) -> Result<(), AnimationPluginError> { - self.inner - .lock() - .await - .update(time_delta) - .await - .map_err(|e| AnimationPluginError::CommunicationError(Box::new(e))) - } - - async fn render(&self) -> Result { - let frame = self - .inner - .lock() - .await - .render() - .await - .map_err(|e| AnimationPluginError::CommunicationError(Box::new(e)))?; - - Ok(frame - .into_iter() - .map(|c| lightfx::Color::rgb(c.r, c.g, c.b)) - .into()) - } - - async fn get_schema(&self) -> Result { - self.inner - .lock() - .await - .get_schema() - .await - .map_err(|e| AnimationPluginError::CommunicationError(Box::new(e))) - } - - async fn set_parameters( - &mut self, - values: &HashMap, - ) -> Result<(), AnimationPluginError> { - self.inner - .lock() - .await - .set_parameters(values) - .await - .map_err(|e| AnimationPluginError::CommunicationError(Box::new(e))) - } - - async fn get_parameters( - &self, - ) -> Result, AnimationPluginError> { - self.inner - .lock() - .await - .get_parameters() - .await - .map_err(|e| AnimationPluginError::CommunicationError(Box::new(e))) - } - - async fn get_fps(&self) -> Result { - self.inner - .lock() - .await - .get_fps() - .await - .map_err(|e| AnimationPluginError::CommunicationError(Box::new(e))) - } - - async fn send_event(&self, _event: Event) -> Result<(), AnimationPluginError> { - self.inner - .lock() - .await - .send_event(_event) - .await - .map_err(|e| AnimationPluginError::CommunicationError(Box::new(e))) - } -} diff --git a/lightfx/src/frame.rs b/lightfx/src/frame.rs index b70de10..d6a5d10 100644 --- a/lightfx/src/frame.rs +++ b/lightfx/src/frame.rs @@ -35,13 +35,10 @@ impl Frame { } } -impl From for Frame -where - T: Iterator, -{ - fn from(iter: T) -> Self { +impl FromIterator for Frame { + fn from_iter>(iter: I) -> Self { Self { - pixels: iter.collect(), + pixels: iter.into_iter().collect(), } } } diff --git a/webapi/Cargo.toml b/webapi/Cargo.toml index 7ffae9d..0f97cc9 100644 --- a/webapi/Cargo.toml +++ b/webapi/Cargo.toml @@ -8,6 +8,7 @@ rustmas-animator = { path = "../animator", default-features = false } lightfx = { path = "../lightfx" } webapi-model = { path = "../webapi-model" } animation-wrapper = { path = "../animation-wrapper", default-features = false } +animation-api = { path = "../animation-api" } actix-web-actors = "4.2.0" actix = "0.13.0" diff --git a/webapi/DEPLOYMENT.md b/webapi/DEPLOYMENT.md index 3fefb00..6920006 100644 --- a/webapi/DEPLOYMENT.md +++ b/webapi/DEPLOYMENT.md @@ -47,15 +47,17 @@ Animation plugin setup ---------------------- Before starting the service you will need to build animations and add your animations -to the plugins directory. Instructions can be found in the provided example [plugins directory](../plugins/README.md). +to the plugins directory. Instructions can be found in the [animations directory](../animations/README.md). + In short, you can build the animations with: ``` -cargo build --release -p animations +cargo install --path animation-wrapper +cd animations +./build_all.sh ``` -The plugin directory set up for all the built-in animations is provided -in the repository already. +This script put the animations in the directory where WebAPI will be looking for them. WebAPI service diff --git a/webapi/src/animations/logic.rs b/webapi/src/animations/logic.rs index 853467e..9fbb801 100644 --- a/webapi/src/animations/logic.rs +++ b/webapi/src/animations/logic.rs @@ -51,9 +51,8 @@ impl Logic { parameters: ¶meters::Logic, ) -> Result { let animation_id = controller - .current_animation() + .current_animation_id() .await - .map(|a| a.animation_id) .ok_or(LogicError::NoAnimationSelected)?; self.switch(&animation_id, None, controller, parameters) @@ -170,7 +169,7 @@ impl Logic { name: db_plugin.manifest.display_name, }) .collect(), - current_animation_id: controller.current_animation().await.map(|a| a.animation_id), + current_animation_id: controller.current_animation_id().await, }) } } diff --git a/webapi/src/animations/storage.rs b/webapi/src/animations/storage.rs index 77b5d8d..67e3cd3 100644 --- a/webapi/src/animations/storage.rs +++ b/webapi/src/animations/storage.rs @@ -1,6 +1,6 @@ use std::{path::PathBuf, str::FromStr}; -use animation_wrapper::config::{PluginConfig, PluginManifest}; +use animation_api::plugin_config::{PluginConfig, PluginManifest}; use anyhow::anyhow; use log::warn; @@ -27,7 +27,7 @@ impl Storage { let manifest = serde_json::to_string(&config.manifest)?; sqlx::query!( "INSERT INTO animation_plugins(id, path, manifest) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING", - config.animation_id, + config.manifest.id, path, manifest ) diff --git a/webapi/src/parameters/logic.rs b/webapi/src/parameters/logic.rs index 286e4d1..646acde 100644 --- a/webapi/src/parameters/logic.rs +++ b/webapi/src/parameters/logic.rs @@ -66,14 +66,13 @@ impl Logic { .await .map_err(|e| LogicError::InternalError(e.to_string()))?; - let animation = controller.current_animation().await; - let animation_id = animation - .as_ref() - .map(|a| &a.animation_id) + let animation_id = controller + .current_animation_id() + .await .ok_or(LogicError::NoAnimationSelected)?; self.storage - .save(animation_id, ¶meter_values) + .save(&animation_id, ¶meter_values) .await .map_err(|e| LogicError::InternalError(e.to_string())) } @@ -82,12 +81,12 @@ impl Logic { &self, controller: &mut rustmas_animator::Controller, ) -> Result { - let animation = controller - .current_animation() + let animation_id = controller + .current_animation_id() .await .ok_or(LogicError::NoAnimationSelected)?; - match self.storage.fetch(&animation.animation_id).await { + match self.storage.fetch(&animation_id).await { Ok(Some(params)) => controller .set_parameters(¶ms) .await