1use std::collections::{BTreeMap, BTreeSet, HashMap};
7
8use codec::{DeltaUpdateEntity, EntitySnapshot};
9use schema::ComponentId;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
13pub struct ClientId(pub u32);
14
15#[derive(Debug, Clone, Copy, PartialEq)]
17pub struct Vec3 {
18 pub x: f32,
19 pub y: f32,
20 pub z: f32,
21}
22
23impl Vec3 {
24 #[must_use]
25 pub fn distance_sq(self, other: Self) -> f32 {
26 let dx = self.x - other.x;
27 let dy = self.y - other.y;
28 let dz = self.z - other.z;
29 dx * dx + dy * dy + dz * dz
30 }
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub struct ClientBudget {
36 pub max_creates: usize,
37 pub max_updates: usize,
38 pub max_destroys: usize,
39}
40
41impl ClientBudget {
42 #[must_use]
43 pub fn unlimited() -> Self {
44 Self {
45 max_creates: usize::MAX,
46 max_updates: usize::MAX,
47 max_destroys: usize::MAX,
48 }
49 }
50}
51
52#[derive(Debug, Clone, Copy, PartialEq)]
54pub struct ClientView {
55 pub position: Vec3,
56 pub radius: f32,
57 pub budget: ClientBudget,
58}
59
60impl ClientView {
61 #[must_use]
62 pub fn new(position: Vec3, radius: f32) -> Self {
63 Self {
64 position,
65 radius,
66 budget: ClientBudget::unlimited(),
67 }
68 }
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73pub struct ReplicationConfig {
74 pub max_entities: usize,
76}
77
78impl ReplicationConfig {
79 #[must_use]
80 pub fn default_limits() -> Self {
81 Self {
82 max_entities: 1_000_000,
83 }
84 }
85}
86
87#[derive(Debug, Clone, PartialEq, Eq)]
89pub struct ClientDelta {
90 pub creates: Vec<EntitySnapshot>,
91 pub destroys: Vec<codec::EntityId>,
92 pub updates: Vec<DeltaUpdateEntity>,
93}
94
95impl ClientDelta {
96 #[must_use]
97 pub fn is_empty(&self) -> bool {
98 self.creates.is_empty() && self.destroys.is_empty() && self.updates.is_empty()
99 }
100}
101
102pub trait WorldView {
104 fn snapshot(&self, entity: codec::EntityId) -> EntitySnapshot;
106
107 fn update(
109 &self,
110 entity: codec::EntityId,
111 dirty_components: &[ComponentId],
112 ) -> Option<DeltaUpdateEntity>;
113}
114
115#[derive(Debug, Clone)]
116struct EntityEntry {
117 position: Vec3,
118 priority: u8,
119 dirty_components: Vec<ComponentId>,
120}
121
122#[derive(Debug, Clone)]
123struct ClientState {
124 view: ClientView,
125 known_entities: BTreeSet<codec::EntityId>,
126}
127
128#[derive(Debug, Clone)]
130pub struct ReplicationGraph {
131 config: ReplicationConfig,
132 entities: BTreeMap<codec::EntityId, EntityEntry>,
133 removed_entities: BTreeSet<codec::EntityId>,
134 clients: HashMap<ClientId, ClientState>,
135}
136
137impl ReplicationGraph {
138 #[must_use]
139 pub fn new(config: ReplicationConfig) -> Self {
140 Self {
141 config,
142 entities: BTreeMap::new(),
143 removed_entities: BTreeSet::new(),
144 clients: HashMap::new(),
145 }
146 }
147
148 pub fn update_entity(
150 &mut self,
151 entity: codec::EntityId,
152 position: Vec3,
153 dirty_components: &[ComponentId],
154 ) {
155 if self.entities.len() >= self.config.max_entities && !self.entities.contains_key(&entity) {
156 return;
157 }
158 let entry = self.entities.entry(entity).or_insert(EntityEntry {
159 position,
160 priority: 0,
161 dirty_components: Vec::new(),
162 });
163 entry.position = position;
164 push_unique_components(&mut entry.dirty_components, dirty_components);
165 }
166
167 pub fn set_entity_priority(&mut self, entity: codec::EntityId, priority: u8) {
169 if let Some(entry) = self.entities.get_mut(&entity) {
170 entry.priority = priority;
171 }
172 }
173
174 pub fn remove_entity(&mut self, entity: codec::EntityId) {
176 self.entities.remove(&entity);
177 self.removed_entities.insert(entity);
178 }
179
180 pub fn upsert_client(&mut self, client: ClientId, view: ClientView) {
182 self.clients
183 .entry(client)
184 .and_modify(|state| state.view = view)
185 .or_insert(ClientState {
186 view,
187 known_entities: BTreeSet::new(),
188 });
189 }
190
191 pub fn remove_client(&mut self, client: ClientId) {
193 self.clients.remove(&client);
194 }
195
196 pub fn build_client_delta(&mut self, client: ClientId, world: &impl WorldView) -> ClientDelta {
198 let Some(state) = self.clients.get_mut(&client) else {
199 return ClientDelta {
200 creates: Vec::new(),
201 destroys: Vec::new(),
202 updates: Vec::new(),
203 };
204 };
205
206 let radius_sq = state.view.radius * state.view.radius;
207 let mut relevant: BTreeSet<codec::EntityId> = BTreeSet::new();
208 for (id, entry) in &self.entities {
209 if entry.position.distance_sq(state.view.position) <= radius_sq {
210 relevant.insert(*id);
211 }
212 }
213
214 let mut creates = Vec::new();
215 let mut updates = Vec::new();
216 for id in relevant.iter().copied() {
217 if !state.known_entities.contains(&id) {
218 creates.push(world.snapshot(id));
219 continue;
220 }
221 if let Some(entry) = self.entities.get(&id) {
222 if !entry.dirty_components.is_empty() {
223 if let Some(update) = world.update(id, &entry.dirty_components) {
224 updates.push(update);
225 }
226 }
227 }
228 }
229
230 let mut destroys: Vec<codec::EntityId> = state
231 .known_entities
232 .difference(&relevant)
233 .copied()
234 .collect();
235 for removed in &self.removed_entities {
236 if state.known_entities.contains(removed) && !destroys.contains(removed) {
237 destroys.push(*removed);
238 }
239 }
240 destroys.sort_by_key(|id| id.raw());
241
242 apply_budget(&mut creates, &mut updates, &mut destroys, state.view.budget);
243
244 let mut next_known = state.known_entities.clone();
245 for destroy in &destroys {
246 next_known.remove(destroy);
247 }
248 for create in &creates {
249 next_known.insert(create.id);
250 }
251 state.known_entities = next_known;
252
253 ClientDelta {
254 creates,
255 destroys,
256 updates,
257 }
258 }
259
260 pub fn clear_dirty(&mut self) {
262 for entry in self.entities.values_mut() {
263 entry.dirty_components.clear();
264 }
265 }
266
267 pub fn clear_removed(&mut self) {
269 self.removed_entities.clear();
270 }
271}
272
273fn push_unique_components(target: &mut Vec<ComponentId>, new_components: &[ComponentId]) {
274 for component in new_components {
275 if !target.contains(component) {
276 target.push(*component);
277 }
278 }
279}
280
281fn apply_budget(
282 creates: &mut Vec<EntitySnapshot>,
283 updates: &mut Vec<DeltaUpdateEntity>,
284 destroys: &mut Vec<codec::EntityId>,
285 budget: ClientBudget,
286) {
287 if creates.len() > budget.max_creates {
288 creates.truncate(budget.max_creates);
289 }
290 if updates.len() > budget.max_updates {
291 updates.truncate(budget.max_updates);
292 }
293 if destroys.len() > budget.max_destroys {
294 destroys.truncate(budget.max_destroys);
295 }
296}