//! WebSocket gateway example. //! //! Starts a native node with some demo actors and a WebSocket gateway. //! Browser clients connect via WebSocket to send/receive messages. //! //! ```bash //! cargo run --example ws_gateway --features transport //! ``` //! //! Then open `crates/wasm/www/index.html` in a browser. use std::sync::Arc; use std::time::Duration; use swactor::actor::{ActorAddress, ActorInterface}; use swactor::runtime::{Ctx, Runtime, RuntimeConfig}; use swactor::transport::{ Codec, CodecRegistry, NetworkMessage, TransportRouter, }; use swactor::Error; use swactor_gateway::WsGateway; // ─── Messages ──────────────────────────────────────────────────────────────── /// A simple echo request: the payload + who to reply to. #[derive(Clone, Debug)] struct EchoRequest { payload: String, reply_to: ActorAddress, } impl NetworkMessage for EchoRequest { fn type_tag() -> &'static str { "example::EchoRequest" } } /// Echo response — just the echoed payload. #[derive(Clone, Debug)] struct EchoResponse { payload: String, } impl NetworkMessage for EchoResponse { fn type_tag() -> &'static str { "example::EchoResponse" } } // ─── JSON codec for the demo messages ──────────────────────────────────────── struct JsonCodec; impl Codec for JsonCodec { fn encode(&self, msg: &EchoRequest) -> Result, Error> { let mut buf = Vec::new(); buf.extend_from_slice(&msg.reply_to.0); buf.extend_from_slice(msg.payload.as_bytes()); Ok(buf) } fn decode(&self, bytes: &[u8]) -> Result { if bytes.len() < 32 { return Err(Error::from("EchoRequest too short")); } let mut addr = [0u8; 32]; addr.copy_from_slice(&bytes[..32]); let payload = String::from_utf8_lossy(&bytes[32..]).to_string(); Ok(EchoRequest { payload, reply_to: ActorAddress(addr), }) } } impl Codec for JsonCodec { fn encode(&self, msg: &EchoResponse) -> Result, Error> { Ok(msg.payload.as_bytes().to_vec()) } fn decode(&self, bytes: &[u8]) -> Result { Ok(EchoResponse { payload: String::from_utf8_lossy(bytes).to_string(), }) } } // Also register the JsMessage type that the browser uses #[derive(Clone, Debug)] struct JsMessage { type_tag: String, payload: String, } impl NetworkMessage for JsMessage { fn type_tag() -> &'static str { "swactor::JsMessage" } } struct JsMessageCodec; impl Codec for JsMessageCodec { fn encode(&self, msg: &JsMessage) -> Result, Error> { Ok(msg.payload.as_bytes().to_vec()) } fn decode(&self, bytes: &[u8]) -> Result { Ok(JsMessage { type_tag: "js".to_string(), payload: String::from_utf8_lossy(bytes).to_string(), }) } } // ─── Echo actor ────────────────────────────────────────────────────────────── struct EchoActor; impl ActorInterface for EchoActor { type Incoming = JsMessage; type Response = (); fn handle(&mut self, ctx: &Ctx, msg: JsMessage) { println!("[echo] Received: type_tag={}, payload={}", msg.type_tag, msg.payload); // Echo back to sender — in a real app, the payload would contain // the reply_to address. For the demo, we just log it. let _ = msg; let _ = ctx; } } // ─── Main ──────────────────────────────────────────────────────────────────── const WS_ADDR: &str = "127.0.0.1:9000"; fn main() { println!("[gateway] Starting WebSocket gateway on ws://{WS_ADDR}"); println!("[gateway] Open crates/wasm/www/index.html in a browser to connect.\n"); // Build codec registry let mut codecs = CodecRegistry::new(); codecs.register::(JsonCodec); codecs.register::(JsonCodec); codecs.register::(JsMessageCodec); let codecs = Arc::new(codecs); // Build runtime with an echo actor let transport_router = Arc::new(TransportRouter::new()); let mut rt = Runtime::new(RuntimeConfig { num_threads: 1, ..RuntimeConfig::default() }); rt.set_codec_registry(codecs.clone()); rt.set_transport_router(transport_router.clone()); let echo_addr = rt.spawn(EchoActor).unwrap(); rt.tick(); // drain spawn queue println!("[gateway] EchoActor spawned at {echo_addr}"); // Start WebSocket gateway let mut gateway = WsGateway::bind(WS_ADDR.parse().unwrap()) .expect("failed to bind WebSocket gateway"); println!("[gateway] Listening on ws://{}", gateway.local_addr()); println!(); // Event loop loop { let n = gateway.tick(&rt, &codecs, &transport_router); if n > 0 { println!("[gateway] Processed {n} envelope(s)"); } rt.tick(); std::thread::sleep(Duration::from_millis(16)); // ~60 ticks/sec } }