1use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
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 = Vec::with_capacity(self.entities.len());
208 let mut relevant_set = HashSet::with_capacity(self.entities.len());
209 for (id, entry) in &self.entities {
210 if entry.position.distance_sq(state.view.position) <= radius_sq {
211 relevant.push(*id);
212 relevant_set.insert(*id);
213 }
214 }
215
216 let mut creates = Vec::new();
217 let mut updates = Vec::new();
218 for id in relevant.iter().copied() {
219 if !state.known_entities.contains(&id) {
220 creates.push(world.snapshot(id));
221 continue;
222 }
223 if let Some(entry) = self.entities.get(&id) {
224 if !entry.dirty_components.is_empty() {
225 if let Some(update) = world.update(id, &entry.dirty_components) {
226 updates.push(update);
227 }
228 }
229 }
230 }
231
232 let mut destroys = Vec::new();
233 let mut destroys_set = HashSet::with_capacity(state.known_entities.len());
234 for id in state.known_entities.iter().copied() {
235 if !relevant_set.contains(&id) {
236 destroys.push(id);
237 destroys_set.insert(id);
238 }
239 }
240 for removed in &self.removed_entities {
241 if state.known_entities.contains(removed) && !destroys_set.contains(removed) {
242 destroys.push(*removed);
243 destroys_set.insert(*removed);
244 }
245 }
246 destroys.sort_by_key(|id| id.raw());
247
248 apply_budget(&mut creates, &mut updates, &mut destroys, state.view.budget);
249
250 for destroy in &destroys {
251 state.known_entities.remove(destroy);
252 }
253 for create in &creates {
254 state.known_entities.insert(create.id);
255 }
256
257 ClientDelta {
258 creates,
259 destroys,
260 updates,
261 }
262 }
263
264 pub fn clear_dirty(&mut self) {
266 for entry in self.entities.values_mut() {
267 entry.dirty_components.clear();
268 }
269 }
270
271 pub fn clear_removed(&mut self) {
273 self.removed_entities.clear();
274 }
275}
276
277fn push_unique_components(target: &mut Vec<ComponentId>, new_components: &[ComponentId]) {
278 for component in new_components {
279 if !target.contains(component) {
280 target.push(*component);
281 }
282 }
283}
284
285fn apply_budget(
286 creates: &mut Vec<EntitySnapshot>,
287 updates: &mut Vec<DeltaUpdateEntity>,
288 destroys: &mut Vec<codec::EntityId>,
289 budget: ClientBudget,
290) {
291 if creates.len() > budget.max_creates {
292 creates.truncate(budget.max_creates);
293 }
294 if updates.len() > budget.max_updates {
295 updates.truncate(budget.max_updates);
296 }
297 if destroys.len() > budget.max_destroys {
298 destroys.truncate(budget.max_destroys);
299 }
300}