refactor: Major interpreter modularization and P2PBox enhancements
Major Interpreter Refactoring: - Split core.rs (373 lines removed) into focused modules - Split expressions/calls.rs (460 lines removed) into cleaner structure - Added new modules: calls.rs, errors.rs, eval.rs, methods_dispatch.rs, state.rs - Improved separation of concerns across interpreter components P2PBox Enhancements: - Added on_once() for one-time event handlers - Added off() for handler deregistration - Implemented handler flags with AtomicBool for thread-safe management - Added loopback testing cache (last_from, last_intent_name) - Improved Arc-based state sharing for transport and handlers Plugin Loader Unification (In Progress): - Created plugin_loader_unified.rs skeleton - Created plugin_ffi_common.rs for shared FFI utilities - Migration plan documented (2400 lines → 1100 lines target) MIR & VM Improvements: - Enhanced modularized MIR builder structure - Added BoxCall dispatch improvements - Better separation in builder modules Documentation Updates: - Added Phase 9.79a unified box dispatch plan - Created plugin loader migration plan - Updated CURRENT_TASK.md with latest progress All tests passing (180 tests) - ready for next phase of refactoring 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -75,16 +75,12 @@ impl BoxFactoryRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
/// プラグインBoxを生成(v2実装)
|
||||
/// プラグインBoxを生成(unified facade→v2)
|
||||
fn create_plugin_box(&self, plugin_name: &str, box_name: &str, args: &[Box<dyn NyashBox>]) -> Result<Box<dyn NyashBox>, String> {
|
||||
use crate::runtime::get_global_loader_v2;
|
||||
|
||||
// v2ローダーを取得
|
||||
let loader = get_global_loader_v2();
|
||||
let loader = loader.read().unwrap();
|
||||
|
||||
// プラグインからBoxを生成
|
||||
loader.create_box(box_name, args)
|
||||
use crate::runtime::get_global_plugin_host;
|
||||
let host = get_global_plugin_host();
|
||||
let host = host.read().unwrap();
|
||||
host.create_box(box_name, args)
|
||||
.map_err(|e| format!("Failed to create {} from plugin {}: {:?}", box_name, plugin_name, e))
|
||||
}
|
||||
}
|
||||
@ -147,4 +143,4 @@ mod tests {
|
||||
_ => panic!("Expected plugin provider"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,6 +5,8 @@
|
||||
pub mod plugin_config;
|
||||
pub mod box_registry;
|
||||
pub mod plugin_loader_v2;
|
||||
pub mod plugin_loader_unified;
|
||||
pub mod plugin_ffi_common;
|
||||
pub mod leak_tracker;
|
||||
pub mod unified_registry;
|
||||
pub mod nyash_runtime;
|
||||
@ -17,6 +19,7 @@ mod tests;
|
||||
pub use plugin_config::PluginConfig;
|
||||
pub use box_registry::{BoxFactoryRegistry, BoxProvider, get_global_registry};
|
||||
pub use plugin_loader_v2::{PluginLoaderV2, get_global_loader_v2, init_global_loader_v2};
|
||||
pub use plugin_loader_unified::{PluginHost, get_global_plugin_host, init_global_plugin_host, PluginLibraryHandle, PluginBoxType, MethodHandle};
|
||||
pub use unified_registry::{get_global_unified_registry, init_global_unified_registry, register_user_defined_factory};
|
||||
pub use nyash_runtime::{NyashRuntime, NyashRuntimeBuilder};
|
||||
// pub use plugin_box::PluginBox; // legacy
|
||||
|
||||
78
src/runtime/plugin_ffi_common.rs
Normal file
78
src/runtime/plugin_ffi_common.rs
Normal file
@ -0,0 +1,78 @@
|
||||
//! Common FFI helpers for Plugin system
|
||||
//! Minimal TLV utilities extracted for unified facade usage.
|
||||
|
||||
/// Encode empty TLV arguments: version=1, argc=0
|
||||
pub fn encode_empty_args() -> Vec<u8> { vec![1u8, 0, 0, 0] }
|
||||
|
||||
/// Encode TLV header with argc (no payload entries encoded here)
|
||||
pub fn encode_tlv_header(argc: u16) -> Vec<u8> {
|
||||
let mut buf = Vec::with_capacity(4);
|
||||
buf.extend_from_slice(&1u16.to_le_bytes());
|
||||
buf.extend_from_slice(&argc.to_le_bytes());
|
||||
buf
|
||||
}
|
||||
|
||||
/// Simple helpers for common primitive returns
|
||||
pub mod decode {
|
||||
/// Try to parse a u32 instance id from an output buffer (little-endian).
|
||||
pub fn instance_id(buf: &[u8]) -> Option<u32> {
|
||||
if buf.len() < 4 { return None; }
|
||||
Some(u32::from_le_bytes([buf[0], buf[1], buf[2], buf[3]]))
|
||||
}
|
||||
|
||||
/// Parse TLV header from buffer; returns (tag, size, payload_slice)
|
||||
pub fn tlv_first(buf: &[u8]) -> Option<(u8, usize, &[u8])> {
|
||||
if buf.len() < 8 { return None; }
|
||||
let tag = buf[4];
|
||||
let size = u16::from_le_bytes([buf[6], buf[7]]) as usize;
|
||||
if buf.len() < 8 + size { return None; }
|
||||
Some((tag, size, &buf[8..8 + size]))
|
||||
}
|
||||
/// Decode i32 payload (size must be 4)
|
||||
pub fn i32(payload: &[u8]) -> Option<i32> {
|
||||
if payload.len() != 4 { return None; }
|
||||
let mut b = [0u8;4]; b.copy_from_slice(payload);
|
||||
Some(i32::from_le_bytes(b))
|
||||
}
|
||||
/// Decode UTF-8 string/bytes
|
||||
pub fn string(payload: &[u8]) -> String {
|
||||
String::from_utf8_lossy(payload).to_string()
|
||||
}
|
||||
}
|
||||
|
||||
/// TLV encode helpers for primitive Nyash values
|
||||
pub mod encode {
|
||||
/// tag for I32
|
||||
const TAG_I32: u8 = 2;
|
||||
/// tag for UTF-8 string
|
||||
const TAG_STRING: u8 = 6;
|
||||
/// tag for Plugin Handle (type_id + instance_id)
|
||||
const TAG_HANDLE: u8 = 8;
|
||||
|
||||
/// Append an i32 TLV entry (tag=2, size=4, little-endian)
|
||||
pub fn i32(buf: &mut Vec<u8>, v: i32) {
|
||||
buf.push(TAG_I32);
|
||||
buf.push(0u8); // reserved
|
||||
buf.extend_from_slice(&(4u16).to_le_bytes());
|
||||
buf.extend_from_slice(&v.to_le_bytes());
|
||||
}
|
||||
|
||||
/// Append a string TLV entry (tag=6, size=u16 trunc, UTF-8)
|
||||
pub fn string(buf: &mut Vec<u8>, s: &str) {
|
||||
let bytes = s.as_bytes();
|
||||
let len = core::cmp::min(bytes.len(), u16::MAX as usize);
|
||||
buf.push(TAG_STRING);
|
||||
buf.push(0u8); // reserved
|
||||
buf.extend_from_slice(&((len as u16).to_le_bytes()));
|
||||
buf.extend_from_slice(&bytes[..len]);
|
||||
}
|
||||
|
||||
/// Append a plugin handle TLV entry (tag=8, size=8, type_id:u32 + instance_id:u32)
|
||||
pub fn plugin_handle(buf: &mut Vec<u8>, type_id: u32, instance_id: u32) {
|
||||
buf.push(TAG_HANDLE);
|
||||
buf.push(0u8); // reserved
|
||||
buf.extend_from_slice(&(8u16).to_le_bytes());
|
||||
buf.extend_from_slice(&type_id.to_le_bytes());
|
||||
buf.extend_from_slice(&instance_id.to_le_bytes());
|
||||
}
|
||||
}
|
||||
133
src/runtime/plugin_loader_unified.rs
Normal file
133
src/runtime/plugin_loader_unified.rs
Normal file
@ -0,0 +1,133 @@
|
||||
//! Unified Plugin Host facade
|
||||
//!
|
||||
//! Thin wrapper over v2 loader to provide a stable facade
|
||||
//! with minimal, friendly API for runtime/runner and future transports.
|
||||
|
||||
use std::sync::{Arc, RwLock};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use crate::bid::{BidError, BidResult};
|
||||
use crate::config::nyash_toml_v2::NyashConfigV2;
|
||||
use crate::runtime::plugin_loader_v2::PluginLoaderV2;
|
||||
|
||||
/// Opaque library handle (by name for now)
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PluginLibraryHandle {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
/// Box type descriptor
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PluginBoxType {
|
||||
pub lib: String,
|
||||
pub name: String,
|
||||
pub type_id: u32,
|
||||
}
|
||||
|
||||
/// Resolved method handle
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MethodHandle {
|
||||
pub lib: String,
|
||||
pub box_type: String,
|
||||
pub type_id: u32,
|
||||
pub method_id: u32,
|
||||
pub returns_result: bool,
|
||||
}
|
||||
|
||||
/// Unified facade
|
||||
pub struct PluginHost {
|
||||
loader: Arc<RwLock<PluginLoaderV2>>, // delegate
|
||||
config: Option<NyashConfigV2>, // cached config for resolution
|
||||
config_path: Option<String>,
|
||||
}
|
||||
|
||||
impl PluginHost {
|
||||
pub fn new(loader: Arc<RwLock<PluginLoaderV2>>) -> Self {
|
||||
Self { loader, config: None, config_path: None }
|
||||
}
|
||||
|
||||
/// Load config and dynamic libraries, keeping a local config cache.
|
||||
pub fn load_libraries(&mut self, config_path: &str) -> BidResult<()> {
|
||||
{
|
||||
let mut l = self.loader.write().unwrap();
|
||||
l.load_config(config_path)?;
|
||||
}
|
||||
|
||||
// Keep our own copy for quick lookups
|
||||
let canonical = std::fs::canonicalize(config_path)
|
||||
.map(|p| p.to_string_lossy().to_string())
|
||||
.unwrap_or_else(|_| config_path.to_string());
|
||||
self.config = Some(NyashConfigV2::from_file(&canonical).map_err(|_| BidError::PluginError)?);
|
||||
self.config_path = Some(canonical);
|
||||
|
||||
// Delegate actual library loads + pre-birth singletons to v2
|
||||
let l = self.loader.read().unwrap();
|
||||
l.load_all_plugins()
|
||||
}
|
||||
|
||||
/// Register built-ins or user-defined boxes if needed (no-op for now).
|
||||
pub fn register_boxes(&self) -> BidResult<()> { Ok(()) }
|
||||
|
||||
/// Expose read-only view of loaded config for callers migrating from v2 paths.
|
||||
pub fn config_ref(&self) -> Option<&NyashConfigV2> { self.config.as_ref() }
|
||||
|
||||
/// Resolve a method handle for a given plugin box type and method name.
|
||||
pub fn resolve_method(&self, box_type: &str, method_name: &str) -> BidResult<MethodHandle> {
|
||||
let cfg = self.config.as_ref().ok_or(BidError::PluginError)?;
|
||||
let (lib_name, _lib_def) = cfg.find_library_for_box(box_type).ok_or(BidError::InvalidType)?;
|
||||
let cfg_path = self.config_path.as_deref().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 = cfg.get_box_config(lib_name, box_type, &toml_value).ok_or(BidError::InvalidType)?;
|
||||
let m = box_conf.methods.get(method_name).ok_or(BidError::InvalidMethod)?;
|
||||
Ok(MethodHandle {
|
||||
lib: lib_name.to_string(),
|
||||
box_type: box_type.to_string(),
|
||||
type_id: box_conf.type_id,
|
||||
method_id: m.method_id,
|
||||
returns_result: m.returns_result,
|
||||
})
|
||||
}
|
||||
|
||||
// --- v2 adapter layer: allow gradual migration of callers ---
|
||||
pub fn create_box(&self, box_type: &str, args: &[Box<dyn crate::box_trait::NyashBox>]) -> BidResult<Box<dyn crate::box_trait::NyashBox>> {
|
||||
let l = self.loader.read().unwrap();
|
||||
l.create_box(box_type, args)
|
||||
}
|
||||
|
||||
pub fn invoke_instance_method(
|
||||
&self,
|
||||
box_type: &str,
|
||||
method_name: &str,
|
||||
instance_id: u32,
|
||||
args: &[Box<dyn crate::box_trait::NyashBox>],
|
||||
) -> BidResult<Option<Box<dyn crate::box_trait::NyashBox>>> {
|
||||
let l = self.loader.read().unwrap();
|
||||
l.invoke_instance_method(box_type, method_name, instance_id, args)
|
||||
}
|
||||
|
||||
pub fn extern_call(
|
||||
&self,
|
||||
iface_name: &str,
|
||||
method_name: &str,
|
||||
args: &[Box<dyn crate::box_trait::NyashBox>],
|
||||
) -> BidResult<Option<Box<dyn crate::box_trait::NyashBox>>> {
|
||||
let l = self.loader.read().unwrap();
|
||||
l.extern_call(iface_name, method_name, args)
|
||||
}
|
||||
}
|
||||
|
||||
// Global singleton
|
||||
static GLOBAL_HOST: Lazy<Arc<RwLock<PluginHost>>> = Lazy::new(|| {
|
||||
let loader = crate::runtime::plugin_loader_v2::get_global_loader_v2();
|
||||
Arc::new(RwLock::new(PluginHost::new(loader)))
|
||||
});
|
||||
|
||||
pub fn get_global_plugin_host() -> Arc<RwLock<PluginHost>> { GLOBAL_HOST.clone() }
|
||||
|
||||
pub fn init_global_plugin_host(config_path: &str) -> BidResult<()> {
|
||||
let host = get_global_plugin_host();
|
||||
host.write().unwrap().load_libraries(config_path)?;
|
||||
host.read().unwrap().register_boxes()?;
|
||||
Ok(())
|
||||
}
|
||||
@ -294,7 +294,7 @@ impl PluginBoxV2 {
|
||||
// Call birth
|
||||
let mut output_buffer = vec![0u8; 1024];
|
||||
let mut output_len = output_buffer.len();
|
||||
let tlv_args = vec![1u8, 0, 0, 0];
|
||||
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)
|
||||
};
|
||||
@ -378,10 +378,7 @@ impl PluginBoxV2 {
|
||||
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 = Vec::with_capacity(4 + args.len() * 16);
|
||||
// Header: ver=1, argc=args.len()
|
||||
buf.extend_from_slice(&1u16.to_le_bytes());
|
||||
buf.extend_from_slice(&(args.len() as u16).to_le_bytes());
|
||||
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 = box_conf.methods.get(method_name).and_then(|m| m.args.clone());
|
||||
if let Some(exp) = expected_args.as_ref() {
|
||||
@ -447,44 +444,27 @@ impl PluginBoxV2 {
|
||||
// Plugin Handle (BoxRef): tag=8, size=8
|
||||
if let Some(p) = a.as_any().downcast_ref::<PluginBoxV2>() {
|
||||
eprintln!("[PluginLoaderV2] arg[{}]: PluginBoxV2({}, id={}) -> Handle(tag=8)", idx, p.box_type, p.inner.instance_id);
|
||||
buf.push(8u8); // tag
|
||||
buf.push(0u8); // reserved
|
||||
buf.extend_from_slice(&(8u16).to_le_bytes());
|
||||
buf.extend_from_slice(&p.inner.type_id.to_le_bytes());
|
||||
buf.extend_from_slice(&p.inner.instance_id.to_le_bytes());
|
||||
crate::runtime::plugin_ffi_common::encode::plugin_handle(&mut buf, p.inner.type_id, p.inner.instance_id);
|
||||
continue;
|
||||
}
|
||||
// Integer: prefer i32
|
||||
if let Some(i) = a.as_any().downcast_ref::<IntegerBox>() {
|
||||
eprintln!("[PluginLoaderV2] arg[{}]: Integer({}) -> I32(tag=2)", idx, i.value);
|
||||
buf.push(2u8); // tag=I32
|
||||
buf.push(0u8);
|
||||
buf.extend_from_slice(&(4u16).to_le_bytes());
|
||||
let v = i.value as i32;
|
||||
buf.extend_from_slice(&v.to_le_bytes());
|
||||
crate::runtime::plugin_ffi_common::encode::i32(&mut buf, v);
|
||||
continue;
|
||||
}
|
||||
// String: tag=6
|
||||
if let Some(s) = a.as_any().downcast_ref::<StringBox>() {
|
||||
eprintln!("[PluginLoaderV2] arg[{}]: String(len={}) -> String(tag=6)", idx, s.value.len());
|
||||
let bytes = s.value.as_bytes();
|
||||
let len = std::cmp::min(bytes.len(), u16::MAX as usize);
|
||||
buf.push(6u8);
|
||||
buf.push(0u8);
|
||||
buf.extend_from_slice(&((len as u16).to_le_bytes()));
|
||||
buf.extend_from_slice(&bytes[..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 = a.to_string_box().value;
|
||||
let bytes = sv.as_bytes();
|
||||
let len = std::cmp::min(bytes.len(), u16::MAX as usize);
|
||||
buf.push(6u8);
|
||||
buf.push(0u8);
|
||||
buf.extend_from_slice(&((len as u16).to_le_bytes()));
|
||||
buf.extend_from_slice(&bytes[..len]);
|
||||
crate::runtime::plugin_ffi_common::encode::string(&mut buf, &sv);
|
||||
} else {
|
||||
return Err(BidError::InvalidArgs);
|
||||
}
|
||||
@ -540,12 +520,7 @@ impl PluginBoxV2 {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
if data.len() < 8 { return Ok(None); }
|
||||
let tag = data[4];
|
||||
let _rsv = data[5];
|
||||
let size = u16::from_le_bytes([data[6], data[7]]) as usize;
|
||||
if data.len() < 8 + size { return Ok(None); }
|
||||
let payload = &data[8..8+size];
|
||||
if let Some((tag, size, payload)) = crate::runtime::plugin_ffi_common::decode::tlv_first(data) {
|
||||
match tag {
|
||||
8 if size == 8 => { // Handle -> PluginBoxV2
|
||||
let mut t = [0u8;4]; t.copy_from_slice(&payload[0..4]);
|
||||
@ -583,13 +558,13 @@ impl PluginBoxV2 {
|
||||
None
|
||||
}
|
||||
2 if size == 4 => { // I32
|
||||
let mut b = [0u8;4]; b.copy_from_slice(payload);
|
||||
let val: Box<dyn NyashBox> = Box::new(IntegerBox::new(i32::from_le_bytes(b) as i64));
|
||||
if dbg_on() { eprintln!("[Plugin→VM] return i32 value={} (returns_result={})", i32::from_le_bytes(b), returns_result); }
|
||||
let n = crate::runtime::plugin_ffi_common::decode::i32(payload).unwrap();
|
||||
let val: Box<dyn NyashBox> = 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<dyn NyashBox>) } else { Some(val) }
|
||||
}
|
||||
6 | 7 => { // String/Bytes
|
||||
let s = String::from_utf8_lossy(payload).to_string();
|
||||
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); }
|
||||
if returns_result {
|
||||
// Heuristic: for Result-returning methods, string payload represents an error message
|
||||
@ -604,7 +579,7 @@ impl PluginBoxV2 {
|
||||
if returns_result { Some(Box::new(crate::boxes::result::NyashResultBox::new_ok(Box::new(crate::box_trait::VoidBox::new()))) as Box<dyn NyashBox>) } else { None }
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}} else { None }
|
||||
};
|
||||
Ok(result)
|
||||
}
|
||||
@ -746,7 +721,7 @@ impl PluginBoxV2 {
|
||||
let mut output_len = output_buffer.len();
|
||||
|
||||
// Create TLV-encoded empty arguments (version=1, argc=0)
|
||||
let tlv_args = vec![1u8, 0, 0, 0]; // version=1, argc=0
|
||||
let tlv_args = crate::runtime::plugin_ffi_common::encode_empty_args();
|
||||
eprintln!("🔍 Output buffer allocated, about to call plugin invoke_fn...");
|
||||
|
||||
let birth_result = unsafe {
|
||||
|
||||
Reference in New Issue
Block a user