Skip to main content

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}