//! Nyash v2 Plugin Loader //! //! cfg/features で2パスを提供: //! - enabled: plugins feature 有効 かつ 非wasm32 ターゲット //! - stub : それ以外(WASMやplugins無効) #[cfg(all(feature = "plugins", not(target_arch = "wasm32")))] mod enabled { use crate::bid::{BidResult, BidError}; use crate::box_trait::{NyashBox, BoxCore, StringBox, IntegerBox}; use crate::config::nyash_toml_v2::{NyashConfigV2, LibraryDefinition}; use std::collections::HashMap; use std::sync::{Arc, RwLock}; // use std::ffi::c_void; // unused use std::any::Any; use once_cell::sync::Lazy; use crate::runtime::leak_tracker; fn dbg_on() -> bool { std::env::var("NYASH_DEBUG_PLUGIN").unwrap_or_default() == "1" } /// Loaded plugin information pub struct LoadedPluginV2 { /// Library handle _lib: Arc, /// Box types provided by this plugin #[allow(dead_code)] box_types: Vec, /// Optional init function #[allow(dead_code)] init_fn: Option i32>, /// Required invoke function invoke_fn: unsafe extern "C" fn(u32, u32, u32, *const u8, usize, *mut u8, *mut usize) -> i32, } // (moved: public constructor wrapper is declared after the enabled module) /// v2 Plugin Box wrapper - temporary implementation #[derive(Debug)] pub struct PluginHandleInner { pub type_id: u32, pub invoke_fn: unsafe extern "C" fn(u32, u32, u32, *const u8, usize, *mut u8, *mut usize) -> i32, pub instance_id: u32, pub fini_method_id: Option, finalized: std::sync::atomic::AtomicBool, } // Loaded box spec from plugins//nyash_box.toml #[derive(Debug, Clone, Default)] struct LoadedBoxSpec { type_id: Option, methods: HashMap, fini_method_id: Option, } #[derive(Debug, Clone, Copy)] struct MethodSpec { method_id: u32, returns_result: bool } impl Drop for PluginHandleInner { fn drop(&mut self) { // Finalize exactly once when the last shared handle is dropped if let Some(fini_id) = self.fini_method_id { if !self.finalized.swap(true, std::sync::atomic::Ordering::SeqCst) { let tlv_args: [u8; 4] = [1, 0, 0, 0]; let mut out: [u8; 4] = [0; 4]; let mut out_len: usize = out.len(); unsafe { (self.invoke_fn)( self.type_id, fini_id, self.instance_id, tlv_args.as_ptr(), tlv_args.len(), out.as_mut_ptr(), &mut out_len, ); } } } } } impl PluginHandleInner { /// Explicitly finalize this handle now (idempotent) pub fn finalize_now(&self) { if let Some(fini_id) = self.fini_method_id { if !self.finalized.swap(true, std::sync::atomic::Ordering::SeqCst) { leak_tracker::finalize_plugin("PluginBox", self.instance_id); let tlv_args: [u8; 4] = [1, 0, 0, 0]; let mut out: [u8; 4] = [0; 4]; let mut out_len: usize = out.len(); unsafe { (self.invoke_fn)( self.type_id, fini_id, self.instance_id, tlv_args.as_ptr(), tlv_args.len(), out.as_mut_ptr(), &mut out_len, ); } } } } } /// Helper to construct a PluginBoxV2 from raw ids and invoke pointer safely pub fn make_plugin_box_v2_inner(box_type: String, type_id: u32, instance_id: u32, invoke_fn: unsafe extern "C" fn(u32,u32,u32,*const u8,usize,*mut u8,*mut usize) -> i32) -> PluginBoxV2 { PluginBoxV2 { box_type, inner: std::sync::Arc::new(PluginHandleInner { type_id, invoke_fn, instance_id, fini_method_id: None, finalized: std::sync::atomic::AtomicBool::new(false) }) } } #[derive(Debug, Clone)] pub struct PluginBoxV2 { pub box_type: String, pub inner: std::sync::Arc, } impl BoxCore for PluginBoxV2 { fn box_id(&self) -> u64 { self.inner.instance_id as u64 } fn parent_type_id(&self) -> Option { None } fn fmt_box(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{}({})", self.box_type, self.inner.instance_id) } fn as_any(&self) -> &dyn Any { self } fn as_any_mut(&mut self) -> &mut dyn Any { self } } impl NyashBox for PluginBoxV2 { fn is_identity(&self) -> bool { true } fn type_name(&self) -> &'static str { // Return the actual box type name for proper method dispatch match self.box_type.as_str() { "FileBox" => "FileBox", _ => "PluginBoxV2", } } fn clone_box(&self) -> Box { if dbg_on() { eprintln!("🔍 DEBUG: PluginBoxV2::clone_box called for {} (id={})", self.box_type, self.inner.instance_id); } // Clone means creating a new instance by calling birth() on the plugin let mut output_buffer = vec![0u8; 1024]; let mut output_len = output_buffer.len(); let tlv_args = [1u8, 0, 0, 0]; // version=1, argc=0 let result = unsafe { (self.inner.invoke_fn)( self.inner.type_id, 0, // method_id=0 (birth) 0, // instance_id=0 (static call) tlv_args.as_ptr(), tlv_args.len(), output_buffer.as_mut_ptr(), &mut output_len, ) }; if result == 0 && output_len >= 4 { let new_instance_id = u32::from_le_bytes([ output_buffer[0], output_buffer[1], output_buffer[2], output_buffer[3] ]); if dbg_on() { eprintln!("🎉 clone_box success: created new {} instance_id={}", self.box_type, new_instance_id); } Box::new(PluginBoxV2 { box_type: self.box_type.clone(), inner: std::sync::Arc::new(PluginHandleInner { type_id: self.inner.type_id, invoke_fn: self.inner.invoke_fn, instance_id: new_instance_id, fini_method_id: self.inner.fini_method_id, finalized: std::sync::atomic::AtomicBool::new(false), }), }) } else { if dbg_on() { eprintln!("❌ clone_box failed: birth() returned error code {}", result); } Box::new(StringBox::new(format!("Clone failed for {}", self.box_type))) } } fn to_string_box(&self) -> crate::box_trait::StringBox { StringBox::new(format!("{}({})", self.box_type, self.inner.instance_id)) } fn equals(&self, _other: &dyn NyashBox) -> crate::box_trait::BoolBox { crate::box_trait::BoolBox::new(false) } fn share_box(&self) -> Box { if dbg_on() { eprintln!("🔍 DEBUG: PluginBoxV2::share_box called for {} (id={})", self.box_type, self.inner.instance_id); } // Share means returning a new Box with the same instance_id Box::new(PluginBoxV2 { box_type: self.box_type.clone(), inner: self.inner.clone(), }) } } impl PluginBoxV2 { pub fn instance_id(&self) -> u32 { self.inner.instance_id } pub fn finalize_now(&self) { self.inner.finalize_now() } pub fn is_finalized(&self) -> bool { self.inner.finalized.load(std::sync::atomic::Ordering::SeqCst) } } /// Public helper to construct a PluginBoxV2 from raw parts (for VM/JIT integration) pub fn construct_plugin_box( box_type: String, type_id: u32, invoke_fn: unsafe extern "C" fn(u32, u32, u32, *const u8, usize, *mut u8, *mut usize) -> i32, instance_id: u32, fini_method_id: Option, ) -> PluginBoxV2 { PluginBoxV2 { box_type, inner: std::sync::Arc::new(PluginHandleInner { type_id, invoke_fn, instance_id, fini_method_id, finalized: std::sync::atomic::AtomicBool::new(false), }), } } /// Plugin loader v2 pub struct PluginLoaderV2 { /// Loaded plugins (library name -> plugin info) plugins: RwLock>>, /// Configuration pub config: Option, /// Path to the loaded nyash.toml (absolute), used for consistent re-reads config_path: Option, /// Singleton instances: (lib_name, box_type) -> shared handle singletons: RwLock>>, /// Loaded per-plugin box specs from nyash_box.toml: (lib_name, box_type) -> spec box_specs: RwLock>, } impl PluginLoaderV2 { fn find_box_by_type_id<'a>(&'a self, config: &'a NyashConfigV2, toml_value: &'a toml::Value, type_id: u32) -> Option<(&'a str, &'a str)> { for (lib_name, lib_def) in &config.libraries { for box_name in &lib_def.boxes { if let Some(box_conf) = config.get_box_config(lib_name, box_name, toml_value) { if box_conf.type_id == type_id { return Some((lib_name.as_str(), box_name.as_str())); } } } } None } /// Create new loader pub fn new() -> Self { Self { plugins: RwLock::new(HashMap::new()), config: None, config_path: None, singletons: RwLock::new(HashMap::new()), box_specs: RwLock::new(HashMap::new()), } } /// Load configuration from nyash.toml pub fn load_config(&mut self, config_path: &str) -> BidResult<()> { // Canonicalize path for later re-reads let canonical = std::fs::canonicalize(config_path) .map(|p| p.to_string_lossy().to_string()) .unwrap_or_else(|_| config_path.to_string()); self.config_path = Some(canonical.clone()); self.config = Some(NyashConfigV2::from_file(&canonical) .map_err(|e| { eprintln!("Failed to load config: {}", e); BidError::PluginError })?); // Bump cache versions for all box types (config reload may change method layout) if let Some(cfg) = self.config.as_ref() { let mut labels: Vec = Vec::new(); for (_lib, def) in &cfg.libraries { for bt in &def.boxes { labels.push(format!("BoxRef:{}", bt)); } } crate::runtime::cache_versions::bump_many(&labels); } Ok(()) } /// Load all plugins from config pub fn load_all_plugins(&self) -> BidResult<()> { let config = self.config.as_ref() .ok_or(BidError::PluginError)?; // Load legacy libraries (backward compatible) for (lib_name, lib_def) in &config.libraries { if let Err(e) = self.load_plugin(lib_name, lib_def) { eprintln!("Warning: Failed to load plugin {}: {:?}", lib_name, e); } } // Load new-style plugins from [plugins] map (name -> root dir) for (plugin_name, root) in &config.plugins { // Synthesize a LibraryDefinition from plugin spec (nyash_box.toml) if present; otherwise minimal let mut boxes: Vec = Vec::new(); let spec_path = std::path::Path::new(root).join("nyash_box.toml"); // Optional artifact path from spec let mut artifact_override: Option = None; if let Ok(txt) = std::fs::read_to_string(&spec_path) { if let Ok(val) = txt.parse::() { if let Some(prov) = val.get("provides").and_then(|t| t.get("boxes")).and_then(|a| a.as_array()) { for it in prov.iter() { if let Some(s) = it.as_str() { boxes.push(s.to_string()); } } } // Artifacts section: choose OS-specific path template if provided if let Some(arts) = val.get("artifacts").and_then(|t| t.as_table()) { let key = if cfg!(target_os = "windows") { "windows" } else if cfg!(target_os = "macos") { "macos" } else { "linux" }; if let Some(p) = arts.get(key).and_then(|v| v.as_str()) { artifact_override = Some(p.to_string()); } } // Build per-box specs for bname in &boxes { let mut spec = LoadedBoxSpec::default(); if let Some(tb) = val.get(bname) { if let Some(tid) = tb.get("type_id").and_then(|v| v.as_integer()) { spec.type_id = Some(tid as u32); } if let Some(lc) = tb.get("lifecycle") { if let Some(fini) = lc.get("fini").and_then(|m| m.get("id")).and_then(|v| v.as_integer()) { spec.fini_method_id = Some(fini as u32); } } if let Some(mtbl) = tb.get("methods").and_then(|t| t.as_table()) { for (mname, md) in mtbl.iter() { if let Some(mid) = md.get("id").and_then(|v| v.as_integer()) { let rr = md.get("returns_result").and_then(|v| v.as_bool()).unwrap_or(false); spec.methods.insert(mname.clone(), MethodSpec { method_id: mid as u32, returns_result: rr }); } } } } if spec.type_id.is_some() || !spec.methods.is_empty() { self.box_specs.write().unwrap().insert((plugin_name.clone(), bname.clone()), spec); } } } } // Path heuristic: use artifact override if present, else "/" let synth_path = if let Some(p) = artifact_override { let jp = std::path::Path::new(root).join(p); jp.to_string_lossy().to_string() } else { std::path::Path::new(root).join(plugin_name).to_string_lossy().to_string() }; let lib_def = LibraryDefinition { boxes: boxes.clone(), path: synth_path }; if let Err(e) = self.load_plugin(plugin_name, &lib_def) { eprintln!("Warning: Failed to load plugin {} from [plugins]: {:?}", plugin_name, e); } } // Strict validation: central [box_types] vs plugin spec type_id let strict = std::env::var("NYASH_PLUGIN_STRICT").ok().as_deref() == Some("1"); if !config.box_types.is_empty() { for ((lib, bname), spec) in self.box_specs.read().unwrap().iter() { if let Some(cid) = config.box_types.get(bname) { if let Some(tid) = spec.type_id { if tid != *cid { eprintln!("[PluginLoaderV2] type_id mismatch for {} (plugin={}): central={}, spec={}", bname, lib, cid, tid); if strict { return Err(BidError::PluginError); } } } } } } // Pre-birth singletons configured in nyash.toml let cfg_path = self.config_path.as_ref().map(|s| s.as_str()).unwrap_or("nyash.toml"); let toml_content = std::fs::read_to_string(cfg_path).map_err(|_| BidError::PluginError)?; let toml_value: toml::Value = toml::from_str(&toml_content).map_err(|_| BidError::PluginError)?; for (lib_name, lib_def) in &config.libraries { for box_name in &lib_def.boxes { if let Some(bc) = config.get_box_config(lib_name, box_name, &toml_value) { if bc.singleton { let _ = self.ensure_singleton_handle(lib_name, box_name); } } } } Ok(()) } /// Construct a PluginBoxV2 from an existing (type_id, instance_id) pair. /// Used by reverse host API to materialize PluginHandle(tag=8) as a VMValue::BoxRef. pub fn construct_existing_instance(&self, type_id: u32, instance_id: u32) -> Option> { let config = self.config.as_ref()?; let cfg_path = self.config_path.as_ref()?; let toml_content = std::fs::read_to_string(cfg_path).ok()?; let toml_value: toml::Value = toml::from_str(&toml_content).ok()?; let (lib_name, box_type) = self.find_box_by_type_id(config, &toml_value, type_id)?; let plugins = self.plugins.read().ok()?; let plugin = plugins.get(lib_name)?.clone(); // fini method id from spec or config let fini_method_id = if let Some(spec) = self.box_specs.read().ok()?.get(&(lib_name.to_string(), box_type.to_string())) { spec.fini_method_id } else { let box_conf = config.get_box_config(lib_name, box_type, &toml_value)?; box_conf.methods.get("fini").map(|m| m.method_id) }; let bx = construct_plugin_box(box_type.to_string(), type_id, plugin.invoke_fn, instance_id, fini_method_id); Some(Box::new(bx)) } fn find_lib_name_for_box(&self, box_type: &str) -> Option { if let Some(cfg) = &self.config { if let Some((name, _)) = cfg.find_library_for_box(box_type) { return Some(name.to_string()); } } for ((lib, b), _) in self.box_specs.read().unwrap().iter() { if b == box_type { return Some(lib.clone()); } } None } /// Ensure a singleton handle is created and stored fn ensure_singleton_handle(&self, lib_name: &str, box_type: &str) -> BidResult<()> { // Fast path: already present if self.singletons.read().unwrap().contains_key(&(lib_name.to_string(), box_type.to_string())) { return Ok(()); } // Create via birth let cfg_path = self.config_path.as_ref().map(|s| s.as_str()).unwrap_or("nyash.toml"); let toml_content = std::fs::read_to_string(cfg_path).map_err(|_| BidError::PluginError)?; let toml_value: toml::Value = toml::from_str(&toml_content).map_err(|_| BidError::PluginError)?; let config = self.config.as_ref().ok_or(BidError::PluginError)?; let plugins = self.plugins.read().unwrap(); let plugin = plugins.get(lib_name).ok_or(BidError::PluginError)?; // Prefer spec-loaded type_id let type_id = if let Some(spec) = self.box_specs.read().unwrap().get(&(lib_name.to_string(), box_type.to_string())) { spec.type_id.unwrap_or_else(|| config.box_types.get(box_type).copied().unwrap_or(0)) } else { let box_conf = config.get_box_config(lib_name, box_type, &toml_value).ok_or(BidError::InvalidType)?; box_conf.type_id }; // Call birth let mut output_buffer = vec![0u8; 1024]; let mut output_len = output_buffer.len(); let tlv_args = crate::runtime::plugin_ffi_common::encode_empty_args(); let birth_result = unsafe { (plugin.invoke_fn)(type_id, 0, 0, tlv_args.as_ptr(), tlv_args.len(), output_buffer.as_mut_ptr(), &mut output_len) }; if birth_result != 0 || output_len < 4 { return Err(BidError::PluginError); } let instance_id = u32::from_le_bytes([output_buffer[0], output_buffer[1], output_buffer[2], output_buffer[3]]); let fini_id = if let Some(spec) = self.box_specs.read().unwrap().get(&(lib_name.to_string(), box_type.to_string())) { spec.fini_method_id } else { let box_conf = config.get_box_config(lib_name, box_type, &toml_value).ok_or(BidError::InvalidType)?; box_conf.methods.get("fini").map(|m| m.method_id) }; let handle = std::sync::Arc::new(PluginHandleInner { type_id, invoke_fn: plugin.invoke_fn, instance_id, fini_method_id: fini_id, finalized: std::sync::atomic::AtomicBool::new(false), }); self.singletons.write().unwrap().insert((lib_name.to_string(), box_type.to_string()), handle); // bump version for this box type to invalidate caches crate::runtime::cache_versions::bump_version(&format!("BoxRef:{}", box_type)); Ok(()) } /// Perform an external host call (env.* namespace) or return an error if unsupported /// Returns Some(Box) for a value result, or None for void-like calls pub fn extern_call( &self, iface_name: &str, method_name: &str, args: &[Box], ) -> BidResult>> { match (iface_name, method_name) { ("env.console", "log") => { for a in args { println!("{}", a.to_string_box().value); } Ok(None) } ("env.task", "cancelCurrent") => { let tok = crate::runtime::global_hooks::current_group_token(); tok.cancel(); Ok(None) } ("env.task", "currentToken") => { // Return a TokenBox representing current task group's cancellation token let tok = crate::runtime::global_hooks::current_group_token(); let tb = crate::boxes::token_box::TokenBox::from_token(tok); Ok(Some(Box::new(tb))) } ("env.debug", "trace") => { // Minimal debug trace; prints to stderr when enabled if std::env::var("NYASH_DEBUG_TRACE").ok().as_deref() == Some("1") { for a in args { eprintln!("[debug.trace] {}", a.to_string_box().value); } } Ok(None) } ("env.runtime", "checkpoint") => { // Safepoint + scheduler poll via global hooks if crate::config::env::runtime_checkpoint_trace() { eprintln!("[runtime.checkpoint] reached"); } crate::runtime::global_hooks::safepoint_and_poll(); Ok(None) } // Future/Await bridge (scaffold): maps MIR Future* to Box operations ("env.future", "new") | ("env.future", "birth") => { // new(value) -> FutureBox(set to value) let fut = crate::boxes::future::FutureBox::new(); if let Some(v) = args.get(0) { fut.set_result(v.clone_box()); } Ok(Some(Box::new(fut))) } ("env.future", "set") => { // set(future, value) if args.len() >= 2 { if let Some(fut) = args[0].as_any().downcast_ref::() { fut.set_result(args[1].clone_box()); } } Ok(None) } ("env.future", "await") => { // await(future) -> Result.Ok(value) / Result.Err(Timeout|Error) use crate::boxes::result::NyashResultBox; if let Some(arg) = args.get(0) { if let Some(fut) = arg.as_any().downcast_ref::() { let max_ms: u64 = crate::config::env::await_max_ms(); let start = std::time::Instant::now(); let mut spins = 0usize; while !fut.ready() { crate::runtime::global_hooks::safepoint_and_poll(); std::thread::yield_now(); spins += 1; if spins % 1024 == 0 { std::thread::sleep(std::time::Duration::from_millis(1)); } if start.elapsed() >= std::time::Duration::from_millis(max_ms) { let err = crate::box_trait::StringBox::new("Timeout"); return Ok(Some(Box::new(NyashResultBox::new_err(Box::new(err))))); } } return match fut.wait_and_get() { Ok(v) => Ok(Some(Box::new(NyashResultBox::new_ok(v)))), Err(e) => { let err = crate::box_trait::StringBox::new(format!("Error: {}", e)); Ok(Some(Box::new(NyashResultBox::new_err(Box::new(err))))) } }; } else { return Ok(Some(Box::new(NyashResultBox::new_ok(arg.clone_box())))); } } Ok(Some(Box::new(crate::boxes::result::NyashResultBox::new_err(Box::new(crate::box_trait::StringBox::new("InvalidArgs")))))) } ("env.future", "delay") => { // delay(ms) -> FutureBox resolved to void after ms use crate::box_trait::NyashBox as _; let fut = crate::boxes::future::FutureBox::new(); let ms = if let Some(arg0) = args.get(0) { if let Some(i) = arg0.as_any().downcast_ref::() { i.value.max(0) as u64 } else { arg0.to_string_box().value.trim().parse::().unwrap_or(0).max(0) as u64 } } else { 0 }; let fut_setter = fut.clone(); let _scheduled = crate::runtime::global_hooks::spawn_task_after(ms, "env.future.delay", Box::new(move || { fut_setter.set_result(Box::new(crate::box_trait::VoidBox::new())); })); crate::runtime::global_hooks::register_future_to_current_group(&fut); Ok(Some(Box::new(fut))) } ("env.future", "spawn_instance") => { // spawn_instance(recv, method_name, args...) -> FutureBox // If a scheduler is available, schedule the call; else invoke synchronously. use crate::box_trait::{NyashBox, VoidBox, ErrorBox}; let fut = crate::boxes::future::FutureBox::new(); if args.len() >= 2 { if let Some(pb) = args[0].as_any().downcast_ref::() { let method_name = if let Some(sb) = args[1].as_any().downcast_ref::() { sb.value.clone() } else { args[1].to_string_box().value }; // Clone boxes for scheduled call (same-thread scheduler; no Send bound) let tail: Vec> = args.iter().skip(2).map(|a| a.clone_box()).collect(); let recv_type = pb.box_type.clone(); let recv_id = pb.instance_id(); // Clones for fallback inline path let recv_type_inline = recv_type.clone(); let method_name_inline = method_name.clone(); let tail_inline: Vec> = tail.iter().map(|a| a.clone_box()).collect(); let fut_setter = fut.clone(); // Phase 2: attempt to bind to current task group's token (no-op if unset) let token = crate::runtime::global_hooks::current_group_token(); let scheduled = crate::runtime::global_hooks::spawn_task_with_token("spawn_instance", token, Box::new(move || { let host = crate::runtime::get_global_plugin_host(); let read_res = host.read(); if let Ok(ro) = read_res { match ro.invoke_instance_method(&recv_type, &method_name, recv_id, &tail) { Ok(ret) => { if let Some(v) = ret { fut_setter.set_result(v); } else { fut_setter.set_result(Box::new(VoidBox::new())); } } Err(e) => { fut_setter.set_result(Box::new(ErrorBox::new("InvokeError", &format!("{}.{}: {:?}", recv_type, method_name, e)))); } } } })); if !scheduled { // Fallback: run inline synchronously let host = crate::runtime::get_global_plugin_host(); let read_res = host.read(); if let Ok(ro) = read_res { match ro.invoke_instance_method(&recv_type_inline, &method_name_inline, recv_id, &tail_inline) { Ok(ret) => { if let Some(v) = ret { fut.set_result(v); } else { fut.set_result(Box::new(VoidBox::new())); } } Err(e) => { fut.set_result(Box::new(ErrorBox::new("InvokeError", &format!("{}.{}: {:?}", recv_type_inline, method_name_inline, e)))); } } } } // Register into current TaskGroup (if any) or implicit group (best-effort) crate::runtime::global_hooks::register_future_to_current_group(&fut); return Ok(Some(Box::new(fut))); } } // Fallback: resolved future of first arg if let Some(v) = args.get(0) { fut.set_result(v.clone_box()); } crate::runtime::global_hooks::register_future_to_current_group(&fut); Ok(Some(Box::new(fut))) } ("env.canvas", _) => { eprintln!("[env.canvas] {} invoked (stub)", method_name); Ok(None) } _ => { // Future: route to plugin-defined extern interfaces via config Err(BidError::InvalidMethod) } } } fn resolve_method_id_from_file(&self, box_type: &str, method_name: &str) -> BidResult { let config = self.config.as_ref().ok_or(BidError::PluginError)?; let lib_name = self.find_lib_name_for_box(box_type).ok_or(BidError::InvalidType)?; // Prefer spec-loaded methods if let Some(spec) = self.box_specs.read().unwrap().get(&(lib_name.clone(), box_type.to_string())) { if let Some(m) = spec.methods.get(method_name) { return Ok(m.method_id); } } // Fallback to central nyash.toml nested box config let cfg_path = self.config_path.as_ref().map(|s| s.as_str()).unwrap_or("nyash.toml"); let toml_content = std::fs::read_to_string(cfg_path).map_err(|_| BidError::PluginError)?; let toml_value: toml::Value = toml::from_str(&toml_content).map_err(|_| BidError::PluginError)?; let box_conf = config.get_box_config(&lib_name, box_type, &toml_value).ok_or(BidError::InvalidType)?; let method = box_conf.methods.get(method_name).ok_or_else(|| { eprintln!("[PluginLoaderV2] Method '{}' not found for box '{}' in {}", method_name, box_type, cfg_path); eprintln!("[PluginLoaderV2] Available methods: {:?}", box_conf.methods.keys().collect::>()); BidError::InvalidMethod })?; Ok(method.method_id) } /// Determine whether a method returns a Result (Ok/Err) wrapper. pub fn method_returns_result(&self, box_type: &str, method_name: &str) -> bool { let config = match &self.config { Some(c) => c, None => return false }; let lib_name = match self.find_lib_name_for_box(box_type) { Some(n) => n, None => return false }; // Prefer spec if present if let Some(spec) = self.box_specs.read().unwrap().get(&(lib_name.clone(), box_type.to_string())) { if let Some(m) = spec.methods.get(method_name) { return m.returns_result; } } // Fallback to central nyash.toml let cfg_path = self.config_path.as_deref().unwrap_or("nyash.toml"); if let Ok(toml_content) = std::fs::read_to_string(cfg_path) { if let Ok(toml_value) = toml::from_str::(&toml_content) { if let Some(bc) = config.get_box_config(&lib_name, box_type, &toml_value) { return bc.methods.get(method_name).map(|m| m.returns_result).unwrap_or(false); } } } false } /// Invoke an instance method on a plugin box by name (minimal TLV encoding) pub fn invoke_instance_method( &self, box_type: &str, method_name: &str, instance_id: u32, args: &[Box], ) -> BidResult>> { // v2.1: 引数ありのメソッドを許可(BoxRef/基本型/文字列化フォールバック) let method_id = self.resolve_method_id_from_file(box_type, method_name)?; // Find plugin and type_id let config = self.config.as_ref().ok_or(BidError::PluginError)?; let lib_name = self.find_lib_name_for_box(box_type).ok_or(BidError::InvalidType)?; let plugins = self.plugins.read().unwrap(); let plugin = plugins.get(&lib_name).ok_or(BidError::PluginError)?; let cfg_path = self.config_path.as_ref().map(|s| s.as_str()).unwrap_or("nyash.toml"); let toml_content = std::fs::read_to_string(cfg_path).map_err(|_| BidError::PluginError)?; let toml_value: toml::Value = toml::from_str(&toml_content).map_err(|_| BidError::PluginError)?; // Prefer spec values; but if method isn't listed in spec, fallback to central nyash.toml for returns_result let (type_id, returns_result) = if let Some(spec) = self.box_specs.read().unwrap().get(&(lib_name.clone(), box_type.to_string())) { let tid = spec .type_id .or_else(|| config.box_types.get(box_type).copied()) .ok_or(BidError::InvalidType)?; let rr = if let Some(m) = spec.methods.get(method_name) { m.returns_result } else { // Fallback to central config for method flags if let Some(box_conf) = config.get_box_config(&lib_name, box_type, &toml_value) { box_conf.methods.get(method_name).map(|m| m.returns_result).unwrap_or(false) } else { false } }; (tid, rr) } else { let box_conf = config.get_box_config(&lib_name, box_type, &toml_value).ok_or(BidError::InvalidType)?; let rr = box_conf.methods.get(method_name).map(|m| m.returns_result).unwrap_or(false); (box_conf.type_id, rr) }; eprintln!("[PluginLoaderV2] Invoke {}.{}: resolving and encoding args (argc={})", box_type, method_name, args.len()); // TLV args: encode using BID-1 style (u16 ver, u16 argc, then entries) let tlv_args = { let mut buf = crate::runtime::plugin_ffi_common::encode_tlv_header(args.len() as u16); // Validate against nyash.toml method args schema if present let expected_args = if self.box_specs.read().unwrap().get(&(lib_name.clone(), box_type.to_string())).is_some() { None } else { config .get_box_config(&lib_name, box_type, &toml_value) .and_then(|bc| bc.methods.get(method_name).and_then(|m| m.args.clone())) }; if let Some(exp) = expected_args.as_ref() { if exp.len() != args.len() { eprintln!( "[PluginLoaderV2] InvalidArgs: {}.{} expects {} args, got {} (schema={:?})", box_type, method_name, exp.len(), args.len(), exp.iter().map(|a| a.kind_str()).collect::>() ); return Err(BidError::InvalidArgs); } } for (idx, a) in args.iter().enumerate() { // Coerce commonly-used plugin boxed primitives to builtin primitives when schema is weak (Name) // Example: Plugin IntegerBox passed to ArrayBox.push(value) should encode as I64 let mut enc_ref: &Box = a; let mut enc_owned: Option> = None; if let Some(p) = a.as_any().downcast_ref::() { if p.box_type == "IntegerBox" { // Read integer value via get(); on success, encode that value instead of a handle if let Ok(val_opt) = self.invoke_instance_method("IntegerBox", "get", p.inner.instance_id, &[]) { if let Some(val_box) = val_opt { enc_owned = Some(val_box); enc_ref = enc_owned.as_ref().unwrap(); } } } else if p.box_type == "StringBox" { // Prefer TLV string for expected string params let need_string = if let Some(exp) = expected_args.as_ref() { matches!(exp.get(idx), Some(crate::config::nyash_toml_v2::ArgDecl::Typed { kind, .. }) if kind == "string") } else { // Name fallback: allow coercion to string true }; if need_string { if let Ok(val_opt) = self.invoke_instance_method("StringBox", "toUtf8", p.inner.instance_id, &[]) { if let Some(val_box) = val_opt { enc_owned = Some(val_box); enc_ref = enc_owned.as_ref().unwrap(); } } } } // Future: BoolBox/F64 coercions } // If schema exists, validate per expected kind if let Some(exp) = expected_args.as_ref() { let decl = &exp[idx]; match decl { crate::config::nyash_toml_v2::ArgDecl::Typed { kind, category } => { match kind.as_str() { "box" => { // Only plugin box supported for now if category.as_deref() != Some("plugin") { return Err(BidError::InvalidArgs); } if enc_ref.as_any().downcast_ref::().is_none() { return Err(BidError::InvalidArgs); } } "string" => { if enc_ref.as_any().downcast_ref::().is_none() { // Attempt late coercion for plugin StringBox if let Some(p) = enc_ref.as_any().downcast_ref::() { if p.box_type == "StringBox" { if let Ok(val_opt) = self.invoke_instance_method("StringBox", "toUtf8", p.inner.instance_id, &[]) { if let Some(val_box) = val_opt { enc_owned = Some(val_box); enc_ref = enc_owned.as_ref().unwrap(); } } } } } if enc_ref.as_any().downcast_ref::().is_none() { return Err(BidError::InvalidArgs); } } "int" | "i32" => { if enc_ref.as_any().downcast_ref::().is_none() { return Err(BidError::InvalidArgs); } } _ => { eprintln!("[PluginLoaderV2] InvalidArgs: unsupported kind '{}' for {}.{} arg[{}]", kind, box_type, method_name, idx); return Err(BidError::InvalidArgs); } } } crate::config::nyash_toml_v2::ArgDecl::Name(_) => { // Back-compat: allow common primitives (string or int) let is_string = enc_ref.as_any().downcast_ref::().is_some(); let is_int = enc_ref.as_any().downcast_ref::().is_some(); if !(is_string || is_int) { eprintln!("[PluginLoaderV2] InvalidArgs: expected string/int for {}.{} arg[{}]", box_type, method_name, idx); return Err(BidError::InvalidArgs); } } } } // Plugin Handle (BoxRef): tag=8, size=8 if let Some(p) = enc_ref.as_any().downcast_ref::() { eprintln!("[PluginLoaderV2] arg[{}]: PluginBoxV2({}, id={}) -> Handle(tag=8)", idx, p.box_type, p.inner.instance_id); crate::runtime::plugin_ffi_common::encode::plugin_handle(&mut buf, p.inner.type_id, p.inner.instance_id); continue; } // Integer: use I64 (tag=3) for broad plugin compatibility if let Some(i) = enc_ref.as_any().downcast_ref::() { eprintln!("[PluginLoaderV2] arg[{}]: Integer({}) -> I64(tag=3)", idx, i.value); crate::runtime::plugin_ffi_common::encode::i64(&mut buf, i.value); continue; } // Bool if let Some(b) = enc_ref.as_any().downcast_ref::() { eprintln!("[PluginLoaderV2] arg[{}]: Bool({}) -> Bool(tag=1)", idx, b.value); crate::runtime::plugin_ffi_common::encode::bool(&mut buf, b.value); continue; } // Float (F64) if let Some(f) = enc_ref.as_any().downcast_ref::() { eprintln!("[PluginLoaderV2] arg[{}]: Float({}) -> F64(tag=5)", idx, f.value); crate::runtime::plugin_ffi_common::encode::f64(&mut buf, f.value); continue; } // Bytes from Array if let Some(arr) = enc_ref.as_any().downcast_ref::() { let items = arr.items.read().unwrap(); let mut tmp = Vec::with_capacity(items.len()); let mut ok = true; for item in items.iter() { if let Some(intb) = item.as_any().downcast_ref::() { if intb.value >= 0 && intb.value <= 255 { tmp.push(intb.value as u8); } else { ok = false; break; } } else { ok = false; break; } } if ok { eprintln!("[PluginLoaderV2] arg[{}]: Array[{}] -> Bytes(tag=7)", idx, tmp.len()); crate::runtime::plugin_ffi_common::encode::bytes(&mut buf, &tmp); continue; } } // String: tag=6 if let Some(s) = enc_ref.as_any().downcast_ref::() { eprintln!("[PluginLoaderV2] arg[{}]: String(len={}) -> String(tag=6)", idx, s.value.len()); crate::runtime::plugin_ffi_common::encode::string(&mut buf, &s.value); continue; } // No schema or unsupported type: only allow fallback when schema is None if expected_args.is_none() { eprintln!("[PluginLoaderV2] arg[{}]: fallback stringify", idx); let sv = enc_ref.to_string_box().value; crate::runtime::plugin_ffi_common::encode::string(&mut buf, &sv); } else { return Err(BidError::InvalidArgs); } } buf }; eprintln!("[VM→Plugin] call {}.{} recv_id={} returns_result={}", box_type, method_name, instance_id, returns_result); if dbg_on() { // Dump compact TLV header and first few bytes for diagnostics let hdr_ver = u16::from_le_bytes([tlv_args[0], tlv_args[1]]); let hdr_argc = u16::from_le_bytes([tlv_args[2], tlv_args[3]]); let preview_len = std::cmp::min(tlv_args.len(), 64); let preview: Vec = tlv_args[..preview_len].iter().map(|b| format!("{:02X}", b)).collect(); eprintln!("[VM→Plugin] TLV ver={} argc={} bytes={} preview={}...", hdr_ver, hdr_argc, tlv_args.len(), preview.join(" ")); } let mut out = vec![0u8; 1024]; let mut out_len: usize = out.len(); let rc = unsafe { (plugin.invoke_fn)( type_id, method_id, instance_id, tlv_args.as_ptr(), tlv_args.len(), out.as_mut_ptr(), &mut out_len, ) }; if rc != 0 { let be = BidError::from_raw(rc); if dbg_on() { eprintln!("[PluginLoaderV2] invoke rc={} ({}) for {}.{}", rc, be.message(), box_type, method_name); } if returns_result { let err = crate::exception_box::ErrorBox::new(&format!("{} (code: {})", be.message(), rc)); return Ok(Some(Box::new(crate::boxes::result::NyashResultBox::new_err(Box::new(err))))); } return Err(be); } // Decode: BID-1 style header + first entry let result = if out_len == 0 { if returns_result { Some(Box::new(crate::boxes::result::NyashResultBox::new_ok(Box::new(crate::box_trait::VoidBox::new()))) as Box) } else { None } } else { let data = &out[..out_len]; if data.len() < 4 { return Ok(None); } let _ver = u16::from_le_bytes([data[0], data[1]]); let argc = u16::from_le_bytes([data[2], data[3]]); if argc == 0 { if returns_result { return Ok(Some(Box::new(crate::boxes::result::NyashResultBox::new_ok(Box::new(crate::box_trait::VoidBox::new()))))); } else { return Ok(None); } } if let Some((tag, size, payload)) = crate::runtime::plugin_ffi_common::decode::tlv_first(data) { if dbg_on() { let preview_len = std::cmp::min(payload.len(), 16); let preview: Vec = payload[..preview_len].iter().map(|b| format!("{:02X}", b)).collect(); eprintln!("[Plugin→VM] tag={} size={} preview={} returns_result={} for {}.{}", tag, size, preview.join(" "), returns_result, box_type, method_name); } match tag { 1 if size == 1 => { // Bool let b = crate::runtime::plugin_ffi_common::decode::bool(payload).unwrap_or(false); let val: Box = Box::new(crate::box_trait::BoolBox::new(b)); if returns_result { Some(Box::new(crate::boxes::result::NyashResultBox::new_ok(val)) as Box) } else { Some(val) } } 5 if size == 8 => { // F64 if let Some(f) = crate::runtime::plugin_ffi_common::decode::f64(payload) { let val: Box = Box::new(crate::boxes::math_box::FloatBox::new(f)); if returns_result { Some(Box::new(crate::boxes::result::NyashResultBox::new_ok(val)) as Box) } else { Some(val) } } else { None } } 8 if size == 8 => { // Handle -> PluginBoxV2 let mut t = [0u8;4]; t.copy_from_slice(&payload[0..4]); let mut i = [0u8;4]; i.copy_from_slice(&payload[4..8]); let r_type = u32::from_le_bytes(t); let r_inst = u32::from_le_bytes(i); if dbg_on() { eprintln!("[Plugin→VM] return handle type_id={} inst={} (returns_result={})", r_type, r_inst, returns_result); } // Map type_id -> (lib_name, box_name) if let Some((ret_lib, ret_box)) = self.find_box_by_type_id(config, &toml_value, r_type) { // Get plugin for ret_lib let plugins = self.plugins.read().unwrap(); if let Some(ret_plugin) = plugins.get(ret_lib) { // Need fini_method_id from config if let Some(ret_conf) = config.get_box_config(ret_lib, ret_box, &toml_value) { let fini_id = ret_conf.methods.get("fini").map(|m| m.method_id); let pbox = PluginBoxV2 { box_type: ret_box.to_string(), inner: std::sync::Arc::new(PluginHandleInner { type_id: r_type, invoke_fn: ret_plugin.invoke_fn, instance_id: r_inst, fini_method_id: fini_id, finalized: std::sync::atomic::AtomicBool::new(false), }), }; let val: Box = Box::new(pbox); if returns_result { return Ok(Some(Box::new(crate::boxes::result::NyashResultBox::new_ok(val)))); } else { return Ok(Some(val)); } } } } None } 9 if size == 8 => { // HostHandle -> Box (user/builtin) if let Some(h) = crate::runtime::plugin_ffi_common::decode::u64(payload) { if dbg_on() { eprintln!("[Plugin→VM] return host_handle={} (returns_result={})", h, returns_result); } if let Some(arc) = crate::runtime::host_handles::get(h) { let val: Box = arc.as_ref().share_box(); if returns_result { Some(Box::new(crate::boxes::result::NyashResultBox::new_ok(val)) as Box) } else { Some(val) } } else { None } } else { None } } 2 if size == 4 => { // I32 let n = crate::runtime::plugin_ffi_common::decode::i32(payload).unwrap(); let val: Box = Box::new(IntegerBox::new(n as i64)); if dbg_on() { eprintln!("[Plugin→VM] return i32 value={} (returns_result={})", n, returns_result); } if returns_result { Some(Box::new(crate::boxes::result::NyashResultBox::new_ok(val)) as Box) } else { Some(val) } } 6 | 7 => { // String/Bytes let s = crate::runtime::plugin_ffi_common::decode::string(payload); if dbg_on() { eprintln!("[Plugin→VM] return str/bytes len={} (returns_result={})", size, returns_result); } // Treat as Ok payload; Err is indicated by non-zero rc earlier let val: Box = Box::new(StringBox::new(s)); if returns_result { Some(Box::new(crate::boxes::result::NyashResultBox::new_ok(val)) as Box) } else { Some(val) } } 3 if size == 8 => { // I64 // Try decoding as i64 directly; also support legacy i32 payload size 4 when mis-encoded let n = if payload.len() == 8 { let mut b = [0u8;8]; b.copy_from_slice(&payload[0..8]); i64::from_le_bytes(b) } else { 0 } ; let val: Box = Box::new(IntegerBox::new(n)); if returns_result { Some(Box::new(crate::boxes::result::NyashResultBox::new_ok(val)) as Box) } else { Some(val) } } 9 if size == 8 => { if let Some(h) = crate::runtime::plugin_ffi_common::decode::u64(payload) { if dbg_on() { eprintln!("[Plugin→VM] return host_handle={} (returns_result={})", h, returns_result); } if let Some(arc) = crate::runtime::host_handles::get(h) { let val: Box = arc.as_ref().share_box(); if returns_result { Some(Box::new(crate::boxes::result::NyashResultBox::new_ok(val)) as Box) } else { Some(val) } } else { None } } else { None } }, _ => None, }} else { None } }; Ok(result) } /// Load single plugin pub fn load_plugin(&self, lib_name: &str, lib_def: &LibraryDefinition) -> BidResult<()> { // Check if already loaded { let plugins = self.plugins.read().unwrap(); if plugins.contains_key(lib_name) { return Ok(()); } } // Resolve platform-specific path (.so/.dll/.dylib) and optional search paths let resolved_path = self.resolve_library_path(&lib_def.path) .or_else(|| self.config.as_ref().and_then(|c| c.resolve_plugin_path(&lib_def.path))) .unwrap_or_else(|| lib_def.path.clone()); // Load library let lib = unsafe { libloading::Library::new(&resolved_path) .map_err(|e| { eprintln!( "Failed to load library '{}': {}\n raw='{}'", resolved_path, e, lib_def.path ); BidError::PluginError })? }; // Optional ABI version check (v0: expect 1) if let Ok(sym) = unsafe { lib.get:: u32>(b"nyash_plugin_abi") } { let ver = unsafe { (*sym)() }; if ver != 1 { eprintln!("[PluginLoaderV2] nyash_plugin_abi version mismatch: {} (expected 1) for {}", ver, lib_name); return Err(BidError::PluginError); } } else { eprintln!("[PluginLoaderV2] nyash_plugin_abi not found for {} (assuming v0=1)", lib_name); } // Get required invoke function and dereference it let invoke_fn = unsafe { let symbol: libloading::Symbol i32> = lib.get(b"nyash_plugin_invoke") .map_err(|e| { eprintln!("Missing nyash_plugin_invoke: {}", e); BidError::InvalidMethod })?; *symbol // Dereference to get the actual function pointer }; // Get optional init function and dereference it let init_fn = unsafe { lib.get:: i32>(b"nyash_plugin_init").ok() .map(|f| *f) }; // Call init if available if let Some(init) = init_fn { let result = unsafe { init() }; eprintln!("[PluginLoaderV2] nyash_plugin_init rc={} for {}", result, lib_name); if result != 0 { eprintln!("Plugin init failed with code: {}", result); return Err(BidError::PluginError); } } else { eprintln!("[PluginLoaderV2] nyash_plugin_init not found for {} (optional)", lib_name); } // Store plugin with Arc-wrapped library let lib_arc = Arc::new(lib); let plugin = Arc::new(LoadedPluginV2 { _lib: lib_arc, box_types: lib_def.boxes.clone(), init_fn, invoke_fn, }); let mut plugins = self.plugins.write().unwrap(); plugins.insert(lib_name.to_string(), plugin); Ok(()) } /// Resolve a plugin library path for the current OS by adapting extensions and common prefixes. /// - Maps .so/.dylib/.dll according to target OS /// - On Windows also tries removing leading 'lib' prefix /// - Searches configured plugin_paths if only filename is provided fn resolve_library_path(&self, raw: &str) -> Option { use std::path::{Path, PathBuf}; let p = Path::new(raw); if p.exists() { return Some(raw.to_string()); } let dir = p.parent().map(|d| d.to_path_buf()).unwrap_or_else(|| PathBuf::from(".")); let file = p.file_name().map(|s| s.to_string_lossy().to_string()).unwrap_or_else(|| raw.to_string()); let stem = Path::new(&file).file_stem().map(|s| s.to_string_lossy().to_string()).unwrap_or(file.clone()); let cur_ext: &str = if cfg!(target_os = "windows") { "dll" } else if cfg!(target_os = "macos") { "dylib" } else { "so" }; // Candidate A: replace extension with OS-specific let mut cand_a = dir.join(Path::new(&stem).with_extension(cur_ext)); if cand_a.exists() { return Some(cand_a.to_string_lossy().to_string()); } // Candidate B (Windows): drop 'lib' prefix if present if cfg!(target_os = "windows") { if let Some(stripped) = stem.strip_prefix("lib") { let cand_b = dir.join(Path::new(stripped).with_extension(cur_ext)); if cand_b.exists() { return Some(cand_b.to_string_lossy().to_string()); } } } // Candidate C: search in configured plugin_paths with adapted filename if let Some(cfg) = &self.config { // try original filename if let Some(path) = cfg.resolve_plugin_path(&file) { return Some(path); } // try adapted A if let Some(path) = cfg.resolve_plugin_path(&cand_a.file_name().unwrap_or_default().to_string_lossy()) { return Some(path); } // try adapted B on windows if cfg!(target_os = "windows") { if let Some(stripped) = stem.strip_prefix("lib") { let name = format!("{}.{}", stripped, cur_ext); if let Some(path) = cfg.resolve_plugin_path(&name) { return Some(path); } } } } None } /// Create a Box instance pub fn create_box(&self, box_type: &str, _args: &[Box]) -> BidResult> { eprintln!("🔍 create_box called for: {}", box_type); let config = self.config.as_ref() .ok_or(BidError::PluginError)?; eprintln!("🔍 Config loaded successfully"); // Find library that provides this box type let (lib_name, _lib_def) = config.find_library_for_box(box_type) .ok_or_else(|| { eprintln!("No plugin provides box type: {}", box_type); BidError::InvalidType })?; // If singleton, return the pre-birthed shared handle let cfg_path = self.config_path.as_ref().map(|s| s.as_str()).unwrap_or("nyash.toml"); if let Ok(toml_content) = std::fs::read_to_string(cfg_path) { if let Ok(toml_value) = toml::from_str::(&toml_content) { if let Some(bc) = config.get_box_config(lib_name, box_type, &toml_value) { if bc.singleton { // ensure created let _ = self.ensure_singleton_handle(lib_name, box_type); if let Some(inner) = self.singletons.read().unwrap().get(&(lib_name.to_string(), box_type.to_string())) { let plugin_box = PluginBoxV2 { box_type: box_type.to_string(), inner: inner.clone() }; return Ok(Box::new(plugin_box)); } } } } } eprintln!("🔍 Found library: {} for box type: {}", lib_name, box_type); // Get loaded plugin let plugins = self.plugins.read().unwrap(); let plugin = plugins.get(lib_name) .ok_or_else(|| { eprintln!("Plugin not loaded: {}", lib_name); BidError::PluginError })?; eprintln!("🔍 Plugin loaded successfully"); // Resolve type_id/fini: prefer per-plugin spec (nyash_box.toml), fallback to central nyash.toml let (type_id, fini_method_id) = if let Some(spec) = self.box_specs.read().unwrap().get(&(lib_name.to_string(), box_type.to_string())) { // Prefer explicit spec values; if missing, fallback to central [box_types] and no fini let tid = spec .type_id .or_else(|| config.box_types.get(box_type).copied()) .ok_or_else(|| { eprintln!( "No type_id found for {} (plugin spec missing and central [box_types] not set)", box_type ); BidError::InvalidType })?; (tid, spec.fini_method_id) } else { eprintln!("🔍 Reading nyash.toml for type configuration..."); if let Ok(toml_content) = std::fs::read_to_string(cfg_path) { eprintln!("🔍 nyash.toml read successfully"); if let Ok(toml_value) = toml::from_str::(&toml_content) { eprintln!("🔍 nyash.toml parsed successfully"); if let Some(box_config) = config.get_box_config(lib_name, box_type, &toml_value) { eprintln!("🔍 Found box config for {} with type_id: {}", box_type, box_config.type_id); let fini_id = box_config.methods.get("fini").map(|m| m.method_id); (box_config.type_id, fini_id) } else { eprintln!("No type configuration for {} in {}", box_type, lib_name); return Err(BidError::InvalidType); } } else { eprintln!("Failed to parse nyash.toml"); return Err(BidError::PluginError); } } else { eprintln!("Failed to read nyash.toml"); return Err(BidError::PluginError); } }; // Call birth constructor (method_id = 0) via TLV encoding eprintln!("🔍 Preparing to call birth() with type_id: {}", type_id); let mut output_buffer = vec![0u8; 1024]; // 1KB buffer for output let mut output_len = output_buffer.len(); // Encode constructor arguments (best-effort). Birth schemas are optional; we support common primitives. let tlv_args = { let mut buf = crate::runtime::plugin_ffi_common::encode_tlv_header(_args.len() as u16); for (idx, a) in _args.iter().enumerate() { // Coerce plugin integer/string primitive boxes to builtins first let mut enc_ref: &Box = a; let mut enc_owned: Option> = None; if let Some(p) = a.as_any().downcast_ref::() { if p.box_type == "IntegerBox" { if let Ok(val_opt) = self.invoke_instance_method("IntegerBox", "get", p.inner.instance_id, &[]) { if let Some(val_box) = val_opt { enc_owned = Some(val_box); enc_ref = enc_owned.as_ref().unwrap(); } } } else if p.box_type == "StringBox" { if let Ok(val_opt) = self.invoke_instance_method("StringBox", "toUtf8", p.inner.instance_id, &[]) { if let Some(val_box) = val_opt { enc_owned = Some(val_box); enc_ref = enc_owned.as_ref().unwrap(); } } } } if let Some(i) = enc_ref.as_any().downcast_ref::() { crate::runtime::plugin_ffi_common::encode::i64(&mut buf, i.value); continue; } if let Some(s) = enc_ref.as_any().downcast_ref::() { crate::runtime::plugin_ffi_common::encode::string(&mut buf, &s.value); continue; } if let Some(b) = enc_ref.as_any().downcast_ref::() { crate::runtime::plugin_ffi_common::encode::bool(&mut buf, b.value); continue; } if let Some(f) = enc_ref.as_any().downcast_ref::() { crate::runtime::plugin_ffi_common::encode::f64(&mut buf, f.value); continue; } // Fallback: HostHandle for user/builtin boxes let h = crate::runtime::host_handles::to_handle_box(enc_ref.clone_box()); crate::runtime::plugin_ffi_common::encode::host_handle(&mut buf, h); } buf }; eprintln!("🔍 Output buffer allocated, about to call plugin invoke_fn..."); let birth_result = unsafe { eprintln!("🔍 Calling invoke_fn(type_id={}, method_id=0, instance_id=0, tlv_args={:?}, output_buf, output_size={})", type_id, tlv_args, output_buffer.len()); (plugin.invoke_fn)( type_id, // Box type ID 0, // method_id for birth 0, // instance_id = 0 for birth (static call) tlv_args.as_ptr(), // TLV-encoded input data tlv_args.len(), // input size output_buffer.as_mut_ptr(), // output buffer &mut output_len, // output buffer size (mutable) ) }; eprintln!("🔍 invoke_fn returned with result: {}", birth_result); if birth_result != 0 { eprintln!("birth() failed with code: {}", birth_result); return Err(BidError::PluginError); } // Parse instance_id from output (first 4 bytes as u32) let instance_id = if output_len >= 4 { u32::from_le_bytes([output_buffer[0], output_buffer[1], output_buffer[2], output_buffer[3]]) } else { eprintln!("birth() returned insufficient data (got {} bytes, need 4)", output_len); return Err(BidError::PluginError); }; eprintln!("🎉 birth() success: {} instance_id={}", box_type, instance_id); // Create v2 plugin box wrapper with actual instance_id let plugin_box = PluginBoxV2 { box_type: box_type.to_string(), inner: std::sync::Arc::new(PluginHandleInner { type_id, invoke_fn: plugin.invoke_fn, instance_id, fini_method_id, finalized: std::sync::atomic::AtomicBool::new(false), }), }; // Post-birth default initialization for known types // StringBox: ensure empty content to avoid plugin-side uninitialized access when length() is called immediately if box_type == "StringBox" { let cfg_path = self.config_path.as_ref().map(|s| s.as_str()).unwrap_or("nyash.toml"); if let Ok(toml_content) = std::fs::read_to_string(cfg_path) { if let Ok(toml_value) = toml::from_str::(&toml_content) { if let Some(box_conf) = self.config.as_ref().and_then(|c| c.get_box_config(lib_name, box_type, &toml_value)) { if let Some(from_utf8) = box_conf.methods.get("fromUtf8") { // TLV: header argc=1, then Bytes(tag=7, len=0) let mut tlv = crate::runtime::plugin_ffi_common::encode_tlv_header(1); crate::runtime::plugin_ffi_common::encode::bytes(&mut tlv, &[]); let mut out = [0u8; 8]; let mut out_len: usize = out.len(); let _ = unsafe { (plugin.invoke_fn)(type_id, from_utf8.method_id, instance_id, tlv.as_ptr(), tlv.len(), out.as_mut_ptr(), &mut out_len) }; } } } } } leak_tracker::init(); leak_tracker::register_plugin(&plugin_box.box_type, instance_id); Ok(Box::new(plugin_box)) } /// Shutdown singletons: finalize and clear all singleton handles pub fn shutdown_singletons(&self) { let mut map = self.singletons.write().unwrap(); for (_, handle) in map.drain() { handle.finalize_now(); } } } // Global loader instance static GLOBAL_LOADER_V2: Lazy>> = Lazy::new(|| Arc::new(RwLock::new(PluginLoaderV2::new()))); /// Get global v2 loader pub fn get_global_loader_v2() -> Arc> { GLOBAL_LOADER_V2.clone() } /// Initialize global loader with config pub fn init_global_loader_v2(config_path: &str) -> BidResult<()> { let loader = get_global_loader_v2(); let mut loader = loader.write().unwrap(); loader.load_config(config_path)?; drop(loader); // Release write lock // Load all plugins let loader = get_global_loader_v2(); let loader = loader.read().unwrap(); loader.load_all_plugins() } /// Gracefully shutdown plugins (finalize singletons) pub fn shutdown_plugins_v2() -> BidResult<()> { let loader = get_global_loader_v2(); let loader = loader.read().unwrap(); loader.shutdown_singletons(); Ok(()) } } // Public constructor wrapper for PluginBoxV2 (enabled build) #[cfg(all(feature = "plugins", not(target_arch = "wasm32")))] pub use self::enabled::make_plugin_box_v2_inner as make_plugin_box_v2; #[cfg(any(not(feature = "plugins"), target_arch = "wasm32"))] mod stub { use crate::bid::{BidResult, BidError}; use crate::box_trait::NyashBox; use once_cell::sync::Lazy; use std::sync::{Arc, RwLock}; // Stub implementation of PluginBoxV2 for WASM/non-plugin builds #[derive(Debug, Clone)] pub struct PluginBoxV2 { pub box_type: String, pub inner: std::sync::Arc, } #[derive(Debug)] pub struct PluginHandleInner { pub type_id: u32, pub instance_id: u32, pub fini_method_id: Option, } pub struct PluginLoaderV2 { pub config: Option<()>, // Dummy config for compatibility } impl PluginLoaderV2 { pub fn new() -> Self { Self { config: None } } } impl PluginLoaderV2 { pub fn load_config(&mut self, _p: &str) -> BidResult<()> { Ok(()) } pub fn load_all_plugins(&self) -> BidResult<()> { Ok(()) } pub fn create_box(&self, _t: &str, _a: &[Box]) -> BidResult> { Err(BidError::PluginError) } pub fn extern_call( &self, _iface_name: &str, _method_name: &str, _args: &[Box], ) -> BidResult>> { Err(BidError::PluginError) } pub fn invoke_instance_method( &self, _box_type: &str, _method_name: &str, _instance_id: u32, _args: &[Box], ) -> BidResult>> { Err(BidError::PluginError) } } static GLOBAL_LOADER_V2: Lazy>> = Lazy::new(|| Arc::new(RwLock::new(PluginLoaderV2::new()))); pub fn get_global_loader_v2() -> Arc> { GLOBAL_LOADER_V2.clone() } pub fn init_global_loader_v2(_config_path: &str) -> BidResult<()> { Ok(()) } pub fn shutdown_plugins_v2() -> BidResult<()> { Ok(()) } } #[cfg(all(feature = "plugins", not(target_arch = "wasm32")))] pub use enabled::*; #[cfg(any(not(feature = "plugins"), target_arch = "wasm32"))] pub use stub::*;