Skip to main content

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}