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 GameTransport: Sync + GameTransportBounds {
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 async fn poll_events(&mut self) -> Vec<NetworkEvent>;
62
63 /// Returns the number of currently connected clients.
64 async fn connected_client_count(&self) -> usize;
65}
66
67/// Helper trait to provide conditional `Send` bounds for [`GameTransport`].
68#[cfg(target_arch = "wasm32")]
69pub trait GameTransportBounds {}
70#[cfg(target_arch = "wasm32")]
71impl<T: ?Sized> GameTransportBounds for T {}
72
73#[cfg(not(target_arch = "wasm32"))]
74pub trait GameTransportBounds: Send {}
75#[cfg(not(target_arch = "wasm32"))]
76impl<T: ?Sized + Send> GameTransportBounds for T {}
77
78/// The ECS Facade. Translates between the engine's protocol-level types
79/// and the concrete ECS's internal representation.
80///
81/// # Why this exists
82/// Bevy uses `Entity`, an opaque 64-bit handle with generation bits.
83/// Our network protocol uses `NetworkId`, a globally unique `u64`.
84/// This trait is the translation layer. The game loop never touches
85/// a Bevy `Entity` directly — it only speaks `NetworkId`.
86///
87/// # Delta extraction
88/// On every tick, modified components are detected and emitted as
89/// `ReplicationEvent` items. Only changed fields are sent — never the full
90/// component. This is the foundation of delta compression.
91pub trait WorldState: Send {
92 /// Maps a protocol-level `NetworkId` to the ECS's local entity handle.
93 ///
94 /// Returns `None` if the entity has been despawned or never existed.
95 fn get_local_id(&self, network_id: NetworkId) -> Option<LocalId>;
96
97 /// Maps a local ECS entity handle back to its protocol-level `NetworkId`.
98 ///
99 /// Returns `None` if the entity is not network-replicated.
100 fn get_network_id(&self, local_id: LocalId) -> Option<NetworkId>;
101
102 /// Extracts replication deltas for all components modified since the last tick.
103 ///
104 /// The returned events contain only the *changed* fields, not full snapshots.
105 /// The caller (the game loop) never interprets these events — it passes them
106 /// directly to the `Encoder` for serialization.
107 fn extract_deltas(&mut self) -> Vec<ReplicationEvent>;
108
109 /// Injects parsed state updates from the network into the ECS.
110 ///
111 /// On the server, these are client inputs (movement commands, actions).
112 /// On the client, these are authoritative state corrections from the server.
113 ///
114 /// The `ClientId` in the update pair provides context for ownership verification
115 /// to prevent unauthorized updates from malicious clients.
116 fn apply_updates(&mut self, updates: &[(ClientId, ComponentUpdate)]);
117
118 /// Advances the world change tick at the start of each server tick, before inputs are applied.
119 fn advance_tick(&mut self) {}
120
121 /// Runs a single simulation frame for the ECS.
122 fn simulate(&mut self) {}
123
124 /// Spawns a new network-replicated entity and returns its `NetworkId`.
125 fn spawn_networked(&mut self) -> NetworkId;
126
127 /// Spawns a new network-replicated entity owned by a specific client.
128 fn spawn_networked_for(&mut self, _client_id: ClientId) -> NetworkId {
129 self.spawn_networked()
130 }
131
132 /// Despawn a network-replicated entity by its `NetworkId`.
133 ///
134 /// # Errors
135 ///
136 /// Returns [`WorldError`] if the entity with the given `network_id` does not exist.
137 fn despawn_networked(&mut self, network_id: NetworkId) -> Result<(), WorldError>;
138
139 /// Triggers a bulk spawn of entities for stress testing.
140 fn stress_test(&mut self, _count: u16, _rotate: bool) {}
141
142 /// Spawns a new network-replicated entity of a specific kind.
143 fn spawn_kind(&mut self, _kind: u16, _x: f32, _y: f32, _rot: f32) -> NetworkId {
144 self.spawn_networked() // Fallback to basic networked spawn
145 }
146
147 /// Despawns all entities from the world.
148 fn clear_world(&mut self) {}
149}
150
151/// Defines the serialization strategy for network payloads.
152///
153/// # Why this exists
154/// In Phase 1, this wraps `serde` + `rmp-serde` for rapid iteration.
155/// In Phase 3, this becomes a custom bit-packer that writes individual
156/// bits across 32-bit word boundaries for maximum compression.
157///
158/// # Performance contract
159/// Phase 1 (current) implementations may allocate during serialization
160/// to simplify development. However, avoiding allocations is a primary
161/// Phase 3 goal for the custom bit-packer.
162///
163/// In Phase 3, implementations MUST be allocation-free on the hot path.
164/// The `encode` method writes into a caller-provided buffer.
165/// The `decode` method reads from a borrowed slice.
166/// No `Vec`, no `String`, no heap allocation during steady-state operation.
167pub trait Encoder: Send + Sync {
168 /// Serializes a replication event into the provided buffer.
169 ///
170 /// Returns the number of bytes written. If the buffer is too small,
171 /// returns `EncodeError::BufferOverflow` — the caller must retry
172 /// with a larger buffer or fragment the event.
173 ///
174 /// # Errors
175 /// Returns `EncodeError::BufferOverflow` if the buffer is too small.
176 fn encode(&self, event: &ReplicationEvent, buffer: &mut [u8]) -> Result<usize, EncodeError>;
177
178 /// Deserializes raw bytes into a component update.
179 ///
180 /// Returns `EncodeError::MalformedPayload` if the bytes do not
181 /// constitute a valid event. The caller must handle this gracefully
182 /// (log + discard) — malformed packets are expected from lossy networks.
183 ///
184 /// # Errors
185 /// Returns `EncodeError::MalformedPayload` on invalid payload bytes, or
186 /// `EncodeError::UnknownComponent` for unregistered component types.
187 fn decode(&self, buffer: &[u8]) -> Result<ComponentUpdate, EncodeError>;
188
189 /// Encodes a high-level `NetworkEvent` into a byte vector.
190 ///
191 /// # Errors
192 /// Returns `EncodeError::Io` if serialization fails.
193 fn encode_event(&self, event: &NetworkEvent) -> Result<Vec<u8>, EncodeError>;
194
195 /// Decodes a high-level `NetworkEvent` from a byte slice.
196 ///
197 /// # Errors
198 /// Returns `EncodeError::MalformedPayload` if the bytes are not a valid event.
199 fn decode_event(&self, data: &[u8]) -> Result<NetworkEvent, EncodeError>;
200
201 /// Returns the maximum possible encoded size for a single event.
202 ///
203 /// Used by the transport layer to pre-allocate datagram buffers.
204 /// Implementations should return a tight upper bound, not a wild guess.
205 fn max_encoded_size(&self) -> usize;
206}