use crate::bid::{BidError, BidResult, NyashHostVtable, NyashPluginInfo, PluginHandle, PLUGIN_ABI_SYMBOL, PLUGIN_INIT_SYMBOL, PLUGIN_INVOKE_SYMBOL, PLUGIN_SHUTDOWN_SYMBOL}; #[cfg(all(feature = "plugins", not(target_arch = "wasm32")))] use libloading::{Library, Symbol}; use std::path::{Path, PathBuf}; /// Loaded plugin with FFI entry points and metadata pub struct LoadedPlugin { pub library: Library, pub handle: PluginHandle, pub type_id: u32, pub plugin_info: NyashPluginInfo, // プラグイン情報を保存 } impl LoadedPlugin { /// Load a plugin dynamic library from file path and initialize it pub fn load_from_file(path: &Path) -> BidResult { // Load library let library = unsafe { Library::new(path) } .map_err(|_| BidError::PluginError)?; // Resolve symbols unsafe { let abi: Symbol u32> = library .get(PLUGIN_ABI_SYMBOL.as_bytes()) .map_err(|_| BidError::PluginError)?; let init: Symbol i32> = library .get(PLUGIN_INIT_SYMBOL.as_bytes()) .map_err(|_| BidError::PluginError)?; let invoke: Symbol i32> = library .get(PLUGIN_INVOKE_SYMBOL.as_bytes()) .map_err(|_| BidError::PluginError)?; let shutdown: Symbol = library .get(PLUGIN_SHUTDOWN_SYMBOL.as_bytes()) .map_err(|_| BidError::PluginError)?; let handle = PluginHandle { abi: *abi, init: *init, invoke: *invoke, shutdown: *shutdown, }; // ABI check handle.check_abi()?; // Initialize plugin let host = default_host_vtable(); let mut info = NyashPluginInfo::empty(); handle.initialize(&host, &mut info)?; let type_id = info.type_id; Ok(Self { library, handle, type_id, plugin_info: info }) } } /// Get the plugin's Box type name pub fn get_type_name(&self) -> BidResult<&str> { unsafe { self.plugin_info.name() } } /// Get all available methods for this plugin pub fn get_methods(&self) -> BidResult> { let mut methods = Vec::new(); unsafe { let methods_slice = self.plugin_info.methods_slice()?; for method_info in methods_slice { let method_name = method_info.name()?.to_string(); methods.push(( method_info.method_id, method_name, method_info.signature_hash, )); } } Ok(methods) } /// Find a method by name and return its info pub fn find_method(&self, method_name: &str) -> BidResult> { unsafe { let methods_slice = self.plugin_info.methods_slice()?; for method_info in methods_slice { if method_info.name()? == method_name { return Ok(Some((method_info.method_id, method_info.signature_hash))); } } } Ok(None) } } // Static host vtable to ensure lifetime static HOST_VTABLE_STORAGE: std::sync::LazyLock = std::sync::LazyLock::new(|| { unsafe extern "C" fn host_alloc(size: usize) -> *mut u8 { let layout = std::alloc::Layout::from_size_align(size, 8).unwrap(); std::alloc::alloc(layout) } unsafe extern "C" fn host_free(_ptr: *mut u8) { // In this prototype we cannot deallocate without size. No-op. } unsafe extern "C" fn host_wake(_id: u64) {} unsafe extern "C" fn host_log(_level: i32, _msg: *const i8) { // Debug output for plugin logs if !_msg.is_null() { let msg = unsafe { std::ffi::CStr::from_ptr(_msg) }; if let Ok(s) = msg.to_str() { eprintln!("🔌 Plugin log [{}]: {}", _level, s); } } } NyashHostVtable { alloc: host_alloc, free: host_free, wake: host_wake, log: host_log } }); /// Build a minimal host vtable for plugins fn default_host_vtable() -> &'static NyashHostVtable { &*HOST_VTABLE_STORAGE } /// Helper: find plugin file path by name and candidate directories pub fn resolve_plugin_path(plugin_name: &str, candidates: &[PathBuf]) -> Option { // Expected filenames by platform (Linux only for now) let file = format!("lib{}{}.so", plugin_name.replace('-', "_"), ""); for dir in candidates { let candidate = dir.join(&file); if candidate.exists() { return Some(candidate); } } None }