From d01c12d02a255b744a037d9153fa71ca5c799724 Mon Sep 17 00:00:00 2001 From: hkrn <129939+hkrn@users.noreply.github.com> Date: Sat, 30 Sep 2023 16:18:41 +0900 Subject: [PATCH] [rust] replace to use `parking_lot` crate instead of standard mutex (#373) * [rust] replace to use `parking_lot` instead of standard mutex Additionally, the commit includes following changes. * retain watcher instance to prevent stop watch immediately * adds logging notification of watcher event * rename `(Model|Motion) WASM plugin` to `WASM (model|motion) I/O plugin` * [rust] move `(Model|Motion)IOPluginController` to its own namespace --- rust/Cargo.lock | 122 ++++--- rust/plugin_wasm/Cargo.toml | 5 +- rust/plugin_wasm/src/model/controller.rs | 364 +++++++++++++++++++ rust/plugin_wasm/src/model/core.rs | 4 +- rust/plugin_wasm/src/model/mod.rs | 1 + rust/plugin_wasm/src/model/plugin.rs | 344 +----------------- rust/plugin_wasm/src/model/test/full.rs | 2 +- rust/plugin_wasm/src/model/test/minimum.rs | 2 +- rust/plugin_wasm/src/model/test/mod.rs | 21 +- rust/plugin_wasm/src/motion/controller.rs | 371 ++++++++++++++++++++ rust/plugin_wasm/src/motion/core.rs | 4 +- rust/plugin_wasm/src/motion/mod.rs | 1 + rust/plugin_wasm/src/motion/plugin.rs | 348 +----------------- rust/plugin_wasm/src/motion/test/full.rs | 2 +- rust/plugin_wasm/src/motion/test/minimum.rs | 2 +- rust/plugin_wasm/src/motion/test/mod.rs | 23 +- 16 files changed, 847 insertions(+), 769 deletions(-) create mode 100644 rust/plugin_wasm/src/model/controller.rs create mode 100644 rust/plugin_wasm/src/motion/controller.rs diff --git a/rust/Cargo.lock b/rust/Cargo.lock index faed31d5..a276489c 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -254,7 +254,7 @@ dependencies = [ "cranelift-entity", "cranelift-isle", "gimli", - "hashbrown 0.14.0", + "hashbrown 0.14.1", "log", "regalloc2", "smallvec", @@ -349,25 +349,6 @@ 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" @@ -453,9 +434,9 @@ checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" [[package]] name = "fastrand" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "fd-lock" @@ -506,15 +487,6 @@ 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" @@ -659,9 +631,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.0" +version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" dependencies = [ "ahash", ] @@ -705,12 +677,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.0.0" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" dependencies = [ "equivalent", - "hashbrown 0.14.0", + "hashbrown 0.14.1", "serde", ] @@ -835,6 +807,16 @@ version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a9bad9f94746442c783ca431b22403b519cd7fbeed0533fdd6328b2f2212128" +[[package]] +name = "lock_api" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.20" @@ -929,9 +911,7 @@ 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", @@ -968,7 +948,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" dependencies = [ "crc32fast", - "hashbrown 0.14.0", + "hashbrown 0.14.1", "indexmap", "memchr", ] @@ -985,6 +965,29 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.3.5", + "smallvec", + "windows-targets", +] + [[package]] name = "paste" version = "1.0.14" @@ -1030,6 +1033,7 @@ dependencies = [ "maplit", "nanoem-protobuf", "notify", + "parking_lot", "pretty_assertions", "rand", "serde", @@ -1317,9 +1321,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.38.14" +version = "0.38.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "747c788e9ce8e92b12cd485c49ddf90723550b654b32508f979b71a7b1ecda4f" +checksum = "d2f9da0cbd88f9f09e7814e388301c8414c51c62aa6ce1e4b5c551d49d96e531" dependencies = [ "bitflags 2.4.0", "errno", @@ -1345,11 +1349,17 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "semver" -version = "1.0.18" +version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" +checksum = "ad977052201c6de01a8ef2aa3378c4bd23217a056337d1d6da40468d267a4fb0" [[package]] name = "serde" @@ -1384,9 +1394,9 @@ dependencies = [ [[package]] name = "sharded-slab" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +checksum = "c1b21f559e07218024e7e9f90f96f601825397de0e25420135f7f952453fed0b" dependencies = [ "lazy_static", ] @@ -1491,18 +1501,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.48" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" +checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.48" +version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" +checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" dependencies = [ "proc-macro2", "quote", @@ -1755,9 +1765,9 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.113.1" +version = "0.113.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a128cea7b8516703ab41b10a0b1aa9ba18d0454cd3792341489947ddeee268db" +checksum = "1fd0d44fab0bd78404e352f3399324eef76516a4580b52bc9031c60f064e98f3" dependencies = [ "indexmap", "semver", @@ -1765,12 +1775,12 @@ dependencies = [ [[package]] name = "wasmprinter" -version = "0.2.66" +version = "0.2.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab2e5e818f88cee5311e9a5df15cba0a8f772978baf3109af97004bce6e8e3c6" +checksum = "f6615a5587149e753bf4b93f90fa3c3f41c88597a7a2da72879afcabeda9648f" dependencies = [ "anyhow", - "wasmparser 0.113.1", + "wasmparser 0.113.2", ] [[package]] @@ -2275,9 +2285,9 @@ dependencies = [ [[package]] name = "wit-parser" -version = "0.11.1" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dcd022610436a1873e60bfdd9b407763f2404adf7d1cb57912c7ae4059e57a5" +checksum = "a39edca9abb16309def3843af73b58d47d243fe33a9ceee572446bcc57556b9a" dependencies = [ "anyhow", "id-arena", diff --git a/rust/plugin_wasm/Cargo.toml b/rust/plugin_wasm/Cargo.toml index 1f3f2050..0390d6d0 100644 --- a/rust/plugin_wasm/Cargo.toml +++ b/rust/plugin_wasm/Cargo.toml @@ -8,7 +8,10 @@ license = "MPL-2.0" [dependencies] anyhow = "1" nanoem-protobuf = { version = "35", path = "../protobuf" } -notify = "6" +notify = { version = "6", default-features = false, features = [ + "macos_kqueue", +] } +parking_lot = "0.12" 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/controller.rs b/rust/plugin_wasm/src/model/controller.rs new file mode 100644 index 00000000..03c42c0d --- /dev/null +++ b/rust/plugin_wasm/src/model/controller.rs @@ -0,0 +1,364 @@ +/* + Copyright (c) 2015-2023 hkrn All rights reserved + + 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, sync::Arc}; + +use anyhow::Result; +use notify::Watcher; +use parking_lot::Mutex; +use walkdir::WalkDir; +use wasmtime::{Engine, Linker}; +use wasmtime_wasi::WasiCtxBuilder; + +use crate::Store; + +use super::plugin::ModelIOPlugin; + +pub struct ModelIOPluginController { + plugins: Arc>>, + function_indices: Vec<(usize, i32, CString)>, + _watcher: notify::RecommendedWatcher, + plugin_index: Option, + failure_reason: Option, + recovery_suggestion: Option, +} + +impl ModelIOPluginController { + pub fn new( + plugins: Arc>>, + watcher: notify::RecommendedWatcher, + ) -> Self { + let function_indices = vec![]; + Self { + plugins, + function_indices, + _watcher: watcher, + plugin_index: None, + failure_reason: None, + recovery_suggestion: None, + } + } + pub fn from_path(path: &Path, callback: F) -> Result + where + F: Fn(&mut WasiCtxBuilder) + Copy + std::marker::Sync + std::marker::Send + 'static, + { + 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) => { + let create_plugin = |path: &Path| -> Result { + let bytes = std::fs::read(path)?; + let mut builder = WasiCtxBuilder::new(); + callback(&mut builder); + let data = builder.build(); + let store = Store::new(linker_inner.engine(), data); + ModelIOPlugin::new(&linker_inner, path, &bytes, store) + }; + match ev.kind { + notify::EventKind::Create(notify::event::CreateKind::File) => { + for path in ev.paths.iter() { + if path.ends_with(".wasm") { + match create_plugin(path) { + Ok(plugin) => { + tracing::info!( + path = ?path, + "WASM model I/O plugin is added", + ); + plugins_inner.lock().push(plugin); + } + Err(err) => { + tracing::warn!( + error = %err, + path = ?path, + "Cannot create WASM model I/O plugin", + ) + } + } + } + } + } + notify::EventKind::Modify(_) => { + for path in ev.paths.iter() { + let mut guard = plugins_inner.lock(); + if let Some(plugin_mut) = guard + .iter_mut() + .find(|plugin: &&mut ModelIOPlugin| plugin.path() == path) + { + match create_plugin(path) { + Ok(plugin) => { + tracing::info!( + path = ?path, + "WASM model I/O plugin is modified and reloaded", + ); + *plugin_mut = plugin + } + Err(err) => { + tracing::warn!( + error = %err, + path = ?path, + "Cannot reload WASM model I/O plugin", + ) + } + } + } + } + } + notify::EventKind::Remove(_) => { + tracing::info!( + path = ?ev.paths, + "WASM model I/O plugins are removed", + ); + plugins_inner + .lock() + .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(); + if filename.map(|s| s.ends_with(".wasm")).unwrap_or(false) { + let bytes = std::fs::read(entry.path())?; + let mut builder = WasiCtxBuilder::new(); + callback(&mut builder); + let data = builder.build(); + let store = Store::new(linker.engine(), data); + let path = entry.path(); + match ModelIOPlugin::new(&linker, path, &bytes, store) { + Ok(plugin) => { + watcher.watch(path, notify::RecursiveMode::NonRecursive)?; + plugins.lock().push(plugin); + tracing::debug!( + filename = filename.unwrap(), + "Loaded WASM model I/O plugin" + ); + } + Err(err) => { + tracing::warn!( + filename = filename.unwrap(), + error = %err, + "Cannot load WASM model I/O plugin", + ) + } + } + } + } + Ok(Self::new(plugins, watcher)) + } + pub fn initialize(&mut self) -> Result<()> { + self.plugins + .lock() + .iter_mut() + .try_for_each(|plugin| plugin.initialize()) + } + pub fn create(&mut self) -> Result<()> { + let mut guard = self.plugins.lock(); + 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()? { + let name = CString::new( + &format!("{}: {} ({})", name, plugin.function_name(index)?, version)[..], + )?; + self.function_indices.push((offset, index, name)); + } + } + Ok(()) + } + pub fn set_language(&mut self, value: i32) -> Result<()> { + self.plugins + .lock() + .iter_mut() + .try_for_each(|plugin| plugin.set_language(value)) + } + pub fn count_all_functions(&self) -> i32 { + self.function_indices.len() as i32 + } + pub fn function_name(&self, index: i32) -> Result<&CString> { + if let Some((_, _, name)) = self.function_indices.get(index as usize) { + Ok(name) + } else { + Err(anyhow::anyhow!("out of bound function index: {}", index)) + } + } + pub fn set_function(&mut self, index: i32) -> Result<()> { + if let Some((plugin_index, function_index, _)) = + self.function_indices.get(index as usize).cloned() + { + let result = self.plugins.lock()[plugin_index].set_function(function_index); + match result { + Ok(0) => { + self.plugin_index = Some(plugin_index); + Ok(()) + } + Ok(_) => self.set_failure(), + Err(err) => Err(err), + } + } else { + Err(anyhow::anyhow!("out of bound function index: {}", index)) + } + } + pub fn set_all_selected_vertex_indices(&mut self, data: &[i32]) -> Result<()> { + 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(|plugin| plugin.set_all_selected_material_indices(data)) + } + pub fn set_all_selected_bone_indices(&mut self, data: &[i32]) -> Result<()> { + 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(|plugin| plugin.set_all_selected_morph_indices(data)) + } + pub fn set_all_selected_label_indices(&mut self, data: &[i32]) -> Result<()> { + 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(|plugin| plugin.set_all_selected_rigid_body_indices(data)) + } + pub fn set_all_selected_joint_indices(&mut self, data: &[i32]) -> Result<()> { + 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(|plugin| plugin.set_all_selected_soft_body_indices(data)) + } + pub fn set_audio_description(&mut self, data: &[u8]) -> Result<()> { + self.current_plugin(|plugin| plugin.set_audio_description(data)) + } + pub fn set_camera_description(&mut self, data: &[u8]) -> Result<()> { + self.current_plugin(|plugin| plugin.set_camera_description(data)) + } + pub fn set_light_description(&mut self, data: &[u8]) -> Result<()> { + self.current_plugin(|plugin| plugin.set_light_description(data)) + } + pub fn set_audio_data(&mut self, data: &[u8]) -> Result<()> { + self.current_plugin(|plugin| plugin.set_audio_data(data)) + } + pub fn set_input_model_data(&mut self, bytes: &[u8]) -> Result<()> { + self.current_plugin(|plugin| plugin.set_input_model_data(bytes)) + } + pub fn execute(&mut self) -> Result<()> { + 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> { + 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<()> { + 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> { + 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, + id: &str, + data: &[u8], + reload: &mut bool, + ) -> Result<()> { + 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), + } + } + pub fn failure_reason(&self) -> Option { + self.failure_reason.clone() + } + pub fn recovery_suggestion(&self) -> Option { + self.recovery_suggestion.clone() + } + pub fn assign_failure_reason(&mut self, value: anyhow::Error) { + self.failure_reason = Some(value.to_string()); + } + pub fn destroy(&mut self) { + self.plugins + .lock() + .iter_mut() + .for_each(|plugin| plugin.destroy()) + } + pub fn terminate(&mut self) { + self.plugins + .lock() + .iter_mut() + .for_each(|plugin| plugin.terminate()) + } + #[allow(unused)] + pub(super) fn all_plugins_mut(&mut self) -> Arc>> { + Arc::clone(&self.plugins) + } + fn set_failure(&mut self) -> Result<()> { + let mut value = String::new(); + self.current_plugin(|plugin| { + value = plugin.failure_reason()?; + Ok(()) + })?; + if !value.is_empty() { + self.failure_reason = Some(value); + } + 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, cb: T) -> Result<()> + where + T: FnOnce(&mut ModelIOPlugin) -> Result<()>, + { + if let Some(plugin_index) = self.plugin_index { + let plugins = &mut self.plugins.lock(); + cb(&mut plugins[plugin_index]) + } else { + Err(anyhow::anyhow!("plugin is not set")) + } + } +} diff --git a/rust/plugin_wasm/src/model/core.rs b/rust/plugin_wasm/src/model/core.rs index d4ab1f64..5ff32280 100644 --- a/rust/plugin_wasm/src/model/core.rs +++ b/rust/plugin_wasm/src/model/core.rs @@ -8,12 +8,12 @@ use anyhow::Result; use crate::nanoem_application_plugin_status_t; -use super::plugin::ModelIOPluginController; - use std::ffi::{c_char, CStr}; use std::path::Path; use std::ptr::null; +use super::controller::ModelIOPluginController; + #[allow(non_camel_case_types)] pub struct nanoem_application_plugin_model_io_t { controller: ModelIOPluginController, diff --git a/rust/plugin_wasm/src/model/mod.rs b/rust/plugin_wasm/src/model/mod.rs index 1f919f66..f4acc020 100644 --- a/rust/plugin_wasm/src/model/mod.rs +++ b/rust/plugin_wasm/src/model/mod.rs @@ -5,6 +5,7 @@ */ mod c_api; +mod controller; mod core; mod plugin; diff --git a/rust/plugin_wasm/src/model/plugin.rs b/rust/plugin_wasm/src/model/plugin.rs index 54853227..abe362d5 100644 --- a/rust/plugin_wasm/src/model/plugin.rs +++ b/rust/plugin_wasm/src/model/plugin.rs @@ -4,17 +4,11 @@ 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, PathBuf}, - sync::{Arc, Mutex}, -}; +use std::path::{Path, PathBuf}; use anyhow::Result; -use notify::Watcher; -use walkdir::WalkDir; -use wasmtime::{AsContextMut, Engine, Instance, Linker, Module}; -use wasmtime_wasi::{WasiCtx, WasiCtxBuilder}; +use wasmtime::{AsContextMut, Instance, Linker, Module}; +use wasmtime_wasi::WasiCtx; use crate::{ inner_count_all_functions, inner_create_opaque, inner_destroy_opaque, inner_execute, @@ -383,337 +377,7 @@ impl ModelIOPlugin { &mut self.store, ) } - pub fn path(&self) -> &Path { + pub fn path(&self) -> &PathBuf { &self.path } } - -pub struct ModelIOPluginController { - plugins: Arc>>, - function_indices: Vec<(usize, i32, CString)>, - plugin_index: Option, - failure_reason: Option, - recovery_suggestion: Option, -} - -impl ModelIOPluginController { - pub fn new(plugins: Arc>>) -> Self { - let function_indices = vec![]; - Self { - plugins, - function_indices, - plugin_index: None, - failure_reason: None, - recovery_suggestion: None, - } - } - pub fn from_path(path: &Path, callback: F) -> Result - where - F: Fn(&mut WasiCtxBuilder) + Copy + std::marker::Sync + std::marker::Send + 'static, - { - 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) => { - let create_plugin = |path: &Path| -> Result { - let bytes = std::fs::read(path)?; - let mut builder = WasiCtxBuilder::new(); - callback(&mut builder); - let data = builder.build(); - let store = Store::new(linker_inner.engine(), data); - ModelIOPlugin::new(&linker_inner, path, &bytes, store) - }; - match ev.kind { - notify::EventKind::Create(notify::event::CreateKind::File) => { - for path in ev.paths.iter() { - if path.ends_with(".wasm") { - match create_plugin(path) { - Ok(plugin) => { - plugins_inner.lock().unwrap().push(plugin); - } - Err(err) => { - tracing::warn!( - error = %err, - path = ?path, - "Cannot create model WASM plugin", - ) - } - } - } - } - } - 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) - { - match create_plugin(path) { - 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(); - if filename.map(|s| s.ends_with(".wasm")).unwrap_or(false) { - let bytes = std::fs::read(entry.path())?; - let mut builder = WasiCtxBuilder::new(); - callback(&mut builder); - let data = builder.build(); - let store = Store::new(linker.engine(), data); - let path = entry.path(); - match ModelIOPlugin::new(&linker, path, &bytes, store) { - Ok(plugin) => { - watcher.watch(path, notify::RecursiveMode::NonRecursive)?; - plugins.lock().unwrap().push(plugin); - tracing::debug!(filename = filename.unwrap(), "Loaded model WASM plugin"); - } - Err(err) => { - tracing::warn!( - filename = filename.unwrap(), - error = %err, - "Cannot load model WASM plugin", - ) - } - } - } - } - Ok(Self::new(plugins)) - } - pub fn initialize(&mut self) -> Result<()> { - self.plugins - .lock() - .unwrap() - .iter_mut() - .try_for_each(|plugin| plugin.initialize()) - } - pub fn create(&mut self) -> Result<()> { - 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()? { - let name = CString::new( - &format!("{}: {} ({})", name, plugin.function_name(index)?, version)[..], - )?; - self.function_indices.push((offset, index, name)); - } - } - Ok(()) - } - pub fn set_language(&mut self, value: i32) -> Result<()> { - self.plugins - .lock() - .unwrap() - .iter_mut() - .try_for_each(|plugin| plugin.set_language(value)) - } - pub fn count_all_functions(&self) -> i32 { - self.function_indices.len() as i32 - } - pub fn function_name(&self, index: i32) -> Result<&CString> { - if let Some((_, _, name)) = self.function_indices.get(index as usize) { - Ok(name) - } else { - Err(anyhow::anyhow!("out of bound function index: {}", index)) - } - } - pub fn set_function(&mut self, index: i32) -> Result<()> { - if let Some((plugin_index, function_index, _)) = - self.function_indices.get(index as usize).cloned() - { - let result = self.plugins.lock().unwrap()[plugin_index].set_function(function_index); - match result { - Ok(0) => { - self.plugin_index = Some(plugin_index); - Ok(()) - } - Ok(_) => self.set_failure(), - Err(err) => Err(err), - } - } else { - Err(anyhow::anyhow!("out of bound function index: {}", index)) - } - } - pub fn set_all_selected_vertex_indices(&mut self, data: &[i32]) -> Result<()> { - 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(|plugin| plugin.set_all_selected_material_indices(data)) - } - pub fn set_all_selected_bone_indices(&mut self, data: &[i32]) -> Result<()> { - 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(|plugin| plugin.set_all_selected_morph_indices(data)) - } - pub fn set_all_selected_label_indices(&mut self, data: &[i32]) -> Result<()> { - 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(|plugin| plugin.set_all_selected_rigid_body_indices(data)) - } - pub fn set_all_selected_joint_indices(&mut self, data: &[i32]) -> Result<()> { - 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(|plugin| plugin.set_all_selected_soft_body_indices(data)) - } - pub fn set_audio_description(&mut self, data: &[u8]) -> Result<()> { - self.current_plugin(|plugin| plugin.set_audio_description(data)) - } - pub fn set_camera_description(&mut self, data: &[u8]) -> Result<()> { - self.current_plugin(|plugin| plugin.set_camera_description(data)) - } - pub fn set_light_description(&mut self, data: &[u8]) -> Result<()> { - self.current_plugin(|plugin| plugin.set_light_description(data)) - } - pub fn set_audio_data(&mut self, data: &[u8]) -> Result<()> { - self.current_plugin(|plugin| plugin.set_audio_data(data)) - } - pub fn set_input_model_data(&mut self, bytes: &[u8]) -> Result<()> { - self.current_plugin(|plugin| plugin.set_input_model_data(bytes)) - } - pub fn execute(&mut self) -> Result<()> { - 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> { - 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<()> { - 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> { - 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, - id: &str, - data: &[u8], - reload: &mut bool, - ) -> Result<()> { - 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), - } - } - pub fn failure_reason(&self) -> Option { - self.failure_reason.clone() - } - pub fn recovery_suggestion(&self) -> Option { - self.recovery_suggestion.clone() - } - pub fn assign_failure_reason(&mut self, value: anyhow::Error) { - self.failure_reason = Some(value.to_string()); - } - pub fn destroy(&mut self) { - 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) -> Arc>> { - Arc::clone(&self.plugins) - } - fn set_failure(&mut self) -> Result<()> { - let mut value = String::new(); - self.current_plugin(|plugin| { - value = plugin.failure_reason()?; - Ok(()) - })?; - if !value.is_empty() { - self.failure_reason = Some(value); - } - 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, cb: T) -> Result<()> - where - T: FnOnce(&mut ModelIOPlugin) -> Result<()>, - { - if let Some(plugin_index) = self.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/full.rs b/rust/plugin_wasm/src/model/test/full.rs index 5d6f1cbc..69eac2c8 100644 --- a/rust/plugin_wasm/src/model/test/full.rs +++ b/rust/plugin_wasm/src/model/test/full.rs @@ -12,7 +12,7 @@ use serde_json::json; use wasmtime_wasi::WasiFile; use crate::model::{ - plugin::ModelIOPluginController, + controller::ModelIOPluginController, test::{create_random_data, read_plugin_output, Pipe, PluginOutput}, }; diff --git a/rust/plugin_wasm/src/model/test/minimum.rs b/rust/plugin_wasm/src/model/test/minimum.rs index ae3690b1..627037b7 100644 --- a/rust/plugin_wasm/src/model/test/minimum.rs +++ b/rust/plugin_wasm/src/model/test/minimum.rs @@ -11,7 +11,7 @@ use pretty_assertions::assert_eq; use serde_json::json; use crate::model::{ - plugin::ModelIOPluginController, + controller::ModelIOPluginController, test::{create_random_data, read_plugin_output, Pipe, PluginOutput}, }; diff --git a/rust/plugin_wasm/src/model/test/mod.rs b/rust/plugin_wasm/src/model/test/mod.rs index 12599983..629086a4 100644 --- a/rust/plugin_wasm/src/model/test/mod.rs +++ b/rust/plugin_wasm/src/model/test/mod.rs @@ -9,10 +9,11 @@ use std::{ collections::{HashMap, HashSet}, env::current_dir, io::IoSliceMut, - sync::{Arc, Mutex}, + sync::Arc, }; use anyhow::Result; +use parking_lot::Mutex; use pretty_assertions::assert_eq; use rand::{thread_rng, Rng}; use serde_derive::{Deserialize, Serialize}; @@ -26,7 +27,7 @@ use wasmtime_wasi::{WasiCtxBuilder, WasiFile}; use crate::Store; -use super::plugin::{ModelIOPlugin, ModelIOPluginController}; +use super::{controller::ModelIOPluginController, plugin::ModelIOPlugin}; #[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)] struct PluginOutput { @@ -66,21 +67,21 @@ impl WasiFile for Pipe { inode: 0, filetype: self.get_filetype().await?, nlink: 0, - size: self.content.lock().unwrap().len() as _, + size: self.content.lock().len() as _, atim: None, mtim: None, ctim: None, }) } async fn read_vectored<'a>(&self, _bufs: &mut [std::io::IoSliceMut<'a>]) -> Result { - let guard = self.content.lock().unwrap(); + let guard = self.content.lock(); for slice in _bufs.iter_mut() { slice.copy_from_slice(guard.as_slice()); } Ok(guard.len() as _) } async fn write_vectored<'a>(&self, _bufs: &[std::io::IoSlice<'a>]) -> Result { - let mut guard = self.content.lock().unwrap(); + let mut guard = self.content.lock(); let size = guard.len(); for slice in _bufs.iter() { guard.extend(slice.iter()); @@ -131,9 +132,11 @@ 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().lock().unwrap().iter_mut() { + for plugin in controller.all_plugins_mut().lock().iter_mut() { plugin.create()?; names.push(plugin.name()?); } diff --git a/rust/plugin_wasm/src/motion/controller.rs b/rust/plugin_wasm/src/motion/controller.rs new file mode 100644 index 00000000..9d46317c --- /dev/null +++ b/rust/plugin_wasm/src/motion/controller.rs @@ -0,0 +1,371 @@ +/* + Copyright (c) 2015-2023 hkrn All rights reserved + + 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, sync::Arc}; + +use anyhow::Result; +use notify::Watcher; +use parking_lot::Mutex; +use walkdir::WalkDir; +use wasmtime::{Engine, Linker}; +use wasmtime_wasi::WasiCtxBuilder; + +use crate::Store; + +use super::plugin::MotionIOPlugin; + +pub struct MotionIOPluginController { + plugins: Arc>>, + function_indices: Vec<(usize, i32, CString)>, + _watcher: notify::RecommendedWatcher, + plugin_index: Option, + failure_reason: Option, + recovery_suggestion: Option, +} + +impl MotionIOPluginController { + pub fn new( + plugins: Arc>>, + watcher: notify::RecommendedWatcher, + ) -> Self { + let function_indices = vec![]; + Self { + plugins, + function_indices, + _watcher: watcher, + plugin_index: None, + failure_reason: None, + recovery_suggestion: None, + } + } + pub fn from_path(path: &Path, callback: F) -> Result + where + F: Fn(&mut WasiCtxBuilder) + Copy + std::marker::Sync + std::marker::Send + 'static, + { + 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) => { + let create_plugin = |path: &Path| -> Result { + let bytes = std::fs::read(path)?; + let mut builder = WasiCtxBuilder::new(); + callback(&mut builder); + let data = builder.build(); + let store = Store::new(linker_inner.engine(), data); + MotionIOPlugin::new(&linker_inner, path, &bytes, store) + }; + match ev.kind { + notify::EventKind::Create(notify::event::CreateKind::File) => { + for path in ev.paths.iter() { + if path.ends_with(".wasm") { + match create_plugin(path) { + Ok(plugin) => { + tracing::info!( + path = ?path, + "WASM motion I/O plugin is added", + ); + plugins_inner.lock().push(plugin); + } + Err(err) => { + tracing::warn!( + error = %err, + path = ?path, + "Cannot create WASM motion I/O plugin", + ) + } + } + } + } + } + notify::EventKind::Modify(_) => { + for path in ev.paths.iter() { + let mut guard = plugins_inner.lock(); + if let Some(plugin_mut) = guard + .iter_mut() + .find(|plugin: &&mut MotionIOPlugin| plugin.path() == path) + { + match create_plugin(path) { + Ok(plugin) => { + tracing::info!( + path = ?path, + "WASM motion I/O plugin is modified and reloaded", + ); + *plugin_mut = plugin + } + Err(err) => { + tracing::warn!( + error = %err, + path = ?path, + "Cannot reload WASM motion I/O plugin", + ) + } + } + } + } + } + notify::EventKind::Remove(_) => { + tracing::info!( + path = ?ev.paths, + "WASM motion I/O plugins are removed", + ); + plugins_inner + .lock() + .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(); + if filename.map(|s| s.ends_with(".wasm")).unwrap_or(false) { + let bytes = std::fs::read(entry.path())?; + let mut builder = WasiCtxBuilder::new(); + callback(&mut builder); + let data = builder.build(); + let store = Store::new(&engine, data); + match MotionIOPlugin::new(&linker, path, &bytes, store) { + Ok(plugin) => { + watcher.watch(path, notify::RecursiveMode::NonRecursive)?; + plugins.lock().push(plugin); + tracing::debug!( + filename = filename.unwrap(), + "Loaded WASM motion I/O plugin" + ); + } + Err(err) => { + tracing::warn!( + filename = filename.unwrap(), + err = %err, + "Cannot load WASM motion I/O plugin", + ) + } + } + } + } + Ok(Self::new(plugins, watcher)) + } + pub fn initialize(&mut self) -> Result<()> { + self.plugins + .lock() + .iter_mut() + .try_for_each(|plugin| plugin.initialize()) + } + pub fn create(&mut self) -> Result<()> { + let mut guard = self.plugins.lock(); + 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()? { + let name = CString::new( + &format!("{}: {} ({})", name, plugin.function_name(index)?, version)[..], + )?; + self.function_indices.push((offset, index, name)); + } + } + Ok(()) + } + pub fn set_language(&mut self, value: i32) -> Result<()> { + self.plugins + .lock() + .iter_mut() + .try_for_each(|plugin| plugin.set_language(value)) + } + pub fn count_all_functions(&self) -> i32 { + self.function_indices.len() as i32 + } + pub fn function_name(&self, index: i32) -> Result<&CString> { + if let Some((_, _, name)) = self.function_indices.get(index as usize) { + Ok(name) + } else { + Err(anyhow::anyhow!("out of bound function index: {}", index)) + } + } + pub fn set_function(&mut self, index: i32) -> Result<()> { + if let Some((plugin_index, function_index, _)) = + self.function_indices.get(index as usize).cloned() + { + let result = self.plugins.lock()[plugin_index].set_function(function_index); + match result { + Ok(0) => { + self.plugin_index = Some(plugin_index); + Ok(()) + } + Ok(_) => self.set_failure(), + Err(err) => Err(err), + } + } else { + Err(anyhow::anyhow!("out of bound function index: {}", index)) + } + } + pub fn set_all_selected_accessory_keyframes(&mut self, value: &[u32]) -> Result<()> { + 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(|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(|plugin| plugin.set_all_selected_camera_keyframes(value)) + } + pub fn set_all_selected_light_keyframes(&mut self, value: &[u32]) -> Result<()> { + 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(|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(|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(|plugin| plugin.set_all_selected_self_shadow_keyframes(value)) + } + pub fn set_audio_description(&mut self, data: &[u8]) -> Result<()> { + self.current_plugin(|plugin| plugin.set_audio_description(data)) + } + pub fn set_camera_description(&mut self, data: &[u8]) -> Result<()> { + self.current_plugin(|plugin| plugin.set_camera_description(data)) + } + pub fn set_light_description(&mut self, data: &[u8]) -> Result<()> { + self.current_plugin(|plugin| plugin.set_light_description(data)) + } + pub fn set_audio_data(&mut self, data: &[u8]) -> Result<()> { + self.current_plugin(|plugin| plugin.set_audio_data(data)) + } + pub fn set_input_model_data(&mut self, bytes: &[u8]) -> Result<()> { + self.current_plugin(|plugin| plugin.set_input_model_data(bytes)) + } + pub fn set_input_motion_data(&mut self, bytes: &[u8]) -> Result<()> { + self.current_plugin(|plugin| plugin.set_input_motion_data(bytes)) + } + pub fn execute(&mut self) -> Result<()> { + 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> { + 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<()> { + 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> { + 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, + id: &str, + data: &[u8], + reload: &mut bool, + ) -> Result<()> { + 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), + } + } + pub fn failure_reason(&self) -> Option { + self.failure_reason.clone() + } + pub fn recovery_suggestion(&self) -> Option { + self.recovery_suggestion.clone() + } + pub fn assign_failure_reason(&mut self, value: anyhow::Error) { + self.failure_reason = Some(value.to_string()); + } + pub fn destroy(&mut self) { + self.plugins + .lock() + .iter_mut() + .for_each(|plugin| plugin.destroy()) + } + pub fn terminate(&mut self) { + self.plugins + .lock() + .iter_mut() + .for_each(|plugin| plugin.terminate()) + } + #[allow(unused)] + pub(super) fn all_plugins_mut(&mut self) -> Arc>> { + Arc::clone(&self.plugins) + } + fn set_failure(&mut self) -> Result<()> { + let mut value = String::new(); + self.current_plugin(|plugin| { + value = plugin.failure_reason()?; + Ok(()) + })?; + if !value.is_empty() { + self.failure_reason = Some(value); + } + 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, cb: T) -> Result<()> + where + T: FnOnce(&mut MotionIOPlugin) -> Result<()>, + { + if let Some(plugin_index) = self.plugin_index { + let plugins = &mut self.plugins.lock(); + cb(&mut plugins[plugin_index]) + } else { + Err(anyhow::anyhow!("plugin is not set")) + } + } +} diff --git a/rust/plugin_wasm/src/motion/core.rs b/rust/plugin_wasm/src/motion/core.rs index 8dcfc00b..f41a62ab 100644 --- a/rust/plugin_wasm/src/motion/core.rs +++ b/rust/plugin_wasm/src/motion/core.rs @@ -8,12 +8,12 @@ use anyhow::Result; use crate::nanoem_application_plugin_status_t; -use super::plugin::MotionIOPluginController; - use std::ffi::{c_char, CStr}; use std::path::Path; use std::ptr::null; +use super::controller::MotionIOPluginController; + #[allow(non_camel_case_types)] pub struct nanoem_application_plugin_motion_io_t { controller: MotionIOPluginController, diff --git a/rust/plugin_wasm/src/motion/mod.rs b/rust/plugin_wasm/src/motion/mod.rs index 1f919f66..f4acc020 100644 --- a/rust/plugin_wasm/src/motion/mod.rs +++ b/rust/plugin_wasm/src/motion/mod.rs @@ -5,6 +5,7 @@ */ mod c_api; +mod controller; mod core; mod plugin; diff --git a/rust/plugin_wasm/src/motion/plugin.rs b/rust/plugin_wasm/src/motion/plugin.rs index 1eefffd8..f90c0829 100644 --- a/rust/plugin_wasm/src/motion/plugin.rs +++ b/rust/plugin_wasm/src/motion/plugin.rs @@ -5,18 +5,13 @@ */ 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::{WasiCtx, WasiCtxBuilder}; +use wasmtime::{AsContextMut, Instance, Linker, Module}; +use wasmtime_wasi::WasiCtx; use crate::{ allocate_byte_array_with_data, allocate_status_ptr, inner_count_all_functions, @@ -433,344 +428,7 @@ impl MotionIOPlugin { &mut self.store, ) } - pub fn path(&self) -> &Path { + pub fn path(&self) -> &PathBuf { &self.path } } - -pub struct MotionIOPluginController { - plugins: Arc>>, - function_indices: Vec<(usize, i32, CString)>, - plugin_index: Option, - failure_reason: Option, - recovery_suggestion: Option, -} - -impl MotionIOPluginController { - pub fn new(plugins: Arc>>) -> Self { - let function_indices = vec![]; - Self { - plugins, - function_indices, - plugin_index: None, - failure_reason: None, - recovery_suggestion: None, - } - } - pub fn from_path(path: &Path, callback: F) -> Result - where - F: Fn(&mut WasiCtxBuilder) + Copy + std::marker::Sync + std::marker::Send + 'static, - { - 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) => { - let create_plugin = |path: &Path| -> Result { - let bytes = std::fs::read(path)?; - let mut builder = WasiCtxBuilder::new(); - callback(&mut builder); - let data = builder.build(); - let store = Store::new(linker_inner.engine(), data); - MotionIOPlugin::new(&linker_inner, path, &bytes, store) - }; - match ev.kind { - notify::EventKind::Create(notify::event::CreateKind::File) => { - for path in ev.paths.iter() { - if path.ends_with(".wasm") { - match create_plugin(path) { - Ok(plugin) => { - plugins_inner.lock().unwrap().push(plugin); - } - Err(err) => { - tracing::warn!( - error = %err, - path = ?path, - "Cannot create motion WASM plugin", - ) - } - } - } - } - } - 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) - { - match create_plugin(path) { - 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(); - if filename.map(|s| s.ends_with(".wasm")).unwrap_or(false) { - let bytes = std::fs::read(entry.path())?; - let mut builder = WasiCtxBuilder::new(); - callback(&mut builder); - let data = builder.build(); - let store = Store::new(&engine, data); - match MotionIOPlugin::new(&linker, path, &bytes, store) { - Ok(plugin) => { - watcher.watch(path, notify::RecursiveMode::NonRecursive)?; - plugins.lock().unwrap().push(plugin); - tracing::debug!(filename = filename.unwrap(), "Loaded motion WASM plugin"); - } - Err(err) => { - warn!( - "Cannot load motion WASM plugin {}: {}", - filename.unwrap(), - err - ) - } - } - } - } - Ok(Self::new(plugins)) - } - pub fn initialize(&mut self) -> Result<()> { - self.plugins - .lock() - .unwrap() - .iter_mut() - .try_for_each(|plugin| plugin.initialize()) - } - pub fn create(&mut self) -> Result<()> { - 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()? { - let name = CString::new( - &format!("{}: {} ({})", name, plugin.function_name(index)?, version)[..], - )?; - self.function_indices.push((offset, index, name)); - } - } - Ok(()) - } - pub fn set_language(&mut self, value: i32) -> Result<()> { - self.plugins - .lock() - .unwrap() - .iter_mut() - .try_for_each(|plugin| plugin.set_language(value)) - } - pub fn count_all_functions(&self) -> i32 { - self.function_indices.len() as i32 - } - pub fn function_name(&self, index: i32) -> Result<&CString> { - if let Some((_, _, name)) = self.function_indices.get(index as usize) { - Ok(name) - } else { - Err(anyhow::anyhow!("out of bound function index: {}", index)) - } - } - pub fn set_function(&mut self, index: i32) -> Result<()> { - if let Some((plugin_index, function_index, _)) = - self.function_indices.get(index as usize).cloned() - { - let result = self.plugins.lock().unwrap()[plugin_index].set_function(function_index); - match result { - Ok(0) => { - self.plugin_index = Some(plugin_index); - Ok(()) - } - Ok(_) => self.set_failure(), - Err(err) => Err(err), - } - } else { - Err(anyhow::anyhow!("out of bound function index: {}", index)) - } - } - pub fn set_all_selected_accessory_keyframes(&mut self, value: &[u32]) -> Result<()> { - 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(|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(|plugin| plugin.set_all_selected_camera_keyframes(value)) - } - pub fn set_all_selected_light_keyframes(&mut self, value: &[u32]) -> Result<()> { - 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(|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(|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(|plugin| plugin.set_all_selected_self_shadow_keyframes(value)) - } - pub fn set_audio_description(&mut self, data: &[u8]) -> Result<()> { - self.current_plugin(|plugin| plugin.set_audio_description(data)) - } - pub fn set_camera_description(&mut self, data: &[u8]) -> Result<()> { - self.current_plugin(|plugin| plugin.set_camera_description(data)) - } - pub fn set_light_description(&mut self, data: &[u8]) -> Result<()> { - self.current_plugin(|plugin| plugin.set_light_description(data)) - } - pub fn set_audio_data(&mut self, data: &[u8]) -> Result<()> { - self.current_plugin(|plugin| plugin.set_audio_data(data)) - } - pub fn set_input_model_data(&mut self, bytes: &[u8]) -> Result<()> { - self.current_plugin(|plugin| plugin.set_input_model_data(bytes)) - } - pub fn set_input_motion_data(&mut self, bytes: &[u8]) -> Result<()> { - self.current_plugin(|plugin| plugin.set_input_motion_data(bytes)) - } - pub fn execute(&mut self) -> Result<()> { - 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> { - 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<()> { - 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> { - 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, - id: &str, - data: &[u8], - reload: &mut bool, - ) -> Result<()> { - 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), - } - } - pub fn failure_reason(&self) -> Option { - self.failure_reason.clone() - } - pub fn recovery_suggestion(&self) -> Option { - self.recovery_suggestion.clone() - } - pub fn assign_failure_reason(&mut self, value: anyhow::Error) { - self.failure_reason = Some(value.to_string()); - } - pub fn destroy(&mut self) { - 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) -> Arc>> { - Arc::clone(&self.plugins) - } - fn set_failure(&mut self) -> Result<()> { - let mut value = String::new(); - self.current_plugin(|plugin| { - value = plugin.failure_reason()?; - Ok(()) - })?; - if !value.is_empty() { - self.failure_reason = Some(value); - } - 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, cb: T) -> Result<()> - where - T: FnOnce(&mut MotionIOPlugin) -> Result<()>, - { - if let Some(plugin_index) = self.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/full.rs b/rust/plugin_wasm/src/motion/test/full.rs index a51735f6..a9b6888f 100644 --- a/rust/plugin_wasm/src/motion/test/full.rs +++ b/rust/plugin_wasm/src/motion/test/full.rs @@ -11,7 +11,7 @@ use pretty_assertions::assert_eq; use serde_json::json; use crate::motion::{ - plugin::MotionIOPluginController, + controller::MotionIOPluginController, test::{create_random_data, inner_create_controller, read_plugin_output, Pipe, PluginOutput}, }; diff --git a/rust/plugin_wasm/src/motion/test/minimum.rs b/rust/plugin_wasm/src/motion/test/minimum.rs index 7fabd575..d3d0e323 100644 --- a/rust/plugin_wasm/src/motion/test/minimum.rs +++ b/rust/plugin_wasm/src/motion/test/minimum.rs @@ -11,7 +11,7 @@ use pretty_assertions::assert_eq; use serde_json::json; use crate::motion::{ - plugin::MotionIOPluginController, + controller::MotionIOPluginController, test::{create_random_data, read_plugin_output, Pipe, PluginOutput}, }; diff --git a/rust/plugin_wasm/src/motion/test/mod.rs b/rust/plugin_wasm/src/motion/test/mod.rs index a244438e..917158eb 100644 --- a/rust/plugin_wasm/src/motion/test/mod.rs +++ b/rust/plugin_wasm/src/motion/test/mod.rs @@ -9,10 +9,11 @@ use std::{ collections::{HashMap, HashSet}, env::current_dir, io::IoSliceMut, - sync::{Arc, Mutex}, + sync::Arc, }; use anyhow::Result; +use parking_lot::Mutex; use pretty_assertions::assert_eq; use rand::{thread_rng, Rng}; use serde_derive::{Deserialize, Serialize}; @@ -24,9 +25,9 @@ use wasi_common::{ use wasmtime::{Engine, Linker}; use wasmtime_wasi::{WasiCtxBuilder, WasiFile}; -use crate::Store; +use crate::{motion::controller::MotionIOPluginController, Store}; -use super::plugin::{MotionIOPlugin, MotionIOPluginController}; +use super::plugin::MotionIOPlugin; #[derive(Debug, Default, PartialEq, Eq, Serialize, Deserialize)] struct PluginOutput { @@ -66,21 +67,21 @@ impl WasiFile for Pipe { inode: 0, filetype: self.get_filetype().await?, nlink: 0, - size: self.content.lock().unwrap().len() as _, + size: self.content.lock().len() as _, atim: None, mtim: None, ctim: None, }) } async fn read_vectored<'a>(&self, _bufs: &mut [std::io::IoSliceMut<'a>]) -> Result { - let guard = self.content.lock().unwrap(); + let guard = self.content.lock(); for slice in _bufs.iter_mut() { slice.copy_from_slice(guard.as_slice()); } Ok(guard.len() as u64) } async fn write_vectored<'a>(&self, _bufs: &[std::io::IoSlice<'a>]) -> Result { - let mut guard = self.content.lock().unwrap(); + let mut guard = self.content.lock(); let size = guard.len(); for slice in _bufs.iter() { guard.extend(slice.iter()); @@ -133,9 +134,11 @@ fn inner_create_controller( 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, - ])))) + let watcher = notify::recommended_watcher(|_res| {})?; + Ok(MotionIOPluginController::new( + Arc::new(Mutex::new(vec![plugin])), + watcher, + )) } #[test] @@ -147,7 +150,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().lock().unwrap().iter_mut() { + for plugin in controller.all_plugins_mut().lock().iter_mut() { plugin.create()?; names.push(plugin.name()?); }