//! Two-process transport demo. //! //! Run in two terminals: //! //! ```bash //! # Terminal 1 — starts the receiver (has the actor) //! cargo run --example tcp_ping_pong --features transport -- receiver //! //! # Terminal 2 — sends pings across TCP //! cargo run --example tcp_ping_pong --features transport -- sender //! ``` use std::io::{Read, Write}; use std::net::{TcpListener, TcpStream}; use std::sync::{Arc, Mutex}; use swactor::{ actor::{ActorAddress, ActorInterface}, runtime::{Ctx, Runtime, RuntimeConfig}, transport::{ Codec, CodecRegistry, NetworkMessage, Transport, TransportRouter, WireEnvelope, }, Error, }; // ─── Messages ─────────────────────────────────────────────────────────────── #[derive(Clone, Debug)] struct Ping { value: u32, reply_to: ActorAddress, } impl NetworkMessage for Ping { fn type_tag() -> &'static str { "example::Ping" } } #[derive(Clone, Debug, PartialEq)] struct Pong { value: u32, } impl NetworkMessage for Pong { fn type_tag() -> &'static str { "example::Pong" } } // ─── Codec (hand-rolled, no serde needed) ─────────────────────────────────── struct ExampleCodec; impl Codec for ExampleCodec { fn encode(&self, msg: &Ping) -> Result, Error> { let mut buf = Vec::with_capacity(36); buf.extend_from_slice(&msg.value.to_be_bytes()); buf.extend_from_slice(&msg.reply_to.0); Ok(buf) } fn decode(&self, bytes: &[u8]) -> Result { if bytes.len() < 36 { return Err(Error::from("Ping: short read")); } let value = u32::from_be_bytes(bytes[0..4].try_into().unwrap()); let mut addr = [0u8; 32]; addr.copy_from_slice(&bytes[4..36]); Ok(Ping { value, reply_to: ActorAddress(addr), }) } } impl Codec for ExampleCodec { fn encode(&self, msg: &Pong) -> Result, Error> { Ok(msg.value.to_be_bytes().to_vec()) } fn decode(&self, bytes: &[u8]) -> Result { if bytes.len() < 4 { return Err(Error::from("Pong: short read")); } Ok(Pong { value: u32::from_be_bytes(bytes[0..4].try_into().unwrap()), }) } } // ─── TCP Transport ────────────────────────────────────────────────────────── /// Simple length-prefixed TCP transport. /// /// Wire format per envelope: /// [4 bytes: total frame len (BE u32)] /// [32 bytes: dest address] /// [4 bytes: type_tag len (BE u32)] /// [N bytes: type_tag UTF-8] /// [remaining: payload bytes] struct TcpTransport { stream: Mutex, } impl Transport for TcpTransport { fn send(&self, envelope: WireEnvelope) -> Result<(), Error> { let tag_bytes = envelope.type_tag.as_bytes(); let frame_len: u32 = (32 + 4 + tag_bytes.len() + envelope.payload.len()) as u32; let mut buf = Vec::with_capacity(4 + frame_len as usize); buf.extend_from_slice(&frame_len.to_be_bytes()); buf.extend_from_slice(&envelope.dest.0); buf.extend_from_slice(&(tag_bytes.len() as u32).to_be_bytes()); buf.extend_from_slice(tag_bytes); buf.extend_from_slice(&envelope.payload); self.stream .lock() .unwrap() .write_all(&buf) .map_err(|e| Error::from(format!("TCP send: {e}"))) } } /// Read one WireEnvelope from a TCP stream. fn read_envelope(stream: &mut TcpStream) -> std::io::Result { // Frame length let mut len_buf = [0u8; 4]; stream.read_exact(&mut len_buf)?; let frame_len = u32::from_be_bytes(len_buf) as usize; // Read entire frame let mut frame = vec![0u8; frame_len]; stream.read_exact(&mut frame)?; // Parse let mut dest = [0u8; 32]; dest.copy_from_slice(&frame[0..32]); let tag_len = u32::from_be_bytes(frame[32..36].try_into().unwrap()) as usize; let type_tag = String::from_utf8_lossy(&frame[36..36 + tag_len]).to_string(); let payload = frame[36 + tag_len..].to_vec(); Ok(WireEnvelope { dest: ActorAddress(dest), type_tag, payload, }) } // ─── Actor ────────────────────────────────────────────────────────────────── struct PongActor; impl ActorInterface for PongActor { type Incoming = Ping; type Response = Pong; fn handle(&mut self, ctx: &Ctx, msg: Ping) { println!( " PongActor received Ping({}), replying with Pong({})", msg.value, msg.value + 1 ); let _ = ctx.send(msg.reply_to, Pong { value: msg.value + 1 }); } } // ─── Codec registry (shared) ─────────────────────────────────────────────── fn build_codecs() -> CodecRegistry { let mut cr = CodecRegistry::new(); cr.register::(ExampleCodec); cr.register::(ExampleCodec); cr } // ─── Main ─────────────────────────────────────────────────────────────────── const ADDR: &str = "127.0.0.1:9100"; fn main() { let args: Vec = std::env::args().collect(); let role = args.get(1).map(|s| s.as_str()).unwrap_or("help"); match role { "receiver" => run_receiver(), "sender" => run_sender(), _ => { eprintln!("Usage: two_process "); eprintln!(); eprintln!(" Terminal 1: cargo run --example two_process --features transport -- receiver"); eprintln!(" Terminal 2: cargo run --example two_process --features transport -- sender"); std::process::exit(1); } } } /// Receiver process: hosts the PongActor, listens for incoming envelopes on TCP. fn run_receiver() { println!("[receiver] Starting on {ADDR}..."); let codecs = Arc::new(build_codecs()); let codecs_recv = codecs.clone(); // Build runtime with PongActor at the well-known address let mut rt = Runtime::new(RuntimeConfig::default()); // We need the actor at the agreed address. Since spawn() generates a random // address, we'll use a workaround: spawn normally, then register a route // for replies going back to the sender (those will be Pong messages). // But actually, the sender's inbox address is dynamic, so the receiver // needs a transport to send Pong back. // For this demo: the receiver accepts a TCP connection, and uses that same // connection (reversed) to send replies. let pong_addr = rt.spawn(PongActor).unwrap(); rt.tick(); // drain spawn queue println!("[receiver] PongActor spawned at {pong_addr}"); println!("[receiver] Listening for connections..."); let listener = TcpListener::bind(ADDR).expect("failed to bind"); // Accept one connection let (mut stream, peer) = listener.accept().expect("accept failed"); println!("[receiver] Connection from {peer}"); // Read the sender's inbox address (first 32 bytes) let mut inbox_bytes = [0u8; 32]; stream.read_exact(&mut inbox_bytes).unwrap(); let sender_inbox_addr = ActorAddress(inbox_bytes); println!("[receiver] Sender inbox: {sender_inbox_addr}"); // Send back the actual PongActor address (so sender can address messages) stream.write_all(&pong_addr.0).unwrap(); // Set up transport for replies back to sender let reply_transport = Arc::new(TcpTransport { stream: Mutex::new(stream.try_clone().unwrap()), }); let router = TransportRouter::new(); router.add_route(sender_inbox_addr, reply_transport); rt.set_codec_registry(codecs.clone()); rt.set_transport_router(Arc::new(router)); // Event loop: read envelopes from TCP, deliver, tick println!("[receiver] Ready — waiting for pings...\n"); loop { match read_envelope(&mut stream) { Ok(envelope) => { let (addr, msg) = codecs_recv.receive(envelope).unwrap(); rt.deliver_raw(addr, msg).unwrap(); rt.tick(); } Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => { println!("\n[receiver] Sender disconnected."); break; } Err(e) => { eprintln!("[receiver] Read error: {e}"); break; } } } } /// Sender process: connects to receiver, sends Pings, reads Pong replies. fn run_sender() { println!("[sender] Connecting to {ADDR}..."); let codecs = Arc::new(build_codecs()); let codecs_recv = codecs.clone(); let mut rt = Runtime::new(RuntimeConfig::default()); let inbox = rt.new_inbox::().unwrap(); let inbox_addr = *inbox.addr(); // Connect and exchange addresses let mut stream = TcpStream::connect(ADDR).expect("failed to connect — is the receiver running?"); // Send our inbox address stream.write_all(&inbox_addr.0).unwrap(); // Read the PongActor's address let mut pong_bytes = [0u8; 32]; stream.read_exact(&mut pong_bytes).unwrap(); let pong_addr = ActorAddress(pong_bytes); println!("[sender] Connected. PongActor is at {pong_addr}\n"); // Set up transport to send Pings to receiver let send_transport = Arc::new(TcpTransport { stream: Mutex::new(stream.try_clone().unwrap()), }); let router = TransportRouter::new(); router.add_route(pong_addr, send_transport); rt.set_codec_registry(codecs.clone()); rt.set_transport_router(Arc::new(router)); // Send 5 pings for i in 1..=5 { println!("[sender] Sending Ping({i})..."); rt.send_to( pong_addr, Ping { value: i, reply_to: inbox_addr, }, ) .unwrap(); // Read the reply from TCP match read_envelope(&mut stream) { Ok(envelope) => { let (addr, msg) = codecs_recv.receive(envelope).unwrap(); rt.deliver_raw(addr, msg).unwrap(); } Err(e) => { eprintln!("[sender] Read error: {e}"); break; } } // Check inbox if let Some(pong) = inbox.try_recv() { println!("[sender] Got Pong({})!", pong.value); } std::thread::sleep(std::time::Duration::from_millis(500)); } println!("\n[sender] Done."); }