1use bytes::Bytes;
2use std::time::Duration;
3use uuid::Uuid;
4use wincode::{SchemaRead, SchemaWrite};
5
6#[derive(SchemaWrite, SchemaRead)]
8pub enum ClientWire {
9 JoinRoom { room_id: String, data: Vec<u8> },
10 LeaveRoom,
11 Ping,
12 Pong,
13 Broadcast { data: Vec<u8> },
14 EchoTest { data: Vec<u8> },
15}
16
17#[derive(SchemaWrite, SchemaRead)]
19pub enum ServerWire {
20 Joined { client_id: Uuid, room_id: String },
21 PlayerJoined { client_id: Uuid },
22 PlayerLeft { client_id: Uuid },
23 Ping,
24 Pong,
25 Error(String),
26 Broadcast { sender_id: Uuid, data: Vec<u8> },
27 EchoTest { data: Vec<u8> },
28}
29
30#[allow(rustdoc::private_intra_doc_links)]
31#[derive(Debug)]
34pub enum ServerEvent {
35 Joined { client_id: Uuid, room_id: String },
37 PlayerJoined { client_id: Uuid },
39 PlayerLeft { client_id: Uuid },
41 Error(String),
43 Broadcast { sender_id: Uuid, data: Bytes },
45 EchoTest { data: Bytes },
47}
48
49#[derive(Clone)]
51pub struct ServerConfig {
52 pub bind_addr: String,
53 pub max_clients: usize,
54 pub max_payload: usize,
55 pub idle_timeout: Duration,
64 pub ping_interval: Duration,
68 pub channel_capacity: usize,
72}
73
74impl Default for ServerConfig {
75 fn default() -> Self {
76 Self {
77 bind_addr: "0.0.0.0:7777".into(),
78 max_clients: 1024,
79 max_payload: 64 * 1024,
80 idle_timeout: Duration::from_secs(31),
82 ping_interval: Duration::from_secs(13),
83 channel_capacity: 1024,
84 }
85 }
86}
87
88pub enum SyncError {
90 PayloadTooLarge { size: usize, max: usize },
91 IdleTimeout,
92 PingTimeout,
93 RoomNotFound,
94 RoomAlreadyExists(String),
95 NotInRoom,
96 MaxClientsReached,
97 ConnectionClosed,
98 ConnectionRefused,
99 Protocol(String),
100 Io(std::io::Error),
101}
102
103impl SyncError {
104 pub fn is_connection_closed(&self) -> bool {
107 match self {
108 Self::ConnectionClosed => true,
109 Self::Io(e) => is_graceful_io_error(e),
110 _ => false,
111 }
112 }
113}
114
115pub(crate) fn is_graceful_io_error(e: &std::io::Error) -> bool {
124 match e.kind() {
125 std::io::ErrorKind::UnexpectedEof
126 | std::io::ErrorKind::ConnectionReset
127 | std::io::ErrorKind::ConnectionAborted
128 | std::io::ErrorKind::BrokenPipe => true,
129 _ => {
130 matches!(
132 e.raw_os_error(),
133 Some(
134 10054 | 10053 | 10058 )
138 )
139 }
140 }
141}
142
143impl std::fmt::Debug for SyncError {
144 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
145 std::fmt::Display::fmt(self, f)
146 }
147}
148
149impl std::fmt::Display for SyncError {
150 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
151 match self {
152 Self::PayloadTooLarge { size, max } => {
153 write!(f, "payload too large: {} bytes (max {})", size, max)
154 }
155 Self::IdleTimeout => write!(f, "client idle timeout"),
156 Self::PingTimeout => write!(f, "no pong received before next ping tick"),
157 Self::RoomNotFound => write!(f, "room not found"),
158 Self::RoomAlreadyExists(id) => write!(f, "room already exists: {id}"),
159 Self::NotInRoom => write!(f, "client not in a room"),
160 Self::MaxClientsReached => write!(f, "max clients reached"),
161 Self::ConnectionClosed => write!(f, "connection closed"),
162 Self::ConnectionRefused => write!(f, "connection refused — is the server running?"),
163 Self::Protocol(msg) => write!(f, "protocol error: {}", msg),
164 Self::Io(e) => write!(f, "io error: {}", e),
165 }
166 }
167}
168
169impl std::error::Error for SyncError {}
170
171impl From<std::io::Error> for SyncError {
172 fn from(e: std::io::Error) -> Self {
173 Self::Io(e)
174 }
175}
176
177pub type Result<T> = std::result::Result<T, SyncError>;