From fcea2ea7a41798fa86e507a38b157e6dbb92b4c3 Mon Sep 17 00:00:00 2001 From: hkrn <129939+hkrn@users.noreply.github.com> Date: Sat, 23 Sep 2023 19:00:41 +0900 Subject: [PATCH] [rust] add capability to watch and reload WASM plugins --- rust/Cargo.lock | 101 ++++++++++++ rust/deny.toml | 2 + rust/plugin_wasm/Cargo.toml | 1 + rust/plugin_wasm/src/model/plugin.rs | 199 +++++++++++++++++------ rust/plugin_wasm/src/model/test/mod.rs | 14 +- rust/plugin_wasm/src/motion/plugin.rs | 202 ++++++++++++++++++------ rust/plugin_wasm/src/motion/test/mod.rs | 14 +- 7 files changed, 422 insertions(+), 111 deletions(-) diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 17a281a5..faed31d5 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -349,6 +349,25 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +dependencies = [ + "cfg-if", +] + [[package]] name = "debugid" version = "0.8.0" @@ -449,6 +468,18 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "filetime" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.3.5", + "windows-sys", +] + [[package]] name = "fixedbitset" version = "0.4.2" @@ -475,6 +506,15 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "fsevent-sys" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" +dependencies = [ + "libc", +] + [[package]] name = "futures" version = "0.3.28" @@ -674,6 +714,26 @@ dependencies = [ "serde", ] +[[package]] +name = "inotify" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" +dependencies = [ + "bitflags 1.3.2", + "inotify-sys", + "libc", +] + +[[package]] +name = "inotify-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" +dependencies = [ + "libc", +] + [[package]] name = "io-extras" version = "0.18.0" @@ -731,6 +791,26 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +[[package]] +name = "kqueue" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" +dependencies = [ + "kqueue-sys", + "libc", +] + +[[package]] +name = "kqueue-sys" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" +dependencies = [ + "bitflags 1.3.2", + "libc", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -822,6 +902,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", + "log", "wasi", "windows-sys", ] @@ -841,6 +922,25 @@ dependencies = [ "prost-build", ] +[[package]] +name = "notify" +version = "6.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" +dependencies = [ + "bitflags 2.4.0", + "crossbeam-channel", + "filetime", + "fsevent-sys", + "inotify", + "kqueue", + "libc", + "log", + "mio", + "walkdir", + "windows-sys", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -929,6 +1029,7 @@ dependencies = [ "futures", "maplit", "nanoem-protobuf", + "notify", "pretty_assertions", "rand", "serde", diff --git a/rust/deny.toml b/rust/deny.toml index 443d01ac..2207248c 100644 --- a/rust/deny.toml +++ b/rust/deny.toml @@ -15,6 +15,8 @@ allow = [ "Apache-2.0 WITH LLVM-exception", "BSD-3-Clause", "BSD-2-Clause", + "CC0-1.0", + "ISC", "MIT", "MPL-2.0", "Unicode-DFS-2016", diff --git a/rust/plugin_wasm/Cargo.toml b/rust/plugin_wasm/Cargo.toml index ae2322a8..1f3f2050 100644 --- a/rust/plugin_wasm/Cargo.toml +++ b/rust/plugin_wasm/Cargo.toml @@ -8,6 +8,7 @@ license = "MPL-2.0" [dependencies] anyhow = "1" nanoem-protobuf = { version = "35", path = "../protobuf" } +notify = "6" tracing = { version = "0.1", default-features = false, features = ["std"] } tracing-subscriber = "0.3" wasmtime = { version = "13", default-features = false, features = [ diff --git a/rust/plugin_wasm/src/model/plugin.rs b/rust/plugin_wasm/src/model/plugin.rs index dbc94dc1..df2d3fb6 100644 --- a/rust/plugin_wasm/src/model/plugin.rs +++ b/rust/plugin_wasm/src/model/plugin.rs @@ -4,12 +4,17 @@ This file is part of emapp component and it's licensed under Mozilla Public License. see LICENSE.md for more details. */ -use std::{ffi::CString, path::Path}; +use std::{ + ffi::CString, + path::{Path, PathBuf}, + sync::{Arc, Mutex}, +}; use anyhow::Result; +use notify::Watcher; use walkdir::WalkDir; use wasmtime::{AsContextMut, Engine, Instance, Linker, Module}; -use wasmtime_wasi::WasiCtxBuilder; +use wasmtime_wasi::{WasiCtx, WasiCtxBuilder}; use crate::{ inner_count_all_functions, inner_create_opaque, inner_destroy_opaque, inner_execute, @@ -83,19 +88,25 @@ fn validate_plugin(instance: &Instance, mut store: impl AsContextMut) -> Result< pub struct ModelIOPlugin { instance: Instance, store: Store, + path: PathBuf, opaque: Option, } impl ModelIOPlugin { - pub fn new(engine: &Engine, bytes: &[u8], mut store: Store) -> Result { - let module = Module::new(engine, bytes)?; - let mut linker = Linker::new(engine); - wasmtime_wasi::add_to_linker(&mut linker, |ctx| ctx)?; + pub fn new( + linker: &Linker, + path: &Path, + bytes: &[u8], + mut store: Store, + ) -> Result { + let module = Module::new(linker.engine(), bytes)?; let instance = linker.instantiate(store.as_context_mut(), &module)?; validate_plugin(&instance, store.as_context_mut())?; + let path = path.to_path_buf(); Ok(Self { instance, store, + path, opaque: None, }) } @@ -372,10 +383,13 @@ impl ModelIOPlugin { &mut self.store, ) } + pub fn path(&self) -> &Path { + &self.path + } } pub struct ModelIOPluginController { - plugins: Vec, + plugins: Arc>>, function_indices: Vec<(usize, i32, CString)>, plugin_index: Option, failure_reason: Option, @@ -383,7 +397,7 @@ pub struct ModelIOPluginController { } impl ModelIOPluginController { - pub fn new(plugins: Vec) -> Self { + pub fn new(plugins: Arc>>) -> Self { let function_indices = vec![]; Self { plugins, @@ -395,10 +409,55 @@ impl ModelIOPluginController { } pub fn from_path(path: &Path, cb: F) -> Result where - F: Fn(&mut WasiCtxBuilder), + F: Fn(&mut WasiCtxBuilder) + Copy + std::marker::Sync + std::marker::Send + 'static, { - let mut plugins = vec![]; let engine = Engine::default(); + let plugins = Arc::new(Mutex::new(vec![])); + let plugins_inner = Arc::clone(&plugins); + let mut linker = Linker::new(&engine); + let linker_inner = linker.clone(); + wasmtime_wasi::add_to_linker(&mut linker, |ctx| ctx)?; + let event_handler = move |res: notify::Result| match res { + Ok(ev) => match ev.kind { + notify::EventKind::Modify(_) => { + for path in ev.paths.iter() { + if let Some(plugin_mut) = plugins_inner + .lock() + .unwrap() + .iter_mut() + .find(|plugin: &&mut ModelIOPlugin| plugin.path() == path) + { + let bytes = std::fs::read(path).unwrap(); + let mut builder = WasiCtxBuilder::new(); + cb(&mut builder); + let data = builder.build(); + let store = Store::new(linker_inner.engine(), data); + match ModelIOPlugin::new(&linker_inner, path, &bytes, store) { + Ok(plugin) => { + *plugin_mut = plugin; + } + Err(err) => { + tracing::warn!( + error = %err, + path = ?path, + "Cannot reload model WASM plugin", + ) + } + } + } + } + } + notify::EventKind::Remove(_) => { + plugins_inner + .lock() + .unwrap() + .retain(|plugin| !ev.paths.contains(&plugin.path)); + } + _ => {} + }, + Err(e) => tracing::warn!(error = ?e, "Catched an watch error"), + }; + let mut watcher = notify::recommended_watcher(event_handler)?; for entry in WalkDir::new(path.parent().unwrap()) { let entry = entry?; let filename = entry.file_name().to_str(); @@ -407,10 +466,12 @@ impl ModelIOPluginController { let mut builder = WasiCtxBuilder::new(); cb(&mut builder); let data = builder.build(); - let store = Store::new(&engine, data); - match ModelIOPlugin::new(&engine, &bytes, store) { + let store = Store::new(linker.engine(), data); + let path = entry.path(); + match ModelIOPlugin::new(&linker, path, &bytes, store) { Ok(plugin) => { - plugins.push(plugin); + watcher.watch(path, notify::RecursiveMode::NonRecursive)?; + plugins.lock().unwrap().push(plugin); tracing::debug!(filename = filename.unwrap(), "Loaded model WASM plugin"); } Err(err) => { @@ -427,14 +488,15 @@ impl ModelIOPluginController { } pub fn initialize(&mut self) -> Result<()> { self.plugins + .lock() + .unwrap() .iter_mut() .try_for_each(|plugin| plugin.initialize()) } pub fn create(&mut self) -> Result<()> { - self.plugins - .iter_mut() - .try_for_each(|plugin| plugin.create())?; - for (offset, plugin) in self.plugins.iter_mut().enumerate() { + let mut guard = self.plugins.lock().unwrap(); + guard.iter_mut().try_for_each(|plugin| plugin.create())?; + for (offset, plugin) in guard.iter_mut().enumerate() { let name = plugin.name()?; let version = plugin.version()?; for index in 0..plugin.count_all_functions()? { @@ -448,6 +510,8 @@ impl ModelIOPluginController { } pub fn set_language(&mut self, value: i32) -> Result<()> { self.plugins + .lock() + .unwrap() .iter_mut() .try_for_each(|plugin| plugin.set_language(value)) } @@ -463,7 +527,7 @@ impl ModelIOPluginController { } pub fn set_function(&mut self, index: i32) -> Result { if let Some((plugin_index, function_index, _)) = self.function_indices.get(index as usize) { - let result = self.plugins[*plugin_index].set_function(*function_index); + let result = self.plugins.lock().unwrap()[*plugin_index].set_function(*function_index); self.plugin_index = Some(*plugin_index); result } else { @@ -471,66 +535,83 @@ impl ModelIOPluginController { } } pub fn set_all_selected_vertex_indices(&mut self, data: &[i32]) -> Result<()> { - self.current_plugin()?.set_all_selected_vertex_indices(data) + self.current_plugin(|plugin| plugin.set_all_selected_vertex_indices(data)) } pub fn set_all_selected_material_indices(&mut self, data: &[i32]) -> Result<()> { - self.current_plugin()? - .set_all_selected_material_indices(data) + self.current_plugin(|plugin| plugin.set_all_selected_material_indices(data)) } pub fn set_all_selected_bone_indices(&mut self, data: &[i32]) -> Result<()> { - self.current_plugin()?.set_all_selected_bone_indices(data) + self.current_plugin(|plugin| plugin.set_all_selected_bone_indices(data)) } pub fn set_all_selected_morph_indices(&mut self, data: &[i32]) -> Result<()> { - self.current_plugin()?.set_all_selected_morph_indices(data) + self.current_plugin(|plugin| plugin.set_all_selected_morph_indices(data)) } pub fn set_all_selected_label_indices(&mut self, data: &[i32]) -> Result<()> { - self.current_plugin()?.set_all_selected_label_indices(data) + self.current_plugin(|plugin| plugin.set_all_selected_label_indices(data)) } pub fn set_all_selected_rigid_body_indices(&mut self, data: &[i32]) -> Result<()> { - self.current_plugin()? - .set_all_selected_rigid_body_indices(data) + self.current_plugin(|plugin| plugin.set_all_selected_rigid_body_indices(data)) } pub fn set_all_selected_joint_indices(&mut self, data: &[i32]) -> Result<()> { - self.current_plugin()?.set_all_selected_joint_indices(data) + self.current_plugin(|plugin| plugin.set_all_selected_joint_indices(data)) } pub fn set_all_selected_soft_body_indices(&mut self, data: &[i32]) -> Result<()> { - self.current_plugin()? - .set_all_selected_soft_body_indices(data) + self.current_plugin(|plugin| plugin.set_all_selected_soft_body_indices(data)) } pub fn set_audio_description(&mut self, data: &[u8]) -> Result<()> { - self.current_plugin()?.set_audio_description(data) + self.current_plugin(|plugin| plugin.set_audio_description(data)) } pub fn set_camera_description(&mut self, data: &[u8]) -> Result<()> { - self.current_plugin()?.set_camera_description(data) + self.current_plugin(|plugin| plugin.set_camera_description(data)) } pub fn set_light_description(&mut self, data: &[u8]) -> Result<()> { - self.current_plugin()?.set_light_description(data) + self.current_plugin(|plugin| plugin.set_light_description(data)) } pub fn set_audio_data(&mut self, data: &[u8]) -> Result<()> { - self.current_plugin()?.set_audio_data(data) + self.current_plugin(|plugin| plugin.set_audio_data(data)) } pub fn set_input_model_data(&mut self, bytes: &[u8]) -> Result<()> { - self.current_plugin()?.set_input_model_data(bytes) + self.current_plugin(|plugin| plugin.set_input_model_data(bytes)) } pub fn execute(&mut self) -> Result<()> { - match self.current_plugin()?.execute() { + let mut result = Ok(0); + self.current_plugin(|plugin| { + result = plugin.execute(); + Ok(()) + })?; + match result { Ok(0) => Ok(()), Ok(_) => self.set_failure(), Err(err) => Err(err), } } pub fn get_output_data(&mut self) -> Result> { - self.current_plugin()?.get_output_data() + let mut bytes = vec![]; + self.current_plugin(|plugin| { + bytes = plugin.get_output_data()?; + Ok(()) + })?; + Ok(bytes) } pub fn load_ui_window_layout(&mut self) -> Result<()> { - match self.current_plugin()?.load_ui_window_layout() { + let mut result = Ok(0); + self.current_plugin(|plugin| { + result = plugin.load_ui_window_layout(); + Ok(()) + })?; + match result { Ok(0) => Ok(()), Ok(_) => self.set_failure(), Err(err) => Err(err), } } pub fn get_ui_window_layout(&mut self) -> Result> { - self.current_plugin()?.get_ui_window_layout() + let mut bytes = vec![]; + self.current_plugin(|plugin| { + bytes = plugin.get_ui_window_layout()?; + Ok(()) + })?; + Ok(bytes) } pub fn set_ui_component_layout( &mut self, @@ -538,10 +619,12 @@ impl ModelIOPluginController { data: &[u8], reload: &mut bool, ) -> Result<()> { - match self - .current_plugin()? - .set_ui_component_layout(id, data, reload) - { + let mut result = Ok(0); + self.current_plugin(|plugin| { + result = plugin.set_ui_component_layout(id, data, reload); + Ok(()) + })?; + match result { Ok(0) => Ok(()), Ok(_) => self.set_failure(), Err(err) => Err(err), @@ -557,31 +640,49 @@ impl ModelIOPluginController { self.failure_reason = Some(value.to_string()); } pub fn destroy(&mut self) { - self.plugins.iter_mut().for_each(|plugin| plugin.destroy()) + self.plugins + .lock() + .unwrap() + .iter_mut() + .for_each(|plugin| plugin.destroy()) } pub fn terminate(&mut self) { self.plugins + .lock() + .unwrap() .iter_mut() .for_each(|plugin| plugin.terminate()) } #[allow(unused)] - pub(super) fn all_plugins_mut(&mut self) -> &mut [ModelIOPlugin] { - &mut self.plugins + pub(super) fn all_plugins_mut(&mut self) -> Arc>> { + Arc::clone(&self.plugins) } fn set_failure(&mut self) -> Result<()> { - let value = self.current_plugin()?.failure_reason()?; + let mut value = String::new(); + self.current_plugin(|plugin| { + value = plugin.failure_reason()?; + Ok(()) + })?; if !value.is_empty() { self.failure_reason = Some(value); } - let value = self.current_plugin()?.recovery_suggestion()?; + let mut value = String::new(); + self.current_plugin(|plugin| { + value = plugin.recovery_suggestion()?; + Ok(()) + })?; if !value.is_empty() { self.recovery_suggestion = Some(value); } Ok(()) } - fn current_plugin(&mut self) -> Result<&mut ModelIOPlugin> { + fn current_plugin(&mut self, mut cb: T) -> Result<()> + where + T: FnMut(&mut ModelIOPlugin) -> Result<()>, + { if let Some(plugin_index) = self.plugin_index { - Ok(&mut self.plugins[plugin_index]) + let plugins = &mut self.plugins.lock().unwrap(); + cb(&mut plugins[plugin_index]) } else { Err(anyhow::anyhow!("plugin is not set")) } diff --git a/rust/plugin_wasm/src/model/test/mod.rs b/rust/plugin_wasm/src/model/test/mod.rs index 0f56a997..12599983 100644 --- a/rust/plugin_wasm/src/model/test/mod.rs +++ b/rust/plugin_wasm/src/model/test/mod.rs @@ -21,7 +21,7 @@ use wasi_common::{ file::{FileType, Filestat}, snapshots::preview_1::types::Error, }; -use wasmtime::Engine; +use wasmtime::{Engine, Linker}; use wasmtime_wasi::{WasiCtxBuilder, WasiFile}; use crate::Store; @@ -127,9 +127,13 @@ fn inner_create_controller(pipe: Box, path: &str) -> Result Result<()> { .join(format!("target/wasm32-wasi/{ty}/deps")); let mut controller = ModelIOPluginController::from_path(&path, |_builder| ())?; let mut names = vec![]; - for plugin in controller.all_plugins_mut() { + for plugin in controller.all_plugins_mut().lock().unwrap().iter_mut() { plugin.create()?; names.push(plugin.name()?); } diff --git a/rust/plugin_wasm/src/motion/plugin.rs b/rust/plugin_wasm/src/motion/plugin.rs index 9a2803b8..d1187479 100644 --- a/rust/plugin_wasm/src/motion/plugin.rs +++ b/rust/plugin_wasm/src/motion/plugin.rs @@ -4,13 +4,19 @@ This file is part of emapp component and it's licensed under Mozilla Public License. see LICENSE.md for more details. */ -use std::{ffi::CString, path::Path, slice}; +use std::{ + ffi::CString, + path::{Path, PathBuf}, + slice, + sync::{Arc, Mutex}, +}; use anyhow::Result; +use notify::Watcher; use tracing::warn; use walkdir::WalkDir; use wasmtime::{AsContextMut, Engine, Instance, Linker, Module}; -use wasmtime_wasi::WasiCtxBuilder; +use wasmtime_wasi::{WasiCtx, WasiCtxBuilder}; use crate::{ allocate_byte_array_with_data, allocate_status_ptr, inner_count_all_functions, @@ -24,6 +30,7 @@ use crate::{ pub struct MotionIOPlugin { instance: Instance, store: Store, + path: PathBuf, opaque: Option, } @@ -125,14 +132,19 @@ fn validate_plugin(instance: &Instance, mut store: impl AsContextMut) -> Result< } impl MotionIOPlugin { - pub fn new(engine: &Engine, bytes: &[u8], mut store: Store) -> Result { - let module = Module::new(engine, bytes)?; - let mut linker = Linker::new(engine); - wasmtime_wasi::add_to_linker(&mut linker, |ctx| ctx)?; + pub fn new( + linker: &Linker, + path: &Path, + bytes: &[u8], + mut store: Store, + ) -> Result { + let module = Module::new(linker.engine(), bytes)?; let instance = linker.instantiate(store.as_context_mut(), &module)?; validate_plugin(&instance, store.as_context_mut())?; + let path = path.to_path_buf(); Ok(Self { instance, + path, store, opaque: None, }) @@ -421,10 +433,13 @@ impl MotionIOPlugin { &mut self.store, ) } + pub fn path(&self) -> &Path { + &self.path + } } pub struct MotionIOPluginController { - plugins: Vec, + plugins: Arc>>, function_indices: Vec<(usize, i32, CString)>, plugin_index: Option, failure_reason: Option, @@ -432,7 +447,7 @@ pub struct MotionIOPluginController { } impl MotionIOPluginController { - pub fn new(plugins: Vec) -> Self { + pub fn new(plugins: Arc>>) -> Self { let function_indices = vec![]; Self { plugins, @@ -444,10 +459,55 @@ impl MotionIOPluginController { } pub fn from_path(path: &Path, cb: F) -> Result where - F: Fn(&mut WasiCtxBuilder), + F: Fn(&mut WasiCtxBuilder) + Copy + std::marker::Sync + std::marker::Send + 'static, { - let mut plugins = vec![]; let engine = Engine::default(); + let plugins = Arc::new(Mutex::new(vec![])); + let plugins_inner = Arc::clone(&plugins); + let mut linker = Linker::new(&engine); + let linker_inner = linker.clone(); + wasmtime_wasi::add_to_linker(&mut linker, |ctx| ctx)?; + let event_handler = move |res: notify::Result| match res { + Ok(ev) => match ev.kind { + notify::EventKind::Modify(_) => { + for path in ev.paths.iter() { + if let Some(plugin_mut) = plugins_inner + .lock() + .unwrap() + .iter_mut() + .find(|plugin: &&mut MotionIOPlugin| plugin.path() == path) + { + let bytes = std::fs::read(path).unwrap(); + let mut builder = WasiCtxBuilder::new(); + cb(&mut builder); + let data = builder.build(); + let store = Store::new(linker_inner.engine(), data); + match MotionIOPlugin::new(&linker_inner, path, &bytes, store) { + Ok(plugin) => { + *plugin_mut = plugin; + } + Err(err) => { + tracing::warn!( + error = %err, + path = ?path, + "Cannot reload motion WASM plugin", + ) + } + } + } + } + } + notify::EventKind::Remove(_) => { + plugins_inner + .lock() + .unwrap() + .retain(|plugin| !ev.paths.contains(&plugin.path)); + } + _ => {} + }, + Err(e) => tracing::warn!(error = ?e, "Catched an watch error"), + }; + let mut watcher = notify::recommended_watcher(event_handler)?; for entry in WalkDir::new(path.parent().unwrap()) { let entry = entry?; let filename = entry.file_name().to_str(); @@ -457,9 +517,10 @@ impl MotionIOPluginController { cb(&mut builder); let data = builder.build(); let store = Store::new(&engine, data); - match MotionIOPlugin::new(&engine, &bytes, store) { + match MotionIOPlugin::new(&linker, path, &bytes, store) { Ok(plugin) => { - plugins.push(plugin); + watcher.watch(path, notify::RecursiveMode::NonRecursive)?; + plugins.lock().unwrap().push(plugin); tracing::debug!(filename = filename.unwrap(), "Loaded motion WASM plugin"); } Err(err) => { @@ -476,14 +537,15 @@ impl MotionIOPluginController { } pub fn initialize(&mut self) -> Result<()> { self.plugins + .lock() + .unwrap() .iter_mut() .try_for_each(|plugin| plugin.initialize()) } pub fn create(&mut self) -> Result<()> { - self.plugins - .iter_mut() - .try_for_each(|plugin| plugin.create())?; - for (offset, plugin) in self.plugins.iter_mut().enumerate() { + let mut guard = self.plugins.lock().unwrap(); + guard.iter_mut().try_for_each(|plugin| plugin.create())?; + for (offset, plugin) in guard.iter_mut().enumerate() { let name = plugin.name()?; let version = plugin.version()?; for index in 0..plugin.count_all_functions()? { @@ -497,6 +559,8 @@ impl MotionIOPluginController { } pub fn set_language(&mut self, value: i32) -> Result<()> { self.plugins + .lock() + .unwrap() .iter_mut() .try_for_each(|plugin| plugin.set_language(value)) } @@ -512,7 +576,8 @@ impl MotionIOPluginController { } pub fn set_function(&mut self, index: i32) -> Result<()> { if let Some((plugin_index, function_index, _)) = self.function_indices.get(index as usize) { - match self.plugins[*plugin_index].set_function(*function_index) { + let result = self.plugins.lock().unwrap()[*plugin_index].set_function(*function_index); + match result { Ok(0) => { self.plugin_index = Some(*plugin_index); Ok(()) @@ -525,78 +590,91 @@ impl MotionIOPluginController { } } pub fn set_all_selected_accessory_keyframes(&mut self, value: &[u32]) -> Result<()> { - self.current_plugin()? - .set_all_selected_accessory_keyframes(value) + self.current_plugin(|plugin| plugin.set_all_selected_accessory_keyframes(value)) } pub fn set_all_named_selected_bone_keyframes( &mut self, name: &str, value: &[u32], ) -> Result<()> { - self.current_plugin()? - .set_all_named_selected_bone_keyframes(name, value) + self.current_plugin(|plugin| plugin.set_all_named_selected_bone_keyframes(name, value)) } pub fn set_all_selected_camera_keyframes(&mut self, value: &[u32]) -> Result<()> { - self.current_plugin()? - .set_all_selected_camera_keyframes(value) + self.current_plugin(|plugin| plugin.set_all_selected_camera_keyframes(value)) } pub fn set_all_selected_light_keyframes(&mut self, value: &[u32]) -> Result<()> { - self.current_plugin()? - .set_all_selected_light_keyframes(value) + self.current_plugin(|plugin| plugin.set_all_selected_light_keyframes(value)) } pub fn set_all_selected_model_keyframes(&mut self, value: &[u32]) -> Result<()> { - self.current_plugin()? - .set_all_selected_model_keyframes(value) + self.current_plugin(|plugin| plugin.set_all_selected_model_keyframes(value)) } pub fn set_all_named_selected_morph_keyframes( &mut self, name: &str, value: &[u32], ) -> Result<()> { - self.current_plugin()? - .set_all_named_selected_morph_keyframes(name, value) + self.current_plugin(|plugin| plugin.set_all_named_selected_morph_keyframes(name, value)) } pub fn set_all_selected_self_shadow_keyframes(&mut self, value: &[u32]) -> Result<()> { - self.current_plugin()? - .set_all_selected_self_shadow_keyframes(value) + self.current_plugin(|plugin| plugin.set_all_selected_self_shadow_keyframes(value)) } pub fn set_audio_description(&mut self, data: &[u8]) -> Result<()> { - self.current_plugin()?.set_audio_description(data) + self.current_plugin(|plugin| plugin.set_audio_description(data)) } pub fn set_camera_description(&mut self, data: &[u8]) -> Result<()> { - self.current_plugin()?.set_camera_description(data) + self.current_plugin(|plugin| plugin.set_camera_description(data)) } pub fn set_light_description(&mut self, data: &[u8]) -> Result<()> { - self.current_plugin()?.set_light_description(data) + self.current_plugin(|plugin| plugin.set_light_description(data)) } pub fn set_audio_data(&mut self, data: &[u8]) -> Result<()> { - self.current_plugin()?.set_audio_data(data) + self.current_plugin(|plugin| plugin.set_audio_data(data)) } pub fn set_input_model_data(&mut self, bytes: &[u8]) -> Result<()> { - self.current_plugin()?.set_input_model_data(bytes) + self.current_plugin(|plugin| plugin.set_input_model_data(bytes)) } pub fn set_input_motion_data(&mut self, bytes: &[u8]) -> Result<()> { - self.current_plugin()?.set_input_motion_data(bytes) + self.current_plugin(|plugin| plugin.set_input_motion_data(bytes)) } pub fn execute(&mut self) -> Result<()> { - match self.current_plugin()?.execute() { + let mut result = Ok(0); + self.current_plugin(|plugin| { + result = plugin.execute(); + Ok(()) + })?; + match result { Ok(0) => Ok(()), Ok(_) => self.set_failure(), Err(err) => Err(err), } } pub fn get_output_data(&mut self) -> Result> { - self.current_plugin()?.get_output_data() + let mut bytes = vec![]; + self.current_plugin(|plugin| { + bytes = plugin.get_output_data()?; + Ok(()) + })?; + Ok(bytes) } pub fn load_ui_window_layout(&mut self) -> Result<()> { - match self.current_plugin()?.load_ui_window_layout() { + let mut result = Ok(0); + self.current_plugin(|plugin| { + result = plugin.load_ui_window_layout(); + Ok(()) + })?; + match result { Ok(0) => Ok(()), Ok(_) => self.set_failure(), Err(err) => Err(err), } } pub fn get_ui_window_layout(&mut self) -> Result> { - self.current_plugin()?.get_ui_window_layout() + let mut bytes = vec![]; + self.current_plugin(|plugin| { + bytes = plugin.get_ui_window_layout()?; + Ok(()) + })?; + Ok(bytes) } pub fn set_ui_component_layout( &mut self, @@ -604,10 +682,12 @@ impl MotionIOPluginController { data: &[u8], reload: &mut bool, ) -> Result<()> { - match self - .current_plugin()? - .set_ui_component_layout(id, data, reload) - { + let mut result = Ok(0); + self.current_plugin(|plugin| { + result = plugin.set_ui_component_layout(id, data, reload); + Ok(()) + })?; + match result { Ok(0) => Ok(()), Ok(_) => self.set_failure(), Err(err) => Err(err), @@ -623,31 +703,49 @@ impl MotionIOPluginController { self.failure_reason = Some(value.to_string()); } pub fn destroy(&mut self) { - self.plugins.iter_mut().for_each(|plugin| plugin.destroy()) + self.plugins + .lock() + .unwrap() + .iter_mut() + .for_each(|plugin| plugin.destroy()) } pub fn terminate(&mut self) { self.plugins + .lock() + .unwrap() .iter_mut() .for_each(|plugin| plugin.terminate()) } #[allow(unused)] - pub(super) fn all_plugins_mut(&mut self) -> &mut [MotionIOPlugin] { - &mut self.plugins + pub(super) fn all_plugins_mut(&mut self) -> Arc>> { + Arc::clone(&self.plugins) } fn set_failure(&mut self) -> Result<()> { - let value = self.current_plugin()?.failure_reason()?; + let mut value = String::new(); + self.current_plugin(|plugin| { + value = plugin.failure_reason()?; + Ok(()) + })?; if !value.is_empty() { self.failure_reason = Some(value); } - let value = self.current_plugin()?.recovery_suggestion()?; + let mut value = String::new(); + self.current_plugin(|plugin| { + value = plugin.recovery_suggestion()?; + Ok(()) + })?; if !value.is_empty() { self.recovery_suggestion = Some(value); } Ok(()) } - fn current_plugin(&mut self) -> Result<&mut MotionIOPlugin> { + fn current_plugin(&mut self, mut cb: T) -> Result<()> + where + T: FnMut(&mut MotionIOPlugin) -> Result<()>, + { if let Some(plugin_index) = self.plugin_index { - Ok(&mut self.plugins[plugin_index]) + let plugins = &mut self.plugins.lock().unwrap(); + cb(&mut plugins[plugin_index]) } else { Err(anyhow::anyhow!("plugin is not set")) } diff --git a/rust/plugin_wasm/src/motion/test/mod.rs b/rust/plugin_wasm/src/motion/test/mod.rs index 7578847f..a244438e 100644 --- a/rust/plugin_wasm/src/motion/test/mod.rs +++ b/rust/plugin_wasm/src/motion/test/mod.rs @@ -21,7 +21,7 @@ use wasi_common::{ file::{FileType, Filestat}, snapshots::preview_1::types::Error, }; -use wasmtime::Engine; +use wasmtime::{Engine, Linker}; use wasmtime_wasi::{WasiCtxBuilder, WasiFile}; use crate::Store; @@ -129,9 +129,13 @@ fn inner_create_controller( let engine = Engine::default(); let data = WasiCtxBuilder::new().stdout(pipe).build(); let store = Store::new(&engine, data); - let bytes = std::fs::read(path)?; - let plugin = MotionIOPlugin::new(&engine, &bytes, store)?; - Ok(MotionIOPluginController::new(vec![plugin])) + let bytes = std::fs::read(&path)?; + let mut linker = Linker::new(&engine); + wasmtime_wasi::add_to_linker(&mut linker, |ctx| ctx)?; + let plugin = MotionIOPlugin::new(&linker, &path, &bytes, store)?; + Ok(MotionIOPluginController::new(Arc::new(Mutex::new(vec![ + plugin, + ])))) } #[test] @@ -143,7 +147,7 @@ fn from_path() -> Result<()> { .join(format!("target/wasm32-wasi/{ty}/deps")); let mut controller = MotionIOPluginController::from_path(&path, |_builder| ())?; let mut names = vec![]; - for plugin in controller.all_plugins_mut() { + for plugin in controller.all_plugins_mut().lock().unwrap().iter_mut() { plugin.create()?; names.push(plugin.name()?); }