use bincode::{Decode, Encode}; #[cfg(any(feature = "client", feature = "server"))] const BINCODE_CFG: bincode::config::Configuration = bincode::config::standard(); #[derive(Debug, Clone, Copy, Encode, Decode)] pub enum WsMessage { Laser(LaserMessage), Mouse(MouseMessage), } #[derive(Debug, Clone, Copy, Encode, Decode)] pub struct LaserMessage { pub x: u32, pub y: u32, pub id: u64, pub line_id: u8, } #[derive(Debug, Clone, Copy, Encode, Decode)] pub struct MouseMessage { pub x: u32, pub y: u32, } #[cfg(feature = "client")] pub mod client { use futures_util::{SinkExt, StreamExt}; use tokio::sync::mpsc; use tokio_tungstenite_wasm::Message; use crate::{ AppResult, WindowHandle, ws::{BINCODE_CFG, WsMessage}, }; pub async fn connect( server_url: &str, window: WindowHandle, mut overlay_rx: mpsc::Receiver, overlay_tx: mpsc::Sender, id: u64, ) -> AppResult<()> { let (mut tx, mut rx) = tokio_tungstenite_wasm::connect(server_url).await?.split(); let send_task = async move { while let Some(msg) = overlay_rx.recv().await { let Ok(encoded) = bincode::encode_to_vec(&msg, BINCODE_CFG) else { continue; }; let _ = tx.send(Message::Binary(encoded.into())).await; } }; let receive_task = async move { while let Some(ev) = rx.next().await { match ev { Ok(Message::Binary(payload)) => { let Ok((decoded, _)): Result<(WsMessage, _), _> = bincode::decode_from_slice(&payload, BINCODE_CFG) else { continue; }; let _ = overlay_tx.send(decoded).await; if let Some(window) = window.get() { window.request_redraw(); } } Ok(Message::Close(_)) => { eprintln!("server closed connection"); if let Some(window) = window.get() { window.set_title("offline"); } break; } Err(err) => { eprintln!("error receiving message: {}", err); if let Some(window) = window.get() { window.set_title(&format!("error: {}", err)); } } _ => {} } } }; let _ = futures_util::future::join(send_task, receive_task).await; Ok(()) } } #[cfg(feature = "server")] pub mod server { use std::net::SocketAddr; use futures_util::{SinkExt, StreamExt}; use tokio::{ net::{TcpListener, TcpStream}, sync::{broadcast, mpsc}, }; use tokio_websockets::Message; use crate::{ AppResult, WindowHandle, ws::{BINCODE_CFG, WsMessage}, }; pub async fn listen( port: u16, window: WindowHandle, overlay_tx: mpsc::Sender, ) -> AppResult<(impl Future, broadcast::Sender<(u64, WsMessage)>)> { let addr = SocketAddr::from(([0, 0, 0, 0], port)); let listener = TcpListener::bind(&addr).await?; println!("listening on {}", addr); let (tx, mut rx) = broadcast::channel(1024); let server_task = tokio::spawn({ let tx = tx.clone(); async move { loop { let conn = match listener.accept().await { Ok((conn, addr)) => { println!("accepted connection from {}", addr); conn } Err(err) => { eprintln!("error accepting connection: {}", err); continue; } }; tokio::spawn(handle_server_conn(conn, tx.clone(), tx.subscribe())); } } }); let overlay_task = tokio::spawn(async move { while let Ok((_, msg)) = rx.recv().await { let _ = overlay_tx.send(msg).await; if let Some(window) = window.get() { window.request_redraw(); } } }); Ok((futures_util::future::join(server_task, overlay_task), tx)) } async fn handle_server_conn( conn: TcpStream, msg_tx: broadcast::Sender<(u64, WsMessage)>, mut msg_rx: broadcast::Receiver<(u64, WsMessage)>, ) -> impl Future { let (_, server) = tokio_websockets::ServerBuilder::new() .accept(conn) .await .unwrap(); let (mut tx, mut rx) = server.split(); let id = fastrand::u64(..); let send_task = tokio::spawn(async move { while let Some(msg) = rx.next().await { match msg { Ok(msg) => { let payload = msg.into_payload(); let Ok((decoded, _)) = bincode::decode_from_slice(&payload, BINCODE_CFG) else { continue; }; let _ = msg_tx.send((id, decoded)); } Err(err) => { eprintln!("error receiving message: {}", err); } } } }); let receive_task = tokio::spawn(async move { while let Ok((msg_id, msg)) = msg_rx.recv().await { // skip if message is from self if msg_id == id { continue; } let Ok(encoded) = bincode::encode_to_vec(&msg, BINCODE_CFG) else { continue; }; let _ = tx.send(Message::binary(encoded)).await; } }); futures_util::future::join(send_task, receive_task) } }