Skip to main content

aetheris_client_wasm/
world_state.rs

1//! Client-side world state and interpolation buffer.
2//!
3//! This module implements the `WorldState` trait for the WASM client,
4//! providing the foundation for the two-tick interpolation buffer.
5
6use crate::shared_world::SabSlot;
7use aetheris_protocol::error::WorldError;
8use aetheris_protocol::events::{ComponentUpdate, ReplicationEvent};
9use aetheris_protocol::traits::WorldState;
10use aetheris_protocol::types::{
11    ClientId, ComponentKind, LocalId, NetworkId, ShipClass, ShipStats, Transform,
12};
13use std::collections::BTreeMap;
14
15/// A simplified client-side world that tracks entity states using `SabSlot`.
16#[derive(Debug)]
17pub struct ClientWorld {
18    /// Map of `NetworkId` to the last known authoritative state.
19    pub entities: BTreeMap<NetworkId, SabSlot>,
20    /// The latest tick received from the server.
21    pub latest_tick: u64,
22}
23
24impl Default for ClientWorld {
25    fn default() -> Self {
26        Self::new()
27    }
28}
29
30impl ClientWorld {
31    /// Creates a new, empty `ClientWorld`.
32    #[must_use]
33    pub fn new() -> Self {
34        Self {
35            entities: BTreeMap::new(),
36            latest_tick: 0,
37        }
38    }
39}
40
41impl WorldState for ClientWorld {
42    fn get_local_id(&self, network_id: NetworkId) -> Option<LocalId> {
43        Some(LocalId(network_id.0))
44    }
45
46    fn get_network_id(&self, local_id: LocalId) -> Option<NetworkId> {
47        Some(NetworkId(local_id.0))
48    }
49
50    fn extract_deltas(&mut self) -> Vec<ReplicationEvent> {
51        Vec::new()
52    }
53
54    fn apply_updates(&mut self, updates: &[(ClientId, ComponentUpdate)]) {
55        for (_, update) in updates {
56            if update.tick > self.latest_tick {
57                self.latest_tick = update.tick;
58            }
59
60            // Ensure entity exists for component updates
61            let is_new = !self.entities.contains_key(&update.network_id);
62            if is_new {
63                tracing::debug!(
64                    network_id = update.network_id.0,
65                    kind = update.component_kind.0,
66                    "New entity from server"
67                );
68            }
69            self.entities.entry(update.network_id).or_insert(SabSlot {
70                network_id: update.network_id.0,
71                x: 0.0,
72                y: 0.0,
73                z: 0.0,
74                rotation: 0.0,
75                dx: 0.0,
76                dy: 0.0,
77                dz: 0.0,
78                hp: 100,
79                shield: 0,
80                entity_type: 0,
81                flags: 1,
82                padding: [0; 5],
83            });
84
85            match update.component_kind {
86                // ComponentKind(1) == Transform (Spatial data)
87                ComponentKind(1) => match rmp_serde::from_slice::<Transform>(&update.payload) {
88                    Ok(transform) => {
89                        if let Some(entry) = self.entities.get_mut(&update.network_id) {
90                            entry.x = transform.x;
91                            entry.y = transform.y;
92                            entry.z = transform.z;
93                            entry.rotation = transform.rotation;
94                            if transform.entity_type != 0 {
95                                entry.entity_type = transform.entity_type;
96                            }
97                        }
98                    }
99                    Err(e) => {
100                        tracing::warn!(network_id = update.network_id.0, error = ?e, "Failed to decode Transform");
101                    }
102                },
103                // ComponentKind(5) == ShipClass (Drives rendering type)
104                ComponentKind(5) => match rmp_serde::from_slice::<ShipClass>(&update.payload) {
105                    Ok(ship_class) => {
106                        if let Some(entry) = self.entities.get_mut(&update.network_id) {
107                            entry.entity_type = match ship_class {
108                                ShipClass::Interceptor => 1,
109                                ShipClass::Dreadnought => 3,
110                                ShipClass::Hauler => 4,
111                            };
112                        }
113                    }
114                    Err(e) => {
115                        tracing::warn!(network_id = update.network_id.0, error = ?e, "Failed to decode ShipClass");
116                    }
117                },
118                // ComponentKind(6) == ShipStats (HP/Shield)
119                ComponentKind(6) => match rmp_serde::from_slice::<ShipStats>(&update.payload) {
120                    Ok(stats) => {
121                        if let Some(entry) = self.entities.get_mut(&update.network_id) {
122                            entry.hp = stats.hp;
123                            entry.shield = stats.shield;
124                        }
125                    }
126                    Err(e) => {
127                        tracing::warn!(network_id = update.network_id.0, error = ?e, "Failed to decode ShipStats");
128                    }
129                },
130                kind => {
131                    tracing::debug!(
132                        network_id = update.network_id.0,
133                        kind = kind.0,
134                        "Unhandled component kind"
135                    );
136                }
137            }
138        }
139    }
140
141    fn simulate(&mut self) {}
142
143    fn spawn_networked(&mut self) -> NetworkId {
144        NetworkId(0)
145    }
146
147    fn spawn_networked_for(&mut self, _client_id: ClientId) -> NetworkId {
148        self.spawn_networked()
149    }
150
151    fn despawn_networked(&mut self, network_id: NetworkId) -> Result<(), WorldError> {
152        self.entities
153            .remove(&network_id)
154            .map(|_| ())
155            .ok_or(WorldError::EntityNotFound(network_id))
156    }
157
158    fn stress_test(&mut self, _count: u16, _rotate: bool) {}
159
160    fn spawn_kind(&mut self, _kind: u16, _x: f32, _y: f32, _rot: f32) -> NetworkId {
161        NetworkId(1)
162    }
163
164    fn clear_world(&mut self) {
165        self.entities.clear();
166    }
167}