aetheris_protocol/events.rs
1use serde::{Deserialize, Serialize};
2
3use crate::types::{ClientId, ComponentKind, NetworkId};
4
5/// An event representing a fragment of a larger message.
6/// Used for MTU stability to prevent packet drops and enable reassembly.
7#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
8pub struct FragmentedEvent {
9 /// Unique identifier for the fragmented message.
10 pub message_id: u32,
11 /// The index of this fragment (0-based).
12 pub fragment_index: u16,
13 /// Total number of fragments for this message.
14 pub total_fragments: u16,
15 /// The raw payload of this fragment.
16 #[serde(with = "serde_bytes")]
17 pub payload: Vec<u8>,
18}
19
20/// An event representing a change to a single component on a single entity.
21/// Produced by `WorldState::extract_deltas()` on the server.
22#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
23pub struct ReplicationEvent {
24 /// Which entity changed.
25 pub network_id: NetworkId,
26 /// Which component type changed.
27 pub component_kind: ComponentKind,
28 /// The serialized delta payload (only the changed fields).
29 /// In Phase 1, this is a full snapshot per component for simplicity.
30 #[serde(with = "serde_bytes")]
31 pub payload: Vec<u8>,
32 /// The server tick at which this change was recorded.
33 pub tick: u64,
34}
35
36/// An inbound update to be applied to the ECS.
37/// Produced by `Encoder::decode()` on the receiving end.
38#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
39pub struct ComponentUpdate {
40 /// The entity to update.
41 pub network_id: NetworkId,
42 /// Which component type to update.
43 pub component_kind: ComponentKind,
44 /// The deserialized field values.
45 #[serde(with = "serde_bytes")]
46 pub payload: Vec<u8>,
47 /// The tick this update originated from.
48 pub tick: u64,
49}
50
51/// Events produced by `GameTransport::poll_events()`.
52#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
53pub enum NetworkEvent {
54 /// A new client has connected and been assigned a `ClientId`.
55 ClientConnected(ClientId),
56 /// A client has disconnected (graceful or timeout).
57 ClientDisconnected(ClientId),
58 /// Raw unreliable data received from a client.
59 UnreliableMessage {
60 /// The client that sent the message.
61 client_id: ClientId,
62 /// The raw message bytes.
63 #[serde(with = "serde_bytes")]
64 data: Vec<u8>,
65 },
66 /// Raw reliable data received from a client.
67 ReliableMessage {
68 /// The client that sent the message.
69 client_id: ClientId,
70 /// The raw message bytes.
71 #[serde(with = "serde_bytes")]
72 data: Vec<u8>,
73 },
74 /// A heartbeat ping from a client.
75 Ping {
76 /// The client that sent the ping.
77 client_id: ClientId,
78 /// The client's tick/timestamp when the ping was sent.
79 tick: u64,
80 },
81 /// A heartbeat pong from the server.
82 Pong {
83 /// The original tick/timestamp from the ping.
84 tick: u64,
85 },
86 /// A session authentication request from the client.
87 Auth {
88 /// The session token obtained from the Control Plane.
89 session_token: String,
90 },
91 /// A WebTransport session was closed by the remote or due to error.
92 SessionClosed(ClientId),
93 /// A WebTransport stream was reset.
94 StreamReset(ClientId),
95 /// A fragment of a larger message.
96 Fragment {
97 /// The client that sent the fragment.
98 client_id: ClientId,
99 /// The fragment data.
100 fragment: FragmentedEvent,
101 },
102 /// A testing command to trigger a stress test (Phase 1/Playground only).
103 StressTest {
104 /// The client that requested the stress test.
105 client_id: ClientId,
106 /// Number of entities to spawn.
107 count: u16,
108 /// Whether spawned entities should rotate.
109 rotate: bool,
110 },
111 /// A testing command to spawn a specific entity (Phase 1/Playground only).
112 Spawn {
113 /// The client that requested the spawn.
114 client_id: ClientId,
115 /// Which entity type to spawn.
116 entity_type: u16,
117 /// Position X
118 x: f32,
119 /// Position Y
120 y: f32,
121 /// Initial rotation
122 rot: f32,
123 },
124 /// A command to clear all entities from the world (Phase 1/Playground only).
125 ClearWorld {
126 /// The client that requested the clear.
127 client_id: ClientId,
128 },
129 /// A local event indicating the client transport has been disconnected.
130 Disconnected(ClientId),
131}
132
133impl NetworkEvent {
134 /// Returns true if this event is capable of being sent over the wire.
135 #[must_use]
136 pub const fn is_wire(&self) -> bool {
137 match self {
138 Self::Ping { .. }
139 | Self::Pong { .. }
140 | Self::Auth { .. }
141 | Self::Fragment { .. }
142 | Self::StressTest { .. }
143 | Self::Spawn { .. }
144 | Self::ClearWorld { .. } => true,
145 Self::ClientConnected(_)
146 | Self::ClientDisconnected(_)
147 | Self::UnreliableMessage { .. }
148 | Self::ReliableMessage { .. }
149 | Self::SessionClosed(_)
150 | Self::StreamReset(_)
151 | Self::Disconnected(_) => false,
152 }
153 }
154}
155
156/// A restricted view of `NetworkEvent` for over-the-wire transport.
157/// Prevents local-only variants (like `ClientConnected`) from being sent/received.
158#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
159pub enum WireEvent {
160 /// A heartbeat ping.
161 Ping {
162 /// The client's tick/timestamp when the ping was sent.
163 tick: u64,
164 },
165 /// A heartbeat pong.
166 Pong {
167 /// The original tick/timestamp from the ping.
168 tick: u64,
169 },
170 /// A session authentication request.
171 Auth {
172 /// The session token.
173 session_token: String,
174 },
175 /// A fragment of a larger message.
176 Fragment(FragmentedEvent),
177 /// A testing command to trigger a stress test.
178 StressTest {
179 /// Number of entities to spawn.
180 count: u16,
181 /// Whether spawned entities should rotate.
182 rotate: bool,
183 },
184 /// A testing command to spawn a specific entity.
185 Spawn {
186 /// Which entity type to spawn.
187 entity_type: u16,
188 /// Position X
189 x: f32,
190 /// Position Y
191 y: f32,
192 /// Initial rotation
193 rot: f32,
194 },
195 /// A command to clear all entities from the world.
196 ClearWorld,
197}
198
199#[cfg(test)]
200mod tests {
201 use super::*;
202
203 #[test]
204 fn test_event_sizes_and_derives() {
205 let ev = NetworkEvent::ClientConnected(ClientId(1));
206 assert_eq!(ev, NetworkEvent::ClientConnected(ClientId(1)));
207
208 let re = ReplicationEvent {
209 network_id: NetworkId(1),
210 component_kind: ComponentKind(0),
211 payload: vec![1, 2, 3],
212 tick: 0,
213 };
214 assert_eq!(re.payload.len(), 3);
215 assert_eq!(
216 re,
217 ReplicationEvent {
218 network_id: NetworkId(1),
219 component_kind: ComponentKind(0),
220 payload: vec![1, 2, 3],
221 tick: 0,
222 }
223 );
224 }
225}