aetheris_protocol/traits.rs
1//! Core trait contracts for the Aetheris Engine.
2//!
3//! These traits form the boundary between the engine's protocol logic and
4//! external dependencies (ECS, Transport, Serialization).
5
6use async_trait::async_trait;
7
8pub use crate::error::{EncodeError, TransportError, WorldError};
9use crate::events::{ComponentUpdate, NetworkEvent, ReplicationEvent};
10pub use crate::types::{ClientId, LocalId, NetworkId, NetworkIdAllocator};
11
12/// Abstracts the underlying network transport.
13///
14/// # Why this exists
15/// In Phase 1, this wraps `renet`. In Phase 3, this wraps `quinn` directly.
16/// The game loop never knows which library is underneath.
17///
18/// # Reliability semantics
19/// - `send_unreliable`: Fire-and-forget. Used for position updates that are
20/// invalidated by the next tick. If the packet is lost, the client simply
21/// interpolates from the last known position.
22/// - `send_reliable`: Ordered and guaranteed delivery. Used for discrete game
23/// events (damage, death, loot) where loss would desync the client.
24#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
25#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
26pub trait PlatformTransport: Sync + PlatformTransportBounds {
27 /// Sends an unreliable datagram to a specific client.
28 ///
29 /// Returns immediately. The transport layer may silently drop this packet
30 /// under congestion — this is by design for volatile data.
31 ///
32 /// # Errors
33 /// Returns `TransportError::ClientNotConnected` if the `client_id` is unknown,
34 /// or `TransportError::PayloadTooLarge` if the packet exceeds MTU.
35 async fn send_unreliable(&self, client_id: ClientId, data: &[u8])
36 -> Result<(), TransportError>;
37
38 /// Sends a reliable, ordered message to a specific client.
39 ///
40 /// The transport guarantees delivery and ordering within a single stream.
41 /// Callers must not assume delivery timing — only eventual delivery.
42 ///
43 /// # Errors
44 /// Returns `TransportError::ClientNotConnected` if the `client_id` is unknown,
45 /// or `TransportError::Io` on underlying transport failure.
46 async fn send_reliable(&self, client_id: ClientId, data: &[u8]) -> Result<(), TransportError>;
47
48 /// Broadcasts an unreliable datagram to all connected clients.
49 ///
50 /// Useful for world-wide events (weather changes, global announcements)
51 /// where individual targeting is unnecessary.
52 ///
53 /// # Errors
54 /// Returns `TransportError::PayloadTooLarge` if the packet exceeds MTU.
55 async fn broadcast_unreliable(&self, data: &[u8]) -> Result<(), TransportError>;
56
57 /// Drains all pending inbound network events since the last call.
58 ///
59 /// This is called exactly once per tick at the top of the game loop.
60 /// Events include: client connections, disconnections, and inbound data.
61 ///
62 /// # Errors
63 /// Returns `TransportError::Io` on underlying transport failure or internal
64 /// state corruption (e.g. mutex poisoning).
65 async fn poll_events(&mut self) -> Result<Vec<NetworkEvent>, TransportError>;
66
67 /// Forcibly disconnects a client.
68 ///
69 /// This immediately terminates the underlying session.
70 ///
71 /// # Errors
72 /// Returns `TransportError::ClientNotConnected` if the `client_id` is unknown,
73 /// or `TransportError::Io` on underlying transport failure.
74 async fn disconnect(&self, client_id: ClientId) -> Result<(), TransportError>;
75
76 /// Returns the number of currently connected clients.
77 async fn connected_client_count(&self) -> usize;
78}
79
80/// Helper trait to provide conditional `Send` bounds for [`PlatformTransport`].
81#[cfg(target_arch = "wasm32")]
82pub trait PlatformTransportBounds {}
83#[cfg(target_arch = "wasm32")]
84impl<T: ?Sized> PlatformTransportBounds for T {}
85
86#[cfg(not(target_arch = "wasm32"))]
87pub trait PlatformTransportBounds: Send {}
88#[cfg(not(target_arch = "wasm32"))]
89impl<T: ?Sized + Send> PlatformTransportBounds for T {}
90
91/// The ECS Facade. Translates between the engine's protocol-level types
92/// and the concrete ECS's internal representation.
93///
94/// # Why this exists
95/// Bevy uses `Entity`, an opaque 64-bit handle with generation bits.
96/// Our network protocol uses `NetworkId`, a globally unique `u64`.
97/// This trait is the translation layer. The game loop never touches
98/// a Bevy `Entity` directly — it only speaks `NetworkId`.
99///
100/// # Delta extraction
101/// On every tick, modified components are detected and emitted as
102/// `ReplicationEvent` items. Only changed fields are sent — never the full
103/// component. This is the foundation of delta compression.
104pub trait WorldState: Send {
105 /// Maps a protocol-level `NetworkId` to the ECS's local entity handle.
106 ///
107 /// Returns `None` if the entity has been despawned or never existed.
108 fn get_local_id(&self, network_id: NetworkId) -> Option<LocalId>;
109
110 /// Maps a local ECS entity handle back to its protocol-level `NetworkId`.
111 ///
112 /// Returns `None` if the entity is not network-replicated.
113 fn get_network_id(&self, local_id: LocalId) -> Option<NetworkId>;
114
115 /// Extracts replication deltas for all components modified since the last tick.
116 ///
117 /// The returned events contain only the *changed* fields, not full snapshots.
118 /// The caller (the game loop) never interprets these events — it passes them
119 /// directly to the `Encoder` for serialization.
120 fn extract_deltas(&mut self) -> Vec<ReplicationEvent>;
121
122 /// Extracts discrete game events that should be sent reliably.
123 ///
124 /// Returns a list of `(Target ClientId, WireEvent)`.
125 /// If `ClientId` is `None`, the event should be broadcast to all authenticated clients.
126 fn extract_reliable_events(&mut self) -> Vec<(Option<ClientId>, crate::events::WireEvent)> {
127 Vec::new()
128 }
129
130 /// Injects parsed state updates from the network into the ECS.
131 ///
132 /// On the server, these are client inputs (movement commands, actions).
133 /// On the client, these are authoritative state corrections from the server.
134 ///
135 /// The `ClientId` in the update pair provides context for ownership verification
136 /// to prevent unauthorized updates from malicious clients.
137 fn apply_updates(&mut self, updates: &[(ClientId, ComponentUpdate)]);
138
139 /// Advances the world change tick at the start of each server tick, before inputs are applied.
140 fn advance_tick(&mut self) {}
141
142 /// Runs a single simulation frame for the ECS.
143 fn simulate(&mut self) {}
144
145 /// Called once per tick after `extract_deltas`, before the next `advance_tick`.
146 ///
147 /// Implementations should use this to clear ECS change-detection trackers so that
148 /// only mutations from the *next* simulation step appear as changed in the following
149 /// extraction. The default no-op is safe for non-Bevy adapters.
150 fn post_extract(&mut self) {}
151
152 /// Spawns a new network-replicated entity and returns its `NetworkId`.
153 fn spawn_networked(&mut self) -> NetworkId;
154
155 /// Spawns a new network-replicated entity owned by a specific client.
156 fn spawn_networked_for(&mut self, _client_id: ClientId) -> NetworkId {
157 self.spawn_networked()
158 }
159
160 /// Despawn a network-replicated entity by its `NetworkId`.
161 ///
162 /// # Errors
163 ///
164 /// Returns [`WorldError`] if the entity with the given `network_id` does not exist.
165 fn despawn_networked(&mut self, network_id: NetworkId) -> Result<(), WorldError>;
166
167 /// Triggers a bulk spawn of entities for stress testing.
168 fn stress_test(&mut self, _count: u16, _rotate: bool) {}
169
170 /// Spawns a new network-replicated entity of a specific kind.
171 fn spawn_kind(&mut self, _kind: u16, _x: f32, _y: f32, _rot: f32) -> NetworkId {
172 self.spawn_networked() // Fallback to basic networked spawn
173 }
174
175 /// Spawns a new network-replicated entity of a specific kind for a specific client.
176 fn spawn_kind_for(
177 &mut self,
178 kind: u16,
179 x: f32,
180 y: f32,
181 rot: f32,
182 _client_id: ClientId,
183 ) -> NetworkId {
184 self.spawn_kind(kind, x, y, rot)
185 }
186
187 /// Spawns the authoritative session agent for a client and marks it as
188 /// the possession target.
189 ///
190 /// The default implementation delegates to `spawn_kind_for`. Adapters
191 /// that support a `SessionAgent` marker component should override this to
192 /// attach that marker so the input pipeline can gate `InputCommand`
193 /// processing exclusively to the session agent.
194 fn spawn_session_agent(
195 &mut self,
196 kind: u16,
197 x: f32,
198 y: f32,
199 rot: f32,
200 client_id: ClientId,
201 ) -> NetworkId {
202 self.spawn_kind_for(kind, x, y, rot, client_id)
203 }
204
205 /// Despawns all entities from the world.
206 fn clear_world(&mut self) {}
207
208 /// Queues a reliable platform event to be sent to a specific client (or all if None).
209 fn queue_reliable_event(
210 &mut self,
211 _client_id: Option<ClientId>,
212 _event: crate::events::PlatformEvent,
213 ) {
214 }
215
216 /// Sets up the initial world state (e.g. Master Workspace).
217 fn setup_world(&mut self) {}
218
219 /// Returns the Workspace `network_id` for a given entity, if any.
220 fn get_entity_workspace(&self, _network_id: NetworkId) -> Option<NetworkId> {
221 None
222 }
223
224 /// Returns the Workspace `network_id` for a client's session agent, if any.
225 fn get_client_workspace(&self, _client_id: ClientId) -> Option<NetworkId> {
226 None
227 }
228
229 /// Returns a deterministic hash of the entire world state.
230 ///
231 /// Used for regression testing and golden file validation in VS-07 §3.3.
232 /// Must include all replicated component state and entity existence.
233 fn state_hash(&self) -> u64;
234}
235
236/// Defines the serialization strategy for network payloads.
237///
238/// # Why this exists
239/// In Phase 1, this wraps `serde` + `rmp-serde` for rapid iteration.
240/// In Phase 3, this becomes a custom bit-packer that writes individual
241/// bits across 32-bit word boundaries for maximum compression.
242///
243/// # Performance contract
244/// Phase 1 (current) implementations may allocate during serialization
245/// to simplify development. However, avoiding allocations is a primary
246/// Phase 3 goal for the custom bit-packer.
247///
248/// In Phase 3, implementations MUST be allocation-free on the hot path.
249/// The `encode` method writes into a caller-provided buffer.
250/// The `decode` method reads from a borrowed slice.
251/// No `Vec`, no `String`, no heap allocation during steady-state operation.
252pub trait Encoder: Send + Sync {
253 /// Returns the codec ID used by this encoder.
254 ///
255 /// P1: `SerdeEncoder` = 1 (rmp-serde).
256 /// P3: `BitpackEncoder` = 2.
257 fn codec_id(&self) -> u32;
258
259 /// Serializes a replication event into the provided buffer.
260 ///
261 /// Returns the number of bytes written. If the buffer is too small,
262 /// returns `EncodeError::BufferOverflow` — the caller must retry
263 /// with a larger buffer or fragment the event.
264 ///
265 /// # Errors
266 /// Returns `EncodeError::BufferOverflow` if the buffer is too small.
267 fn encode(&self, event: &ReplicationEvent, buffer: &mut [u8]) -> Result<usize, EncodeError>;
268
269 /// Deserializes raw bytes into a component update.
270 ///
271 /// Returns `EncodeError::MalformedPayload` if the bytes do not
272 /// constitute a valid event. The caller must handle this gracefully
273 /// (log + discard) — malformed packets are expected from lossy networks.
274 ///
275 /// # Errors
276 /// Returns `EncodeError::MalformedPayload` on invalid payload bytes, or
277 /// `EncodeError::UnknownComponent` for unregistered component types.
278 fn decode(&self, buffer: &[u8]) -> Result<ComponentUpdate, EncodeError>;
279
280 /// Encodes a high-level `NetworkEvent` into a byte vector.
281 ///
282 /// # Errors
283 /// Returns `EncodeError::Io` if serialization fails.
284 fn encode_event(&self, event: &NetworkEvent) -> Result<Vec<u8>, EncodeError>;
285
286 /// Encodes a high-level `NetworkEvent` into the provided buffer.
287 ///
288 /// Returns the number of bytes written.
289 ///
290 /// # Errors
291 /// Returns `EncodeError::BufferOverflow` if the buffer is too small,
292 /// or `EncodeError::Io` if serialization fails.
293 fn encode_event_into(
294 &self,
295 event: &NetworkEvent,
296 buffer: &mut [u8],
297 ) -> Result<usize, EncodeError>;
298
299 /// Decodes a high-level `NetworkEvent` from a byte slice.
300 ///
301 /// # Errors
302 /// Returns `EncodeError::MalformedPayload` if the bytes are not a valid event.
303 fn decode_event(&self, data: &[u8]) -> Result<NetworkEvent, EncodeError>;
304
305 /// Returns the maximum possible encoded size for a single event.
306 ///
307 /// Used by the transport layer to pre-allocate datagram buffers.
308 /// Implementations should return a tight upper bound, not a wild guess.
309 fn max_encoded_size(&self) -> usize;
310}