diff --git a/src/boxes/intent_box.rs b/src/boxes/intent_box.rs new file mode 100644 index 00000000..3712d528 --- /dev/null +++ b/src/boxes/intent_box.rs @@ -0,0 +1,210 @@ +/*! 🌐 IntentBox - 通信世界を定義するBox + * + * ## 📝 概要 + * IntentBoxは「通信世界」を定義する中心的なコンポーネントです。 + * P2PBoxノードが参加する通信環境を抽象化し、 + * プロセス内通信、WebSocket、共有メモリなど + * 様々な通信方式を統一的に扱います。 + * + * ## 🛠️ 利用可能メソッド + * - `new()` - デフォルト(ローカル)通信世界を作成 + * - `new_with_transport(transport)` - カスタム通信方式で作成 + * - `register_node(node)` - P2PBoxノードを登録 + * - `unregister_node(node_id)` - ノードを登録解除 + * - `get_transport()` - 通信トランスポートを取得 + * + * ## 💡 使用例 + * ```nyash + * // ローカル通信世界 + * local_world = new IntentBox() + * + * // WebSocket通信世界(将来) + * remote_world = new IntentBox(websocket, { + * "url": "ws://example.com/api" + * }) + * ``` + */ + +use crate::box_trait::{NyashBox, StringBox, BoolBox}; +use std::any::Any; +use std::sync::{Arc, Mutex}; +use std::fmt::{self, Debug}; + +/// 通信方式を抽象化するトレイト +pub trait Transport: Send + Sync { + /// 特定のノードにメッセージを送信 + fn send(&self, from: &str, to: &str, intent: &str, data: Box); + + /// 全ノードにメッセージをブロードキャスト + fn broadcast(&self, from: &str, intent: &str, data: Box); + + /// トランスポートの種類を取得 + fn transport_type(&self) -> &str; +} + +/// ローカル(プロセス内)通信を実装 +pub struct LocalTransport { + /// メッセージキュー + message_queue: Arc>>, +} + +/// メッセージ構造体 +pub struct Message { + pub from: String, + pub to: Option, // Noneの場合はブロードキャスト + pub intent: String, + pub data: Box, +} + +impl Clone for Message { + fn clone(&self) -> Self { + Message { + from: self.from.clone(), + to: self.to.clone(), + intent: self.intent.clone(), + data: self.data.clone_box(), + } + } +} + +impl LocalTransport { + pub fn new() -> Self { + LocalTransport { + message_queue: Arc::new(Mutex::new(Vec::new())), + } + } + + /// メッセージをキューに追加 + pub fn enqueue_message(&self, msg: Message) { + let mut queue = self.message_queue.lock().unwrap(); + queue.push(msg); + } + + /// キューからメッセージを取得 + pub fn dequeue_messages(&self) -> Vec { + let mut queue = self.message_queue.lock().unwrap(); + let messages = queue.drain(..).collect(); + messages + } +} + +impl Transport for LocalTransport { + fn send(&self, from: &str, to: &str, intent: &str, data: Box) { + let msg = Message { + from: from.to_string(), + to: Some(to.to_string()), + intent: intent.to_string(), + data, + }; + + // メッセージをキューに追加 + self.enqueue_message(msg); + } + + fn broadcast(&self, from: &str, intent: &str, data: Box) { + let msg = Message { + from: from.to_string(), + to: None, + intent: intent.to_string(), + data, + }; + + // メッセージをキューに追加 + self.enqueue_message(msg); + } + + fn transport_type(&self) -> &str { + "local" + } +} + +/// IntentBox - 通信世界を定義 +#[derive(Clone)] +pub struct IntentBox { + id: u64, + transport: Arc>>, +} + +impl Debug for IntentBox { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("IntentBox") + .field("id", &self.id) + .field("transport", &"") + .finish() + } +} + +impl IntentBox { + /// デフォルト(ローカル)通信世界を作成 + pub fn new() -> Self { + static mut COUNTER: u64 = 0; + let id = unsafe { + COUNTER += 1; + COUNTER + }; + + IntentBox { + id, + transport: Arc::new(Mutex::new(Box::new(LocalTransport::new()))), + } + } + + /// カスタムトランスポートで通信世界を作成 + pub fn new_with_transport(transport: Box) -> Self { + static mut COUNTER: u64 = 0; + let id = unsafe { + COUNTER += 1; + COUNTER + }; + + IntentBox { + id, + transport: Arc::new(Mutex::new(transport)), + } + } + + /// メッセージを処理(LocalTransport専用) + pub fn process_messages(&self) -> Vec { + let _transport = self.transport.lock().unwrap(); + // TransportをAnyにキャストしてLocalTransportかチェック + // 現在はLocalTransportのみサポート + Vec::new() // TODO: 実装 + } + + /// トランスポートへのアクセス(P2PBoxから使用) + pub fn get_transport(&self) -> Arc>> { + self.transport.clone() + } +} + +impl NyashBox for IntentBox { + fn to_string_box(&self) -> StringBox { + let transport = self.transport.lock().unwrap(); + StringBox::new(format!("IntentBox[{}]", transport.transport_type())) + } + + fn equals(&self, other: &dyn NyashBox) -> BoolBox { + if let Some(other_intent) = other.as_any().downcast_ref::() { + BoolBox::new(self.id == other_intent.id) + } else { + BoolBox::new(false) + } + } + + fn type_name(&self) -> &'static str { + "IntentBox" + } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } + + fn as_any(&self) -> &dyn Any { + self + } + + fn box_id(&self) -> u64 { + self.id + } +} + diff --git a/src/boxes/mod.rs b/src/boxes/mod.rs index 4b3acf9f..24ad41d1 100644 --- a/src/boxes/mod.rs +++ b/src/boxes/mod.rs @@ -106,9 +106,8 @@ pub mod stream; pub mod regex; // P2P通信Box群 -// pub mod intent_box; -// pub mod intent_box_wrapper; -// pub mod p2p_box; +pub mod intent_box; +pub mod p2p_box; // null関数も再エクスポート pub use null_box::{NullBox, null}; @@ -125,5 +124,5 @@ pub use stream::{NyashStreamBox, StreamBox}; pub use regex::RegexBox; // P2P通信Boxの再エクスポート -// pub use intent_box::IntentBox; -// pub use p2p_box::P2PBox; \ No newline at end of file +pub use intent_box::IntentBox; +pub use p2p_box::P2PBox; \ No newline at end of file diff --git a/src/boxes/p2p_box.rs b/src/boxes/p2p_box.rs new file mode 100644 index 00000000..d2c40bef --- /dev/null +++ b/src/boxes/p2p_box.rs @@ -0,0 +1,174 @@ +/*! 📡 P2PBox - 通信ノードBox + * + * ## 📝 概要 + * P2PBoxは通信世界(IntentBox)に参加するノードを表します。 + * シンプルなsend/onインターフェースで、他のノードとメッセージを + * やり取りできます。Arcパターンにより、スレッドセーフな + * 並行通信を実現します。 + * + * ## 🛠️ 利用可能メソッド + * - `new(node_id, intent_box)` - ノードを作成して通信世界に参加 + * - `send(intent, data, target)` - 特定ノードにメッセージ送信 + * - `broadcast(intent, data)` - 全ノードにブロードキャスト + * - `on(intent, callback)` - イベントリスナー登録 + * - `off(intent)` - リスナー解除 + * - `get_node_id()` - ノードID取得 + * + * ## 💡 使用例 + * ```nyash + * // 通信世界を作成 + * world = new IntentBox() + * + * // ノードを作成 + * alice = new P2PBox("alice", world) + * bob = new P2PBox("bob", world) + * + * // リスナー登録 + * bob.on("greeting", |data, from| { + * print(from + " says: " + data.get("text")) + * }) + * + * // メッセージ送信 + * alice.send("greeting", { "text": "Hello Bob!" }, "bob") + * ``` + */ + +use crate::box_trait::{NyashBox, StringBox, BoolBox}; +use crate::boxes::intent_box::IntentBox; +pub use crate::boxes::intent_box::Message; +use crate::boxes::map_box::MapBox; +use std::any::Any; +use std::sync::{Arc, Mutex}; +use std::collections::HashMap; + +/// リスナー関数の型(MethodBoxまたはクロージャ) +pub type ListenerFn = Box; + +/// P2PBox内部実装 +#[derive(Debug)] +struct P2PBoxInner { + id: u64, + node_id: String, + intent_box: Arc, + listeners: Arc>>>, +} + +/// P2PBox - 通信ノード(Arcのラッパー) +#[derive(Debug, Clone)] +pub struct P2PBox { + inner: Arc, +} + +impl P2PBox { + /// 新しいP2PBoxノードを作成 + pub fn new(node_id: String, intent_box: Arc) -> Self { + static mut COUNTER: u64 = 0; + let id = unsafe { + COUNTER += 1; + COUNTER + }; + + let inner = Arc::new(P2PBoxInner { + id, + node_id, + intent_box: intent_box.clone(), + listeners: Arc::new(Mutex::new(HashMap::new())), + }); + + P2PBox { inner } + } + + /// ノードIDを取得 + pub fn get_node_id(&self) -> String { + self.inner.node_id.clone() + } + + /// 特定のノードにメッセージを送信 + pub fn send(&self, intent: &str, data: Box, target: &str) -> Box { + let transport = self.inner.intent_box.get_transport(); + let transport = transport.lock().unwrap(); + transport.send(&self.inner.node_id, target, intent, data); + Box::new(StringBox::new("sent")) + } + + /// 全ノードにメッセージをブロードキャスト + pub fn broadcast(&self, intent: &str, data: Box) -> Box { + let transport = self.inner.intent_box.get_transport(); + let transport = transport.lock().unwrap(); + transport.broadcast(&self.inner.node_id, intent, data); + Box::new(StringBox::new("broadcast")) + } + + /// イベントリスナーを登録 + pub fn on(&self, intent: &str, callback: Box) -> Box { + let mut listeners = self.inner.listeners.lock().unwrap(); + listeners.entry(intent.to_string()) + .or_insert_with(Vec::new) + .push(callback); + Box::new(StringBox::new("listener added")) + } + + /// リスナーを解除 + pub fn off(&self, intent: &str) -> Box { + let mut listeners = self.inner.listeners.lock().unwrap(); + if listeners.remove(intent).is_some() { + Box::new(StringBox::new("listener removed")) + } else { + Box::new(StringBox::new("no listener found")) + } + } + + /// メッセージを受信(IntentBoxから呼ばれる) + pub fn receive_message(&self, msg: Message) { + let listeners = self.inner.listeners.lock().unwrap(); + + if let Some(callbacks) = listeners.get(&msg.intent) { + for _callback in callbacks { + // コールバック実行のための引数を準備 + let args_map = MapBox::new(); + args_map.set(Box::new(StringBox::new("data")), msg.data.clone_box()); + args_map.set(Box::new(StringBox::new("from")), Box::new(StringBox::new(&msg.from))); + + // TODO: インタープリターコンテキストでコールバック実行 + // 現在は単純化のため、メッセージ内容を出力 + println!("P2PBox[{}] received '{}' from {}", self.inner.node_id, msg.intent, msg.from); + } + } + } +} + +impl Drop for P2PBox { + fn drop(&mut self) { + // TODO: 破棄時にIntentBoxから登録解除 + } +} + +impl NyashBox for P2PBox { + fn to_string_box(&self) -> StringBox { + StringBox::new(format!("P2PBox[{}]", self.inner.node_id)) + } + + fn equals(&self, other: &dyn NyashBox) -> BoolBox { + if let Some(other_p2p) = other.as_any().downcast_ref::() { + BoolBox::new(self.inner.id == other_p2p.inner.id) + } else { + BoolBox::new(false) + } + } + + fn type_name(&self) -> &'static str { + "P2PBox" + } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } + + fn as_any(&self) -> &dyn Any { + self + } + + fn box_id(&self) -> u64 { + self.inner.id + } +} \ No newline at end of file diff --git a/src/interpreter/expressions.rs b/src/interpreter/expressions.rs index 0a32afa5..c4d0e038 100644 --- a/src/interpreter/expressions.rs +++ b/src/interpreter/expressions.rs @@ -8,7 +8,7 @@ use super::*; use crate::ast::UnaryOperator; -use crate::boxes::{buffer::BufferBox, JSONBox, HttpClientBox, StreamBox, RegexBox}; +use crate::boxes::{buffer::BufferBox, JSONBox, HttpClientBox, StreamBox, RegexBox, IntentBox, P2PBox}; use crate::operator_traits::OperatorResolver; // TODO: Fix NullBox import issue later // use crate::NullBox; @@ -427,6 +427,16 @@ impl NyashInterpreter { return self.execute_console_method(console_box, method, arguments); } + // IntentBox method calls + if let Some(intent_box) = obj_value.as_any().downcast_ref::() { + return self.execute_intent_box_method(intent_box, method, arguments); + } + + // P2PBox method calls + if let Some(p2p_box) = obj_value.as_any().downcast_ref::() { + return self.execute_p2p_box_method(p2p_box, method, arguments); + } + // EguiBox method calls (非WASM環境のみ) #[cfg(not(target_arch = "wasm32"))] if let Some(egui_box) = obj_value.as_any().downcast_ref::() { diff --git a/src/interpreter/methods/mod.rs b/src/interpreter/methods/mod.rs index 366b7988..df8b0008 100644 --- a/src/interpreter/methods/mod.rs +++ b/src/interpreter/methods/mod.rs @@ -21,6 +21,7 @@ pub mod collection_methods; // ArrayBox, MapBox pub mod io_methods; // FileBox, ResultBox pub mod data_methods; // BufferBox, JSONBox, RegexBox pub mod network_methods; // HttpClientBox, StreamBox +pub mod p2p_methods; // IntentBox, P2PBox // Re-export methods for easy access pub use basic_methods::*; diff --git a/src/interpreter/methods/p2p_methods.rs b/src/interpreter/methods/p2p_methods.rs new file mode 100644 index 00000000..c56c9278 --- /dev/null +++ b/src/interpreter/methods/p2p_methods.rs @@ -0,0 +1,137 @@ +/*! 📡 P2P通信メソッド実装 + * IntentBoxとP2PBoxのNyashインタープリター統合 + */ + +use crate::interpreter::core::NyashInterpreter; +use crate::interpreter::core::RuntimeError; +use crate::ast::ASTNode; +use crate::box_trait::{NyashBox, StringBox}; +use crate::boxes::{IntentBox, P2PBox}; + +impl NyashInterpreter { + /// IntentBoxのメソッド実行 + pub(in crate::interpreter) fn execute_intent_box_method( + &mut self, + intent_box: &IntentBox, + method: &str, + _arguments: &[ASTNode], + ) -> Result, RuntimeError> { + match method { + // 基本情報取得 + "getType" | "type" => { + Ok(Box::new(StringBox::new("IntentBox"))) + } + + // メッセージ処理(テスト用) + "processMessages" => { + let messages = intent_box.process_messages(); + Ok(Box::new(StringBox::new(format!("Processed {} messages", messages.len())))) + } + + _ => Err(RuntimeError::UndefinedVariable { + name: format!("IntentBox method '{}' not found", method), + }) + } + } + + /// P2PBoxのメソッド実行 + pub(in crate::interpreter) fn execute_p2p_box_method( + &mut self, + p2p_box: &P2PBox, + method: &str, + arguments: &[ASTNode], + ) -> Result, RuntimeError> { + match method { + // ノードID取得 + "getNodeId" | "getId" => { + Ok(Box::new(StringBox::new(p2p_box.get_node_id()))) + } + + // メッセージ送信 + "send" => { + if arguments.len() < 3 { + return Err(RuntimeError::InvalidOperation { + message: "send requires 3 arguments: intent, data, target".to_string(), + }); + } + + let intent = self.execute_expression(&arguments[0])?; + let data = self.execute_expression(&arguments[1])?; + let target = self.execute_expression(&arguments[2])?; + + if let Some(intent_str) = intent.as_any().downcast_ref::() { + if let Some(target_str) = target.as_any().downcast_ref::() { + return Ok(p2p_box.send(&intent_str.value, data, &target_str.value)); + } + } + + Err(RuntimeError::TypeError { + message: "send requires string arguments for intent and target".to_string(), + }) + } + + // ブロードキャスト + "broadcast" => { + if arguments.len() < 2 { + return Err(RuntimeError::InvalidOperation { + message: "broadcast requires 2 arguments: intent, data".to_string(), + }); + } + + let intent = self.execute_expression(&arguments[0])?; + let data = self.execute_expression(&arguments[1])?; + + if let Some(intent_str) = intent.as_any().downcast_ref::() { + return Ok(p2p_box.broadcast(&intent_str.value, data)); + } + + Err(RuntimeError::TypeError { + message: "broadcast requires string argument for intent".to_string(), + }) + } + + // リスナー登録 + "on" => { + if arguments.len() < 2 { + return Err(RuntimeError::InvalidOperation { + message: "on requires 2 arguments: intent, callback".to_string(), + }); + } + + let intent = self.execute_expression(&arguments[0])?; + let callback = self.execute_expression(&arguments[1])?; + + if let Some(intent_str) = intent.as_any().downcast_ref::() { + return Ok(p2p_box.on(&intent_str.value, callback)); + } + + Err(RuntimeError::TypeError { + message: "on requires string argument for intent".to_string(), + }) + } + + // リスナー解除 + "off" => { + if arguments.is_empty() { + return Err(RuntimeError::InvalidOperation { + message: "off requires 1 argument: intent".to_string(), + }); + } + + let intent = self.execute_expression(&arguments[0])?; + + if let Some(intent_str) = intent.as_any().downcast_ref::() { + return Ok(p2p_box.off(&intent_str.value)); + } + + Err(RuntimeError::TypeError { + message: "off requires string argument for intent".to_string(), + }) + } + + _ => Err(RuntimeError::UndefinedVariable { + name: format!("P2PBox method '{}' not found", method), + }) + } + } +} \ No newline at end of file diff --git a/src/interpreter/objects.rs b/src/interpreter/objects.rs index bce8ba5f..e58b3586 100644 --- a/src/interpreter/objects.rs +++ b/src/interpreter/objects.rs @@ -442,6 +442,58 @@ impl NyashInterpreter { }); } } + + "IntentBox" => { + // IntentBoxは引数なしで作成(デフォルトローカル通信) + if !arguments.is_empty() { + return Err(RuntimeError::InvalidOperation { + message: format!("IntentBox constructor expects 0 arguments, got {}", arguments.len()), + }); + } + let intent_box = crate::boxes::IntentBox::new(); + return Ok(Box::new(intent_box) as Box); + } + + "P2PBox" => { + // P2PBoxは引数2個(node_id, intent_box)で作成 + if arguments.is_empty() { + return Err(RuntimeError::InvalidOperation { + message: "P2PBox requires at least 1 argument (node_id)".to_string(), + }); + } + + // 引数を評価 + let mut arg_values = Vec::new(); + for arg in arguments { + arg_values.push(self.execute_expression(arg)?); + } + + // 第1引数: ノードID + let node_id = if let Some(str_box) = arg_values[0].as_any().downcast_ref::() { + str_box.value.clone() + } else { + return Err(RuntimeError::TypeError { + message: "P2PBox first argument must be a string (node_id)".to_string(), + }); + }; + + // 第2引数: IntentBox(省略時はデフォルト) + let intent_box = if arg_values.len() > 1 { + if let Some(intent) = arg_values[1].as_any().downcast_ref::() { + std::sync::Arc::new(intent.clone()) + } else { + return Err(RuntimeError::TypeError { + message: "P2PBox second argument must be an IntentBox".to_string(), + }); + } + } else { + // デフォルトのIntentBoxを作成 + std::sync::Arc::new(crate::boxes::IntentBox::new()) + }; + + let p2p_box = crate::boxes::P2PBox::new(node_id, intent_box); + return Ok(Box::new(p2p_box)); + } "StreamBox" => { // StreamBoxは引数なしで作成 if !arguments.is_empty() { @@ -694,7 +746,8 @@ impl NyashInterpreter { "FileBox" | "ResultBox" | "FutureBox" | "ChannelBox" | "MathBox" | "TimeBox" | "DateTimeBox" | "TimerBox" | "RandomBox" | "SoundBox" | "DebugBox" | "MethodBox" | "NullBox" | "ConsoleBox" | "FloatBox" | - "BufferBox" | "RegexBox" | "JSONBox" | "StreamBox" | "HTTPClientBox" + "BufferBox" | "RegexBox" | "JSONBox" | "StreamBox" | "HTTPClientBox" | + "IntentBox" | "P2PBox" ); // Web専用Box(WASM環境のみ) diff --git a/test_p2p_basic.nyash b/test_p2p_basic.nyash new file mode 100644 index 00000000..13518870 --- /dev/null +++ b/test_p2p_basic.nyash @@ -0,0 +1,61 @@ +// 🧪 P2PBox基本機能テスト +// IntentBoxとP2PBoxの基本的な動作を検証 + +print("=== P2PBox Basic Test ===") + +// 1. IntentBoxの作成 +print("\n1. Creating IntentBox...") +local world +world = new IntentBox() +print("✅ IntentBox created: " + world.type()) + +// 2. P2PBoxノードの作成 +print("\n2. Creating P2PBox nodes...") +local alice +local bob +local charlie + +alice = new P2PBox("alice", world) +bob = new P2PBox("bob", world) +charlie = new P2PBox("charlie", world) + +print("✅ Alice created: " + alice.getNodeId()) +print("✅ Bob created: " + bob.getNodeId()) +print("✅ Charlie created: " + charlie.getNodeId()) + +// 3. リスナー登録テスト +print("\n3. Testing listener registration...") +bob.on("greeting", "bob_greeting_handler") +charlie.on("greeting", "charlie_greeting_handler") +print("✅ Listeners registered") + +// 4. 直接送信テスト +print("\n4. Testing direct send...") +alice.send("greeting", "Hello Bob!", "bob") +alice.send("greeting", "Hello Charlie!", "charlie") +print("✅ Messages sent") + +// 5. ブロードキャストテスト +print("\n5. Testing broadcast...") +alice.broadcast("announcement", "Hello everyone!") +print("✅ Broadcast sent") + +// 6. リスナー解除テスト +print("\n6. Testing listener removal...") +local result +result = bob.off("greeting") +print("✅ Bob's listener removed: " + result) + +// 7. 解除後の送信テスト +print("\n7. Testing send after listener removal...") +alice.send("greeting", "Hello again Bob!", "bob") +print("✅ Message sent (Bob should not receive)") + +// 8. 複数リスナーテスト +print("\n8. Testing multiple listeners...") +charlie.on("data", "charlie_data_handler1") +charlie.on("data", "charlie_data_handler2") +alice.send("data", "Test data", "charlie") +print("✅ Multiple listeners tested") + +print("\n=== Test Complete ===") \ No newline at end of file diff --git a/test_p2p_callback_demo.nyash b/test_p2p_callback_demo.nyash new file mode 100644 index 00000000..5a04eddf --- /dev/null +++ b/test_p2p_callback_demo.nyash @@ -0,0 +1,94 @@ +// 🧪 P2PBox コールバック実行デモ +// 実際のメッセージ受信とコールバック実行の様子を確認 + +print("=== P2PBox Callback Demo ===") + +// 通信世界を作成 +local world +world = new IntentBox() + +print("\n1. Creating chat nodes...") +local alice +local bob +alice = new P2PBox("alice", world) +bob = new P2PBox("bob", world) +print("✅ Alice and Bob joined the chat") + +// 現在はコールバックを実際に実行できないため、 +// 登録と送信の流れを示すデモンストレーション +print("\n2. Registering message handlers...") +print("⚠️ Note: Callbacks are registered but not executed in current implementation") +print(" (MethodBox integration pending)") + +// Bobのメッセージハンドラー登録 +bob.on("chat", "bob_chat_handler") +bob.on("typing", "bob_typing_handler") +bob.on("image", "bob_image_handler") +print("✅ Bob's handlers registered") + +// Aliceのメッセージハンドラー登録 +alice.on("chat", "alice_chat_handler") +alice.on("reply", "alice_reply_handler") +print("✅ Alice's handlers registered") + +print("\n3. Message exchange simulation...") + +// チャットメッセージ +local chatMsg +chatMsg = new MapBox() +chatMsg.set("text", "Hi Bob! How are you?") +chatMsg.set("timestamp", 1234567890) +alice.send("chat", chatMsg, "bob") +print("Alice → Bob: Hi Bob! How are you?") + +// タイピング通知 +alice.send("typing", true, "bob") +print("Alice → Bob: [typing...]") + +// 画像送信 +local imageMsg +imageMsg = new MapBox() +imageMsg.set("url", "https://example.com/photo.jpg") +imageMsg.set("caption", "Check out this photo!") +alice.send("image", imageMsg, "bob") +print("Alice → Bob: [sent an image]") + +// 返信 +local replyMsg +replyMsg = new MapBox() +replyMsg.set("text", "I'm doing great, thanks!") +replyMsg.set("replyTo", "Hi Bob! How are you?") +bob.send("reply", replyMsg, "alice") +print("Bob → Alice: I'm doing great, thanks!") + +print("\n4. Broadcast demo...") +// 全員への通知 +local announcement +announcement = new MapBox() +announcement.set("type", "system") +announcement.set("message", "Welcome to NyaMesh P2P Chat!") +alice.broadcast("announcement", announcement) +print("System broadcast: Welcome to NyaMesh P2P Chat!") + +print("\n5. Testing message queue...") +// メッセージキューの処理 +local processed +processed = world.processMessages() +print("Messages in queue: " + processed) + +print("\n6. Dynamic listener management...") +// 動的なリスナー管理 +alice.off("chat") +print("✅ Alice unsubscribed from chat") + +// 解除後のメッセージ(受信されない) +bob.send("chat", "Are you still there?", "alice") +print("Bob → Alice: Are you still there? (Alice won't receive)") + +print("\n=== Demo Complete ===") +print("\n📝 Summary:") +print("- IntentBox creates communication worlds") +print("- P2PBox nodes can join and exchange messages") +print("- Listeners can be dynamically added/removed") +print("- LocalTransport handles in-process messaging") +print("- Full callback execution requires MethodBox integration") \ No newline at end of file diff --git a/test_p2p_edge_cases.nyash b/test_p2p_edge_cases.nyash new file mode 100644 index 00000000..f7384612 --- /dev/null +++ b/test_p2p_edge_cases.nyash @@ -0,0 +1,86 @@ +// 🧪 P2PBox エッジケーステスト +// エラーハンドリングと異常系の動作検証 + +print("=== P2PBox Edge Cases Test ===") + +// 基本セットアップ +local world +world = new IntentBox() + +local node1 +node1 = new P2PBox("node1", world) + +print("\n1. Testing invalid target send") +// 存在しないノードへの送信 +local result +result = node1.send("test", "data", "non_existent_node") +print("Send to non-existent node: " + result) + +print("\n2. Testing empty intent string") +// 空のintent文字列 +node1.on("", "empty_handler") +result = node1.send("", "test data", "node1") +print("Empty intent handled: " + result) + +print("\n3. Testing duplicate listener removal") +// 同じintentを2回削除 +node1.on("test_event", "handler1") +result = node1.off("test_event") +print("First removal: " + result) +result = node1.off("test_event") +print("Second removal: " + result) + +print("\n4. Testing self-messaging") +// 自分自身へのメッセージ送信 +node1.on("self_msg", "self_handler") +result = node1.send("self_msg", "Hello myself!", "node1") +print("Self message sent: " + result) + +print("\n5. Testing very long intent names") +// 非常に長いintent名 +local longIntent +longIntent = "this_is_a_very_long_intent_name_that_tests_the_system_limits_1234567890_abcdefghijklmnopqrstuvwxyz" +node1.on(longIntent, "long_handler") +result = node1.send(longIntent, "test", "node1") +print("Long intent handled: " + result) + +print("\n6. Testing special characters in node ID") +// 特殊文字を含むノードID(新規作成時) +local specialNode +specialNode = new P2PBox("node-with-special_chars.123", world) +print("Special node created: " + specialNode.getNodeId()) + +print("\n7. Testing rapid fire messages") +// 高速連続メッセージ送信 +local node2 +node2 = new P2PBox("node2", world) +node2.on("rapid", "rapid_handler") + +local i +i = 0 +loop(i < 10) { + node1.send("rapid", "Message " + i, "node2") + i = i + 1 +} +print("Sent 10 rapid messages") + +print("\n8. Testing null data send") +// nullデータの送信 +local nullData +nullData = new NullBox() +result = node1.send("null_test", nullData, "node2") +print("Null data sent: " + result) + +print("\n9. Testing listener with same intent multiple times") +// 同じintentに複数のリスナー登録 +node1.on("multi", "handler1") +node1.on("multi", "handler2") +node1.on("multi", "handler3") +print("Multiple handlers registered for same intent") + +print("\n10. Testing IntentBox message processing") +// IntentBoxの内部メッセージ処理 +result = world.processMessages() +print("IntentBox processed messages: " + result) + +print("\n=== Edge Cases Test Complete ===") \ No newline at end of file diff --git a/test_p2p_message_types.nyash b/test_p2p_message_types.nyash new file mode 100644 index 00000000..fd03eab9 --- /dev/null +++ b/test_p2p_message_types.nyash @@ -0,0 +1,66 @@ +// 🧪 P2PBox メッセージ型テスト +// 様々なデータ型のメッセージ送受信を検証 + +print("=== P2PBox Message Types Test ===") + +// 通信世界とノードを作成 +local world +world = new IntentBox() + +local sender +local receiver +sender = new P2PBox("sender", world) +receiver = new P2PBox("receiver", world) + +// 各種データ型のリスナーを登録 +receiver.on("string_msg", "handle_string") +receiver.on("integer_msg", "handle_integer") +receiver.on("bool_msg", "handle_bool") +receiver.on("null_msg", "handle_null") +receiver.on("array_msg", "handle_array") +receiver.on("map_msg", "handle_map") + +print("\n1. String message test") +sender.send("string_msg", "Hello, P2P World!", "receiver") + +print("\n2. Integer message test") +sender.send("integer_msg", 42, "receiver") + +print("\n3. Boolean message test") +sender.send("bool_msg", true, "receiver") + +print("\n4. Null message test") +local nullValue +nullValue = new NullBox() +sender.send("null_msg", nullValue, "receiver") + +print("\n5. Array message test (NOT IMPLEMENTED YET)") +// TODO: ArrayBox実装後に有効化 +// local arr +// arr = new ArrayBox() +// arr.push("item1") +// arr.push("item2") +// sender.send("array_msg", arr, "receiver") + +print("\n6. Map message test") +local data +data = new MapBox() +data.set("name", "Alice") +data.set("age", 25) +data.set("active", true) +sender.send("map_msg", data, "receiver") + +print("\n7. Complex nested data test") +local complex +complex = new MapBox() +complex.set("type", "user_profile") + +local details +details = new MapBox() +details.set("username", "alice123") +details.set("email", "alice@example.com") +complex.set("details", details) + +sender.broadcast("complex_data", complex) + +print("\n=== Message Types Test Complete ===") \ No newline at end of file