diff --git a/CLAUDE.md b/CLAUDE.md index 5025db71..6119aefa 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -450,6 +450,29 @@ docs/ **📋 詳細**: [DOCUMENTATION_REORGANIZATION_STRATEGY.md](DOCUMENTATION_REORGANIZATION_STRATEGY.md) +## ðŸ¤ ãƒ—ãƒ­ã‚¢ã‚¯ãƒ†ã‚£ãƒ–é–‹ç™ºæ–¹é‡ + +### 🎯 エラー対応時ã®å§¿å‹¢ +エラーを見ã¤ã‘ãŸéš›ã¯ã€å˜ã«å ±å‘Šã™ã‚‹ã ã‘ã§ãªã: + +1. **🔠原因分æž** - ã‚¨ãƒ©ãƒ¼ã®æ ¹æœ¬åŽŸå› ã‚’æŽ¢ã‚‹ +2. **📊 影響範囲** - ä»–ã®ã‚³ãƒ¼ãƒ‰ã¸ã®å½±éŸ¿ã‚’調査 +3. **💡 æ”¹å–„ææ¡ˆ** - 関連ã™ã‚‹å•題もå«ã‚ã¦è§£æ±ºç­–ã‚’æç¤º +4. **🧹 機会改善** - デッドコード削除ãªã©ã€ã¤ã„ã§ã«ã§ãる改善も実施 + +### âš–ï¸ ãƒãƒ©ãƒ³ã‚¹ã®å–り方 +- **ç©æ¥µçš„ã«åˆ†æžãƒ»ææ¡ˆ**ã™ã‚‹ãŒã€æœ€çµ‚判断ã¯ãƒ¦ãƒ¼ã‚¶ãƒ¼ã«å§”ã­ã‚‹ +- 「ChatGPTã•ã‚“ã«ä»»ã›ã¦ã‚‹ã€ã¨è¨€ã‚れã¦ã‚‚ã€åˆ†æžçµæžœã¯å…±æœ‰ã™ã‚‹ +- 複数ã®AIãŒå”調ã™ã‚‹å ´åˆã§ã‚‚ã€å„自ã®è¦–点ã§ä¾¡å€¤ã‚’æä¾›ã™ã‚‹ + +### 📠例 +``` +⌠å—å‹•çš„: 「エラーをファイルã«å‡ºåŠ›ã—ã¾ã—ãŸã€ +✅ 能動的: 「エラーをファイルã«å‡ºåŠ›ã—ã¾ã—ãŸã€‚主ãªåŽŸå› ã¯åž‹ã®ä¸ä¸€è‡´ï¼ˆ7箇所)ã§ã€ + instance_id()ã®ãƒ¡ã‚½ãƒƒãƒ‰å‘¼ã³å‡ºã—修正ã§5ã¤è§£æ±ºã§ããã†ã§ã™ã€‚ + ã¾ãŸã€é–¢é€£ã—ã¦clone_boxã®å®Ÿè£…ã«ã‚‚åŒæ§˜ã®å•題を発見ã—ã¾ã—ãŸã€‚〠+``` + ## 🚨 コンテキスト圧縮時ã®é‡è¦ãƒ«ãƒ¼ãƒ« ### âš ï¸ **コンテキスト圧縮を検出ã—ãŸå ´åˆã®å¿…須手順** diff --git a/chatgpt5_build_errors.txt b/chatgpt5_build_errors.txt new file mode 100644 index 00000000..d12b9ae7 --- /dev/null +++ b/chatgpt5_build_errors.txt @@ -0,0 +1,24 @@ +ChatGPT5ŸÅkˆ‹Óëɨéü: + +1. [E0599] no method named `call_fini` found for reference `&enabled::PluginBoxV2` + 4@: src/scope_tracker.rs:45:28 + ¨éü: plugin.call_fini() - á½ÃÉLX(WjD + +2. [E0308] mismatched types (2‡@) + 4@: src/interpreter/core.rs:579:45, 618:45 + ¨éü: &**v - expected `&Box`, found `&dyn NyashBox` + +3. [E0615] attempted to take value of method `instance_id` (3‡@) + 4@: + - src/interpreter/expressions/calls.rs:695:98 + - src/interpreter/expressions/calls.rs:785:98 + - src/backend/vm.rs:567:90 + ¨éü: plugin.instance_id - á½ÃÉ’Õ£üëÉhWf( + îcH: plugin.instance_id() k ô + +4. [E0609] no field `invoke_fn` on type `&enabled::PluginBoxV2` + 4@: src/runtime/plugin_loader_v2.rs:139:19 + ¨éü: self.invoke_fn - X(WjDÕ£üëÉ + îcH: self.inner.invoke_fn + +: 7 n³óѤë¨éü \ No newline at end of file diff --git a/chatgpt5_build_errors_updated.txt b/chatgpt5_build_errors_updated.txt new file mode 100644 index 00000000..2e72d90b --- /dev/null +++ b/chatgpt5_build_errors_updated.txt @@ -0,0 +1,21 @@ +ChatGPT5実装ã«ã‚ˆã‚‹æ›´æ–°å¾Œã®ãƒ“ルドエラー: + +改善ã•れãŸç‚¹ï¼ˆä¿®æ­£æ¸ˆã¿ï¼‰: +- ✅ call_fini() メソッドエラー解決 +- ✅ instance_id フィールド/メソッドエラー解決 +- ✅ invoke_fn フィールドエラー解決 + +残存エラー(2個): + +1. [E0308] mismatched types - src/interpreter/core.rs:579:45 + エラー: &**v - expected `&Box`, found `&dyn NyashBox` + +2. [E0308] mismatched types - src/interpreter/core.rs:618:45 + エラー: &**v - expected `&Box`, found `&dyn NyashBox` + +推奨修正: +- &**v ã‚’ v ã«å¤‰æ›´ï¼ˆArcã¸ã®å‚ç…§ã¨ã—ã¦æ‰±ã†ï¼‰ +- ã¾ãŸã¯åž‹æ³¨é‡ˆã‚’ &dyn NyashBox ã«å¤‰æ›´ + +影響範囲: +- interpreter/core.rsã®ã¿ï¼ˆä»–ã®ãƒ¢ã‚¸ãƒ¥ãƒ¼ãƒ«ã®ã‚¨ãƒ©ãƒ¼ã¯è§£æ±ºæ¸ˆã¿ï¼‰ \ No newline at end of file diff --git a/docs/reference/boxes-system/plugin_lifecycle.md b/docs/reference/boxes-system/plugin_lifecycle.md index 889c7242..bdcf5b6e 100644 --- a/docs/reference/boxes-system/plugin_lifecycle.md +++ b/docs/reference/boxes-system/plugin_lifecycle.md @@ -1,6 +1,6 @@ -# プラグインBoxã®ãƒ©ã‚¤ãƒ•サイクル㨠nyash.toml methods 定義 +# プラグインBoxã®ãƒ©ã‚¤ãƒ•サイクル(v2)㨠nyash.toml 定義 -本書ã¯ã€ãƒ—ラグインBox(PluginBoxV2)ã®ç”Ÿæˆï¼ˆbirth)ã¨çµ‚了(finiï¼‰ã®æµã‚Œã€ãªã‚‰ã³ã« nyash.toml v2 ã«ãŠã‘ã‚‹ `methods` 定義ã®å½¹å‰²ã‚’ã¾ã¨ã‚ãŸã‚‚ã®ã§ã™ã€‚ +本書ã¯ã€ãƒ—ラグインBox(PluginBoxV2)ã®ç”Ÿæˆï¼ˆbirth)ã¨çµ‚了(finiï¼‰ã®æµã‚Œã€`singleton` オプションã€ãªã‚‰ã³ã« nyash.toml v2 ã«ãŠã‘ã‚‹ `methods` 定義ã®å½¹å‰²ã‚’ã¾ã¨ã‚ãŸã‚‚ã®ã§ã™ã€‚ --- @@ -15,26 +15,26 @@ 1. `unified registry` ㌠`PluginLoaderV2::create_box(box_type, args)` を呼ã³å‡ºã™ã€‚ 2. `PluginLoaderV2` 㯠`nyash.toml` ã‹ã‚‰ `type_id` 㨠`methods` を読ã¿è¾¼ã‚€ã€‚ 3. `invoke_fn(type_id, method_id=0 /* birth */, instance_id=0, ...)` を呼ã³ã€æˆ»ã‚Šå€¤ï¼ˆå‡ºåŠ›TLV)ã®å…ˆé ­4ãƒã‚¤ãƒˆã‹ã‚‰ `instance_id` ã‚’å–得。 -4. `PluginBoxV2 { type_id, instance_id, invoke_fn, fini_method_id }` を生æˆã—ã¦è¿”ã™ã€‚ +4. `PluginBoxV2 { box_type, inner: Arc }` を生æˆã—ã¦è¿”ã™ã€‚ + - `PluginHandleInner` 㯠`{ type_id, instance_id, invoke_fn, fini_method_id, finalized }` ã‚’ä¿æŒã—ã€å‚照カウント(Arc)ã§å…±æœ‰ã•れる。 補足: - `fini_method_id` 㯠`nyash.toml` ã® `methods` ã‹ã‚‰ `fini` ã® `method_id` ã‚’å–り出ã—ã¦ä¿æŒã—ã¾ã™ã€‚未定義ã®å ´åˆã¯ `None`。 --- -## 3. 終了(finiï¼‰ã®æµã‚Œï¼ˆç¾çŠ¶ï¼‰ +## 3. 終了(finiï¼‰ã®æµã‚Œï¼ˆç¾åœ¨ï¼‰ - ãƒ•ã‚£ãƒ¼ãƒ«ãƒ‰å·®ã—æ›¿ãˆæ™‚ï¼ˆä»£å…¥ã§æ—§å€¤ã‚’ç½®ãæ›ãˆã‚‹ã¨ã): - 旧値㌠`InstanceBox` ã®å ´åˆ: インタプリタ㌠`fini()` を呼ã³ã€finalized ã¨ã—ã¦ãƒžãƒ¼ã‚­ãƒ³ã‚°ã—ã¾ã™ã€‚ - 旧値㌠`PluginBoxV2` ã®å ´åˆ: `fini_method_id` ãŒè¨­å®šã•れã¦ã„れ㰠`invoke_fn(type_id, fini_method_id, instance_id, ...)` を呼ã³ã¾ã™ã€‚ -- 破棄(Drop)時: - - Rustã®Dropã§FFIを呼ã¶ã®ã¯å®‰å…¨æ€§ã®è¦³ç‚¹ã§ãƒªã‚¹ã‚¯ãŒã‚ã‚‹ãŸã‚ã€ç¾çжã¯ã€Œæ˜Žç¤ºã‚¿ã‚¤ãƒŸãƒ³ã‚°ï¼ˆãƒ•ã‚£ãƒ¼ãƒ«ãƒ‰å·®ã—æ›¿ãˆãªã©ï¼‰ã€ã§ã® fini 呼ã³å‡ºã—を優先ã—ã¦ã„ã¾ã™ã€‚ - -注æ„: -- ローカル変数ã®ã‚¹ã‚³ãƒ¼ãƒ—終了時ã«è‡ªå‹•ã§ fini を呼ã¶å®Ÿè£…ã¯ã€ç¾æ™‚点ã§ã¯å…¥ã£ã¦ã„ã¾ã›ã‚“ï¼ˆå°†æ¥æ¤œè¨Žï¼‰ã€‚ +- プラグインBox(PluginBoxV2): + - ã™ã¹ã¦ã®å‚照(Arc)ãŒDropã•れ「最後ã®å‚ç…§ãŒè§£æ”¾ã€ã•ã‚ŒãŸæ™‚ã€`Drop`ã§ä¸€åº¦ã ã‘ `fini` を呼ã¶ï¼ˆRAIIã€äºŒé‡å‘¼ã³å‡ºã—防止)。 + - 明示finiãŒå¿…è¦ãªå ´åˆã¯ `PluginBoxV2::finalize_now()` を使ãˆã‚‹ï¼ˆå†…部的ã«ä¸€åº¦ã ã‘fini実行)。 + - 代入/フィールド代入/Map.get/Array.get/slice/退é¿ãªã©ã¯ã€ŒPluginBoxV2ã¯å…±æœ‰ï¼ˆshare)ã€ãれ以外ã¯è¤‡è£½ï¼ˆclone)ã€ã§çµ±ä¸€ã€‚ --- -## 4. nyash.toml v2 ã®å®šç¾©ä¾‹ +## 4. nyash.toml v2 ã®å®šç¾©ä¾‹ï¼ˆmethods + singleton) ```toml [libraries] @@ -58,6 +58,24 @@ fini = { method_id = 4294967295 } # ä»»æ„ã®çµ‚端ID - `methods` ã« `fini` を定義ã™ã‚Œã°ã€å·®ã—æ›¿ãˆæ™‚ãªã©ã« fini ãŒå‘¼ã°ã‚Œã¾ã™ã€‚ - `fini` 未定義ã®å ´åˆã€ãƒ—ラグインBoxã®çµ‚了処ç†ã¯å‘¼ã°ã‚Œã¾ã›ã‚“(フォールãƒãƒƒã‚¯å‹•作)。 +### singleton例 + +```toml +[libraries."libnyash_counter_plugin.so".CounterBox] +type_id = 7 +singleton = true + +[libraries."libnyash_counter_plugin.so".CounterBox.methods] +birth = { method_id = 0 } +inc = { method_id = 1 } +get = { method_id = 2 } +fini = { method_id = 4294967295 } +``` + +- `singleton = true` を設定ã™ã‚‹ã¨ã€ãƒ­ãƒ¼ãƒ€ãƒ¼åˆæœŸåŒ–時ã«äº‹å‰birthã—ã€ãƒ­ãƒ¼ãƒ€ãƒ¼ãŒå…±æœ‰ãƒãƒ³ãƒ‰ãƒ«ã‚’ä¿æŒã—ã¾ã™ã€‚ +- `create_box()` ã¯ä¿æŒä¸­ã®å…±æœ‰ãƒãƒ³ãƒ‰ãƒ«ã‚’è¿”ã™ãŸã‚ã€è¤‡æ•°å›žã® `new` ã§ã‚‚åŒä¸€ã‚¤ãƒ³ã‚¹ã‚¿ãƒ³ã‚¹ã‚’共有ã§ãã¾ã™ã€‚ +- Nyash終了時(ã¾ãŸã¯æ˜Žç¤ºè¦æ±‚時)㫠`shutdown_plugins_v2()` を呼ã¶ã¨ã€ãƒ­ãƒ¼ãƒ€ãƒ¼ãŒä¿æŒã™ã‚‹å…¨ã‚·ãƒ³ã‚°ãƒ«ãƒˆãƒ³ã® `fini` を実行ã—ã€ã‚¯ãƒªãƒ¼ãƒ³ã«è§£æ”¾ã•れã¾ã™ã€‚ + --- ## 5. WASM(wasm-bindgen)ã¨ã®é–¢ä¿‚ diff --git a/nyash.toml b/nyash.toml index ffcf2a25..f578f080 100644 --- a/nyash.toml +++ b/nyash.toml @@ -38,6 +38,7 @@ cloneSelf = { method_id = 8 } [libraries."libnyash_counter_plugin.so".CounterBox] type_id = 7 +singleton = true [libraries."libnyash_counter_plugin.so".CounterBox.methods] birth = { method_id = 0 } diff --git a/src/backend/vm.rs b/src/backend/vm.rs index bca1184d..fa08b56a 100644 --- a/src/backend/vm.rs +++ b/src/backend/vm.rs @@ -564,7 +564,7 @@ impl VM { if let Some(plugin) = box_nyash.as_any().downcast_ref::() { let loader = crate::runtime::get_global_loader_v2(); let loader = loader.read().map_err(|_| VMError::InvalidInstruction("Plugin loader lock poisoned".into()))?; - match loader.invoke_instance_method(&plugin.box_type, method, plugin.instance_id, &arg_values) { + match loader.invoke_instance_method(&plugin.box_type, method, plugin.instance_id(), &arg_values) { Ok(Some(result_box)) => { if let Some(dst_id) = dst { self.set_value(*dst_id, VMValue::from_nyash_box(result_box)); diff --git a/src/boxes/array/mod.rs b/src/boxes/array/mod.rs index 4065f09f..a4432262 100644 --- a/src/boxes/array/mod.rs +++ b/src/boxes/array/mod.rs @@ -59,7 +59,13 @@ impl ArrayBox { let idx = idx_box.value as usize; let items = self.items.read().unwrap(); match items.get(idx) { - Some(item) => item.clone_box(), + Some(item) => { + #[cfg(all(feature = "plugins", not(target_arch = "wasm32")))] + if item.as_any().downcast_ref::().is_some() { + return item.share_box(); + } + item.clone_box() + } None => Box::new(crate::boxes::null_box::NullBox::new()), } } else { @@ -228,7 +234,13 @@ impl ArrayBox { // Create slice let slice_items: Vec> = items[start_idx..end_idx] .iter() - .map(|item| item.clone_box()) + .map(|item| { + #[cfg(all(feature = "plugins", not(target_arch = "wasm32")))] + if item.as_any().downcast_ref::().is_some() { + return item.share_box(); + } + item.clone_box() + }) .collect(); Box::new(ArrayBox::new_with_elements(slice_items)) @@ -241,7 +253,13 @@ impl Clone for ArrayBox { // ディープコピー(独立インスタンス) let items_guard = self.items.read().unwrap(); let cloned_items: Vec> = items_guard.iter() - .map(|item| item.clone_box()) // è¦ç´ ã‚‚ディープコピー + .map(|item| { + #[cfg(all(feature = "plugins", not(target_arch = "wasm32")))] + if item.as_any().downcast_ref::().is_some() { + return item.share_box(); + } + item.clone_box() + }) // è¦ç´ ã‚‚ディープコピー(ãƒãƒ³ãƒ‰ãƒ«ã¯å…±æœ‰ï¼‰ .collect(); ArrayBox { diff --git a/src/boxes/map_box.rs b/src/boxes/map_box.rs index 27a10b62..9f41840a 100644 --- a/src/boxes/map_box.rs +++ b/src/boxes/map_box.rs @@ -135,7 +135,13 @@ impl MapBox { pub fn get(&self, key: Box) -> Box { let key_str = key.to_string_box().value; match self.data.read().unwrap().get(&key_str) { - Some(value) => value.clone_box(), + Some(value) => { + #[cfg(all(feature = "plugins", not(target_arch = "wasm32")))] + if value.as_any().downcast_ref::().is_some() { + return value.share_box(); + } + value.clone_box() + } None => Box::new(StringBox::new(&format!("Key not found: {}", key_str))), } } @@ -310,4 +316,4 @@ impl Debug for MapBox { .field("keys", &data.keys().collect::>()) .finish() } -} \ No newline at end of file +} diff --git a/src/config/nyash_toml_v2.rs b/src/config/nyash_toml_v2.rs index 8033ebc3..5c3dd310 100644 --- a/src/config/nyash_toml_v2.rs +++ b/src/config/nyash_toml_v2.rs @@ -47,6 +47,10 @@ pub struct BoxTypeConfig { /// Method definitions pub methods: HashMap, + + /// Singleton service flag (keep one shared instance alive in loader) + #[serde(default)] + pub singleton: bool, } /// Method definition (simplified - no argument info needed) diff --git a/src/interpreter/core.rs b/src/interpreter/core.rs index f37768a8..64a97265 100644 --- a/src/interpreter/core.rs +++ b/src/interpreter/core.rs @@ -575,7 +575,14 @@ impl NyashInterpreter { /// local変数スタックをä¿å­˜ãƒ»å¾©å…ƒï¼ˆé–¢æ•°å‘¼ã³å‡ºã—時) pub(super) fn save_local_vars(&self) -> HashMap> { self.local_vars.iter() - .map(|(k, v)| (k.clone(), (**v).clone_box())) // Deref Arc to get the Box + .map(|(k, v)| { + let b: &dyn NyashBox = &**v; + #[cfg(all(feature = "plugins", not(target_arch = "wasm32")))] + if b.as_any().downcast_ref::().is_some() { + return (k.clone(), b.share_box()); + } + (k.clone(), b.clone_box()) + }) .collect() } @@ -593,12 +600,7 @@ impl NyashInterpreter { let _ = instance.fini(); eprintln!("🔄 Scope exit: Called fini() on local variable '{}' (InstanceBox)", name); } - // プラグインBoxã®å ´åˆ - #[cfg(all(feature = "plugins", not(target_arch = "wasm32")))] - if let Some(plugin) = (**value).as_any().downcast_ref::() { - plugin.call_fini(); - eprintln!("🔄 Scope exit: Called fini() on local variable '{}' (PluginBox)", name); - } + // プラグインBoxã¯å…±æœ‰ãƒãƒ³ãƒ‰ãƒ«ã®å¯èƒ½æ€§ãŒé«˜ã„ãŸã‚自動finiã—ãªã„(明示呼ã³å‡ºã—ã®ã¿ï¼‰ // ビルトインBoxã¯å…ƒã€…finiメソッドをæŒãŸãªã„ã®ã§å‘¼ã°ãªã„ // (StringBoxã€IntegerBoxç­‰ã¯ãƒªã‚½ãƒ¼ã‚¹ç®¡ç†ä¸è¦ï¼‰ } @@ -612,7 +614,14 @@ impl NyashInterpreter { /// outbox変数スタックをä¿å­˜ãƒ»å¾©å…ƒï¼ˆstatic関数呼ã³å‡ºã—時) pub(super) fn save_outbox_vars(&self) -> HashMap> { self.outbox_vars.iter() - .map(|(k, v)| (k.clone(), (**v).clone_box())) // Deref Arc to get the Box + .map(|(k, v)| { + let b: &dyn NyashBox = &**v; + #[cfg(all(feature = "plugins", not(target_arch = "wasm32")))] + if b.as_any().downcast_ref::().is_some() { + return (k.clone(), b.share_box()); + } + (k.clone(), b.clone_box()) + }) .collect() } @@ -624,12 +633,7 @@ impl NyashInterpreter { let _ = instance.fini(); eprintln!("🔄 Scope exit: Called fini() on outbox variable '{}' (InstanceBox)", name); } - // プラグインBoxã®å ´åˆ - #[cfg(all(feature = "plugins", not(target_arch = "wasm32")))] - if let Some(plugin) = (**value).as_any().downcast_ref::() { - plugin.call_fini(); - eprintln!("🔄 Scope exit: Called fini() on outbox variable '{}' (PluginBox)", name); - } + // プラグインBoxã¯å…±æœ‰ãƒãƒ³ãƒ‰ãƒ«ã®å¯èƒ½æ€§ãŒé«˜ã„ãŸã‚自動finiã—ãªã„ // ビルトインBoxã¯å…ƒã€…finiメソッドをæŒãŸãªã„ã®ã§å‘¼ã°ãªã„(è¦ä¿®æ­£ï¼‰ } diff --git a/src/interpreter/expressions/calls.rs b/src/interpreter/expressions/calls.rs index f089f016..69523a33 100644 --- a/src/interpreter/expressions/calls.rs +++ b/src/interpreter/expressions/calls.rs @@ -692,7 +692,7 @@ impl NyashInterpreter { } let loader = crate::runtime::get_global_loader_v2(); let loader = loader.read().unwrap(); - match loader.invoke_instance_method(&plugin.box_type, method, plugin.instance_id, &arg_values) { + match loader.invoke_instance_method(&plugin.box_type, method, plugin.instance_id(), &arg_values) { Ok(Some(result_box)) => return Ok(result_box), Ok(None) => return Ok(Box::new(VoidBox::new())), Err(_) => {} @@ -782,7 +782,7 @@ impl NyashInterpreter { if let Some(plugin) = plugin_ref.as_any().downcast_ref::() { let mut arg_values: Vec> = Vec::new(); for arg in arguments { arg_values.push(self.execute_expression(arg)?); } - match loader.invoke_instance_method(&plugin.box_type, method, plugin.instance_id, &arg_values) { + match loader.invoke_instance_method(&plugin.box_type, method, plugin.instance_id(), &arg_values) { Ok(Some(result_box)) => return Ok(result_box), Ok(None) => return Ok(Box::new(crate::box_trait::VoidBox::new())), Err(e) => { @@ -818,23 +818,23 @@ impl NyashInterpreter { { // 親ãŒãƒ¦ãƒ¼ã‚¶ãƒ¼å®šç¾©ã«è¦‹ã¤ã‹ã‚‰ãªã„å ´åˆã¯ã€ãƒ—ラグインã¨ã—ã¦è©¦è¡Œ // ç¾åœ¨ã®ã‚¤ãƒ³ã‚¹ã‚¿ãƒ³ã‚¹ã‹ã‚‰ __plugin_content ã‚’å‚ç…§ - if let Some(plugin_shared) = current_instance.get_field_legacy("__plugin_content") { - // å¼•æ•°ã‚’è©•ä¾¡ï¼ˆãƒ­ãƒƒã‚¯ã¯æ—¢ã«è§£æ”¾æ¸ˆã¿ã®è¨­è¨ˆï¼‰ - let plugin_ref = &*plugin_shared; - if let Some(plugin) = plugin_ref.as_any().downcast_ref::() { - let mut arg_values: Vec> = Vec::new(); - for arg in arguments { - arg_values.push(self.execute_expression(arg)?); + if let Some(plugin_shared) = current_instance.get_field_legacy("__plugin_content") { + // å¼•æ•°ã‚’è©•ä¾¡ï¼ˆãƒ­ãƒƒã‚¯ã¯æ—¢ã«è§£æ”¾æ¸ˆã¿ã®è¨­è¨ˆï¼‰ + let plugin_ref = &*plugin_shared; + if let Some(plugin) = plugin_ref.as_any().downcast_ref::() { + let mut arg_values: Vec> = Vec::new(); + for arg in arguments { + arg_values.push(self.execute_expression(arg)?); + } + let loader = crate::runtime::get_global_loader_v2(); + let loader = loader.read().unwrap(); + match loader.invoke_instance_method(&plugin.box_type, method, plugin.instance_id(), &arg_values) { + Ok(Some(result_box)) => return Ok(result_box), + Ok(None) => return Ok(Box::new(VoidBox::new())), + Err(_) => {} + } + } } - let loader = crate::runtime::get_global_loader_v2(); - let loader = loader.read().unwrap(); - match loader.invoke_instance_method(&plugin.box_type, method, plugin.instance_id, &arg_values) { - Ok(Some(result_box)) => return Ok(result_box), - Ok(None) => return Ok(Box::new(VoidBox::new())), - Err(_) => {} - } - } - } } // 3. 親クラスã®Box宣言をå–得(ユーザー定義Boxã®å ´åˆï¼‰ @@ -1003,7 +1003,7 @@ impl NyashInterpreter { } let loader_guard = crate::runtime::plugin_loader_v2::get_global_loader_v2(); let loader = loader_guard.read().map_err(|_| RuntimeError::RuntimeFailure { message: "Plugin loader lock poisoned".into() })?; - match loader.invoke_instance_method(&plugin_box.box_type, method, plugin_box.instance_id, &arg_values) { + match loader.invoke_instance_method(&plugin_box.box_type, method, plugin_box.instance_id(), &arg_values) { Ok(Some(result_box)) => Ok(result_box), Ok(None) => Ok(Box::new(VoidBox::new())), Err(e) => Err(RuntimeError::RuntimeFailure { message: format!("Plugin method {} failed: {:?}", method, e) }), diff --git a/src/runtime/plugin_loader_v2.rs b/src/runtime/plugin_loader_v2.rs index 404ee17a..9b5f5930 100644 --- a/src/runtime/plugin_loader_v2.rs +++ b/src/runtime/plugin_loader_v2.rs @@ -34,18 +34,71 @@ mod enabled { /// v2 Plugin Box wrapper - temporary implementation #[derive(Debug)] - pub struct PluginBoxV2 { - pub box_type: String, + 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, - /// Optional fini method_id from nyash.toml (None if not provided) pub fini_method_id: Option, + finalized: std::sync::atomic::AtomicBool, + } + + 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) { + 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, + ); + } + } + } + } + } + +#[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.instance_id as u64 + self.inner.instance_id as u64 } fn parent_type_id(&self) -> Option { @@ -53,7 +106,7 @@ mod enabled { } fn fmt_box(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{}({})", self.box_type, self.instance_id) + write!(f, "{}({})", self.box_type, self.inner.instance_id) } fn as_any(&self) -> &dyn Any { @@ -75,7 +128,7 @@ mod enabled { } fn clone_box(&self) -> Box { - eprintln!("🔠DEBUG: PluginBoxV2::clone_box called for {} (id={})", self.box_type, self.instance_id); + eprintln!("🔠DEBUG: PluginBoxV2::clone_box called for {} (id={})", self.box_type, self.inner.instance_id); // Clone means creating a new instance by calling birth() let mut output_buffer = vec![0u8; 1024]; @@ -83,8 +136,8 @@ mod enabled { let tlv_args = vec![1u8, 0, 0, 0]; // version=1, argc=0 let result = unsafe { - (self.invoke_fn)( - self.type_id, + (self.inner.invoke_fn)( + self.inner.type_id, 0, // method_id=0 (birth) 0, // instance_id=0 (static call) tlv_args.as_ptr(), @@ -103,13 +156,16 @@ mod enabled { eprintln!("🎉 clone_box success: created new {} instance_id={}", self.box_type, new_instance_id); - // Return new PluginBoxV2 with new instance_id + // Return new PluginBoxV2 with new instance_id (separate inner handle) Box::new(PluginBoxV2 { box_type: self.box_type.clone(), - type_id: self.type_id, - invoke_fn: self.invoke_fn, - instance_id: new_instance_id, - fini_method_id: self.fini_method_id, + 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 { eprintln!("⌠clone_box failed: birth() returned error code {}", result); @@ -119,7 +175,7 @@ mod enabled { } fn to_string_box(&self) -> crate::box_trait::StringBox { - StringBox::new(format!("{}({})", self.box_type, self.instance_id)) + StringBox::new(format!("{}({})", self.box_type, self.inner.instance_id)) } fn equals(&self, _other: &dyn NyashBox) -> crate::box_trait::BoolBox { @@ -127,43 +183,19 @@ mod enabled { } fn share_box(&self) -> Box { - eprintln!("🔠DEBUG: PluginBoxV2::share_box called for {} (id={})", self.box_type, self.instance_id); + 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(), - type_id: self.type_id, - invoke_fn: self.invoke_fn, - instance_id: self.instance_id, // Same instance_id - this is sharing! - fini_method_id: self.fini_method_id, + inner: self.inner.clone(), }) } } impl PluginBoxV2 { - /// Call fini() on this plugin instance if configured - pub fn call_fini(&self) { - if let Some(fini_id) = self.fini_method_id { - // Empty TLV args - let tlv_args: [u8; 4] = [1, 0, 0, 0]; - let mut out: [u8; 4] = [0; 4]; - let mut out_len: usize = out.len(); - let rc = 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, - ) - }; - if rc != 0 { - eprintln!("âš ï¸ PluginBoxV2::fini failed for {} id={} rc={}", self.box_type, self.instance_id, rc); - } - } - } + pub fn instance_id(&self) -> u32 { self.inner.instance_id } + pub fn finalize_now(&self) { self.inner.finalize_now() } } /// Plugin loader v2 @@ -175,6 +207,9 @@ impl PluginBoxV2 { 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>>, } impl PluginLoaderV2 { @@ -196,6 +231,7 @@ impl PluginBoxV2 { plugins: RwLock::new(HashMap::new()), config: None, config_path: None, + singletons: RwLock::new(HashMap::new()), } } @@ -225,10 +261,59 @@ impl PluginBoxV2 { eprintln!("Warning: Failed to load plugin {}: {:?}", lib_name, e); } } + // 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(()) } + /// 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)?; + let box_conf = config.get_box_config(lib_name, box_type, &toml_value).ok_or(BidError::InvalidType)?; + let type_id = box_conf.type_id; + // 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 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 = 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); + 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( @@ -344,12 +429,12 @@ impl PluginBoxV2 { // Plugin Handle (BoxRef): tag=8, size=8 if let Some(p) = a.as_any().downcast_ref::() { - eprintln!("[PluginLoaderV2] arg[{}]: PluginBoxV2({}, id={}) -> Handle(tag=8)", idx, p.box_type, p.instance_id); + 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.type_id.to_le_bytes()); - buf.extend_from_slice(&p.instance_id.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()); continue; } // Integer: prefer i32 @@ -436,10 +521,13 @@ impl PluginBoxV2 { let fini_id = ret_conf.methods.get("fini").map(|m| m.method_id); let pbox = PluginBoxV2 { box_type: ret_box.to_string(), - type_id: r_type, - invoke_fn: ret_plugin.invoke_fn, - instance_id: r_inst, - fini_method_id: fini_id, + 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), + }), }; return Ok(Some(Box::new(pbox) as Box)); } @@ -521,7 +609,7 @@ impl PluginBoxV2 { Ok(()) } - + /// Create a Box instance pub fn create_box(&self, box_type: &str, _args: &[Box]) -> BidResult> { eprintln!("🔠create_box called for: {}", box_type); @@ -538,6 +626,23 @@ impl PluginBoxV2 { 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 @@ -552,7 +657,6 @@ impl PluginBoxV2 { // Get type_id from config - read actual nyash.toml content eprintln!("🔠Reading nyash.toml for type configuration..."); - let cfg_path = self.config_path.as_ref().map(|s| s.as_str()).unwrap_or("nyash.toml"); let (type_id, fini_method_id) = 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) { @@ -616,14 +720,25 @@ impl PluginBoxV2 { // Create v2 plugin box wrapper with actual instance_id let plugin_box = PluginBoxV2 { box_type: box_type.to_string(), - type_id, - invoke_fn: plugin.invoke_fn, - instance_id, - fini_method_id, + 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), + }), }; 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 @@ -647,6 +762,14 @@ impl PluginBoxV2 { 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(()) + } } #[cfg(any(not(feature = "plugins"), target_arch = "wasm32"))] @@ -696,6 +819,7 @@ mod stub { 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")))] diff --git a/src/scope_tracker.rs b/src/scope_tracker.rs index 05a5c60b..7c30196b 100644 --- a/src/scope_tracker.rs +++ b/src/scope_tracker.rs @@ -39,10 +39,9 @@ impl ScopeTracker { let _ = instance.fini(); continue; } - // PluginBox: call plugin fini + // PluginBoxV2: do not auto-finalize (shared handle may be referenced elsewhere) #[cfg(all(feature = "plugins", not(target_arch = "wasm32")))] - if let Some(plugin) = arc_box.as_any().downcast_ref::() { - plugin.call_fini(); + if arc_box.as_any().downcast_ref::().is_some() { continue; } // Builtin and others: no-op for now diff --git a/tests/e2e_plugin_counterbox.rs b/tests/e2e_plugin_counterbox.rs index fad48bac..621c2496 100644 --- a/tests/e2e_plugin_counterbox.rs +++ b/tests/e2e_plugin_counterbox.rs @@ -72,3 +72,29 @@ v Err(e) => panic!("Counter assignment test failed: {:?}", e), } } + +#[test] +fn e2e_counter_mapbox_shares_handle() { + if !try_init_plugins() { return; } + + let code = r#" +local c, m, v +c = new CounterBox() +m = new MapBox() +m.set("k", c) +v = m.get("k") +v.inc() +// c should reflect the increment if handle is shared +v = c.get() +v +"#; + let ast = NyashParser::parse_from_string(code).expect("parse failed"); + let mut interpreter = nyash_rust::interpreter::NyashInterpreter::new(); + + match interpreter.execute(ast) { + Ok(result) => { + assert_eq!(result.to_string_box().value, "1"); + } + Err(e) => panic!("Counter MapBox share test failed: {:?}", e), + } +} diff --git a/tests/e2e_plugin_singleton.rs b/tests/e2e_plugin_singleton.rs new file mode 100644 index 00000000..da7af1d0 --- /dev/null +++ b/tests/e2e_plugin_singleton.rs @@ -0,0 +1,39 @@ +#![cfg(all(feature = "plugins", not(target_arch = "wasm32")))] + +use nyash_rust::parser::NyashParser; +use nyash_rust::runtime::plugin_loader_v2::{init_global_loader_v2, get_global_loader_v2}; +use nyash_rust::runtime::box_registry::get_global_registry; +use nyash_rust::runtime::PluginConfig; + +fn try_init_plugins() -> bool { + if !std::path::Path::new("nyash.toml").exists() { return false; } + if let Err(e) = init_global_loader_v2("nyash.toml") { eprintln!("init failed: {:?}", e); return false; } + let loader = get_global_loader_v2(); + let loader = loader.read().unwrap(); + if let Some(conf) = &loader.config { + let mut map = std::collections::HashMap::new(); + for (lib, def) in &conf.libraries { for b in &def.boxes { map.insert(b.clone(), lib.clone()); } } + get_global_registry().apply_plugin_config(&PluginConfig { plugins: map }); + true + } else { false } +} + +#[test] +fn e2e_counterbox_singleton_shared_across_news() { + if !try_init_plugins() { return; } + + // CounterBox is configured as singleton in nyash.toml + let code = r#" +local a, b, v +a = new CounterBox() +b = new CounterBox() +a.inc() +v = b.get() +v +"#; + let ast = NyashParser::parse_from_string(code).expect("parse"); + let mut interpreter = nyash_rust::interpreter::NyashInterpreter::new(); + let result = interpreter.execute(ast).expect("exec"); + assert_eq!(result.to_string_box().value, "1"); +} + diff --git a/tests/e2e_plugin_singleton_shutdown.rs b/tests/e2e_plugin_singleton_shutdown.rs new file mode 100644 index 00000000..8f2129df --- /dev/null +++ b/tests/e2e_plugin_singleton_shutdown.rs @@ -0,0 +1,51 @@ +#![cfg(all(feature = "plugins", not(target_arch = "wasm32")))] + +use nyash_rust::parser::NyashParser; +use nyash_rust::runtime::plugin_loader_v2::{init_global_loader_v2, get_global_loader_v2, shutdown_plugins_v2}; +use nyash_rust::runtime::box_registry::get_global_registry; +use nyash_rust::runtime::PluginConfig; + +fn try_init_plugins() -> bool { + if !std::path::Path::new("nyash.toml").exists() { return false; } + if let Err(e) = init_global_loader_v2("nyash.toml") { eprintln!("init failed: {:?}", e); return false; } + let loader = get_global_loader_v2(); + let loader = loader.read().unwrap(); + if let Some(conf) = &loader.config { + let mut map = std::collections::HashMap::new(); + for (lib, def) in &conf.libraries { for b in &def.boxes { map.insert(b.clone(), lib.clone()); } } + get_global_registry().apply_plugin_config(&PluginConfig { plugins: map }); + true + } else { false } +} + +#[test] +fn e2e_singleton_shutdown_and_recreate() { + if !try_init_plugins() { return; } + + // Use CounterBox singleton and bump to 1 + let code1 = r#" +local a +a = new CounterBox() +a.inc() +"#; + let ast1 = NyashParser::parse_from_string(code1).expect("parse1"); + let mut interpreter = nyash_rust::interpreter::NyashInterpreter::new(); + interpreter.execute(ast1).expect("exec1"); + + // Shutdown plugins (finalize singleton) + shutdown_plugins_v2().expect("shutdown ok"); + + // Re-init plugins and ensure singleton is recreated (count resets to 0) + assert!(try_init_plugins()); + let code2 = r#" +local b, v +b = new CounterBox() +v = b.get() +v +"#; + let ast2 = NyashParser::parse_from_string(code2).expect("parse2"); + let mut interpreter2 = nyash_rust::interpreter::NyashInterpreter::new(); + let result = interpreter2.execute(ast2).expect("exec2"); + assert_eq!(result.to_string_box().value, "0"); +} +