1use std::collections::BTreeMap;
22
23pub mod datagram;
24pub mod frame;
25pub mod varint;
26pub mod view;
27
28pub use frame::{DecodeError, FrameSummary, ReplicaEntity, ReplicaTable};
29pub use view::ViewHistory;
30
31pub type EntityId = u64;
33
34pub type ComponentId = u8;
36
37#[derive(Debug, Clone, PartialEq)]
39pub struct Entity {
40 pub pos: [f32; 3],
41 pub pos_seq: u64,
43 pub components: BTreeMap<ComponentId, Component>,
46 pub seq: u64,
48 pub spawn_seq: u64,
52}
53
54impl Entity {
55 pub fn changed_since(&self, since: u64) -> bool {
57 self.seq > since
58 }
59
60 pub fn component(&self, id: ComponentId) -> Option<&[u8]> {
62 self.components.get(&id).and_then(|c| c.bytes.as_deref())
63 }
64}
65
66#[derive(Debug, Clone, PartialEq)]
67pub struct Component {
68 pub bytes: Option<Vec<u8>>,
69 pub seq: u64,
70}
71
72const ENTITY_COST: usize = 160;
79const COMPONENT_MAP_COST: usize = 400;
82const COMPONENT_COST: usize = 64;
84
85#[derive(Debug)]
87pub struct Replicated {
88 entities: BTreeMap<EntityId, Entity>,
89 seq: u64,
90 log: Option<Vec<u8>>,
91 cost: usize,
94 store_id: u64,
97}
98
99impl Default for Replicated {
100 fn default() -> Self {
101 static NEXT: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(1);
102 Self {
103 entities: BTreeMap::new(),
104 seq: 0,
105 log: None,
106 cost: 0,
107 store_id: NEXT.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
108 }
109 }
110}
111
112impl Clone for Replicated {
116 fn clone(&self) -> Self {
117 Self {
118 entities: self.entities.clone(),
119 seq: self.seq,
120 cost: self.cost,
121 ..Self::default()
122 }
123 }
124}
125
126impl PartialEq for Replicated {
129 fn eq(&self, other: &Self) -> bool {
130 self.entities == other.entities && self.seq == other.seq
131 }
132}
133
134mod op {
135 pub const SPAWN: u8 = 1;
136 pub const DESPAWN: u8 = 2;
137 pub const POS: u8 = 3;
138 pub const SET: u8 = 4;
139 pub const REMOVE: u8 = 5;
140}
141
142#[derive(Debug, Clone, PartialEq, Eq)]
144pub struct ChangeError(pub String);
145
146impl std::fmt::Display for ChangeError {
147 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148 f.write_str(&self.0)
149 }
150}
151
152impl std::error::Error for ChangeError {}
153
154impl Replicated {
155 pub fn new() -> Self {
156 Self::default()
157 }
158
159 pub fn seq(&self) -> u64 {
161 self.seq
162 }
163
164 pub fn len(&self) -> usize {
165 self.entities.len()
166 }
167
168 pub fn is_empty(&self) -> bool {
169 self.entities.is_empty()
170 }
171
172 pub fn cost(&self) -> usize {
175 self.cost
176 }
177
178 pub fn store_id(&self) -> u64 {
180 self.store_id
181 }
182
183 pub fn clear(&mut self) {
187 self.entities.clear();
188 self.cost = 0;
189 self.bump();
190 }
191
192 pub fn get(&self, id: EntityId) -> Option<&Entity> {
193 self.entities.get(&id)
194 }
195
196 pub fn contains(&self, id: EntityId) -> bool {
197 self.entities.contains_key(&id)
198 }
199
200 pub fn iter(&self) -> impl Iterator<Item = (EntityId, &Entity)> {
202 self.entities.iter().map(|(id, e)| (*id, e))
203 }
204
205 fn bump(&mut self) -> u64 {
206 self.seq += 1;
207 self.seq
208 }
209
210 pub fn spawn(&mut self, id: EntityId, pos: [f32; 3]) -> bool {
213 if self.entities.contains_key(&id) {
214 return false;
215 }
216 self.cost += ENTITY_COST;
217 let seq = self.bump();
218 self.entities.insert(
219 id,
220 Entity {
221 pos,
222 pos_seq: seq,
223 components: BTreeMap::new(),
224 seq,
225 spawn_seq: seq,
226 },
227 );
228 if let Some(log) = &mut self.log {
229 log.push(op::SPAWN);
230 varint::write_u64(log, id);
231 write_pos(log, pos);
232 }
233 true
234 }
235
236 pub fn despawn(&mut self, id: EntityId) -> bool {
238 let Some(e) = self.entities.remove(&id) else {
239 return false;
240 };
241 self.cost -= entity_cost(&e);
242 self.bump();
243 if let Some(log) = &mut self.log {
244 log.push(op::DESPAWN);
245 varint::write_u64(log, id);
246 }
247 true
248 }
249
250 pub fn set_pos(&mut self, id: EntityId, pos: [f32; 3]) -> bool {
252 let Some(current) = self.entities.get(&id).map(|e| e.pos) else {
253 return false;
254 };
255 if current.map(f32::to_bits) == pos.map(f32::to_bits) {
256 return true;
257 }
258 let seq = self.bump();
259 let e = self.entities.get_mut(&id).expect("checked above");
260 e.pos = pos;
261 e.pos_seq = seq;
262 e.seq = seq;
263 if let Some(log) = &mut self.log {
264 log.push(op::POS);
265 varint::write_u64(log, id);
266 write_pos(log, pos);
267 }
268 true
269 }
270
271 pub fn set_component(&mut self, id: EntityId, component: ComponentId, bytes: &[u8]) -> bool {
273 let Some(e) = self.entities.get(&id) else {
274 return false;
275 };
276 if e.component(component) == Some(bytes) {
277 return true;
278 }
279 let seq = self.bump();
280 let e = self.entities.get_mut(&id).expect("checked above");
281 if e.components.is_empty() {
282 self.cost += COMPONENT_MAP_COST;
283 }
284 let old = e.components.insert(
285 component,
286 Component {
287 bytes: Some(bytes.to_vec()),
288 seq,
289 },
290 );
291 e.seq = seq;
292 self.cost += bytes.len();
293 match old {
294 Some(c) => self.cost -= c.bytes.map_or(0, |b| b.len()),
295 None => self.cost += COMPONENT_COST,
296 }
297 if let Some(log) = &mut self.log {
298 log.push(op::SET);
299 varint::write_u64(log, id);
300 log.push(component);
301 varint::write_u64(log, bytes.len() as u64);
302 log.extend_from_slice(bytes);
303 }
304 true
305 }
306
307 pub fn remove_component(&mut self, id: EntityId, component: ComponentId) -> bool {
310 let present = self
311 .entities
312 .get(&id)
313 .is_some_and(|e| e.component(component).is_some());
314 if !present {
315 return false;
316 }
317 let seq = self.bump();
318 let e = self.entities.get_mut(&id).expect("checked above");
319 let old = e
320 .components
321 .insert(component, Component { bytes: None, seq });
322 e.seq = seq;
323 self.cost -= old.and_then(|c| c.bytes).map_or(0, |b| b.len());
325 if let Some(log) = &mut self.log {
326 log.push(op::REMOVE);
327 varint::write_u64(log, id);
328 log.push(component);
329 }
330 true
331 }
332
333 pub fn record_changes(&mut self, on: bool) {
336 self.log = on.then(Vec::new);
337 }
338
339 pub fn take_changes(&mut self) -> Vec<u8> {
342 match &mut self.log {
343 Some(log) => std::mem::take(log),
344 None => Vec::new(),
345 }
346 }
347
348 pub fn full_changes(&self) -> Vec<u8> {
351 let mut out = Vec::new();
352 for (id, e) in &self.entities {
353 out.push(op::SPAWN);
354 varint::write_u64(&mut out, *id);
355 write_pos(&mut out, e.pos);
356 for (cid, c) in &e.components {
357 if let Some(bytes) = &c.bytes {
358 out.push(op::SET);
359 varint::write_u64(&mut out, *id);
360 out.push(*cid);
361 varint::write_u64(&mut out, bytes.len() as u64);
362 out.extend_from_slice(bytes);
363 }
364 }
365 }
366 out
367 }
368
369 pub fn apply_changes(&mut self, bytes: &[u8]) -> Result<(), ChangeError> {
372 self.apply_changes_limited(bytes, usize::MAX, usize::MAX)
373 }
374
375 pub fn apply_changes_limited(
380 &mut self,
381 mut bytes: &[u8],
382 max_entities: usize,
383 max_cost: usize,
384 ) -> Result<(), ChangeError> {
385 let err = |m: &str| ChangeError(m.to_string());
386 let over = |entities: usize, cost: usize| {
387 ChangeError(format!(
388 "the change takes the store past its limit ({entities} entities, {cost} bytes; at most {max_entities} and {max_cost})"
389 ))
390 };
391 while let Some((&tag, rest)) = bytes.split_first() {
392 bytes = rest;
393 let id = varint::read_u64(&mut bytes).ok_or_else(|| err("truncated entity id"))?;
394 match tag {
395 op::SPAWN => {
396 let pos = read_pos(&mut bytes).ok_or_else(|| err("truncated position"))?;
397 let (entities, cost) = (self.entities.len() + 1, self.cost + ENTITY_COST);
398 if !self.entities.contains_key(&id)
399 && (entities > max_entities || cost > max_cost)
400 {
401 return Err(over(entities, cost));
402 }
403 if !self.spawn(id, pos) {
404 return Err(ChangeError(format!("spawn of existing entity {id}")));
405 }
406 }
407 op::DESPAWN => {
408 self.despawn(id);
409 }
410 op::POS => {
411 let pos = read_pos(&mut bytes).ok_or_else(|| err("truncated position"))?;
412 if !self.set_pos(id, pos) {
413 return Err(ChangeError(format!("move of missing entity {id}")));
414 }
415 }
416 op::SET => {
417 let (&cid, rest) = bytes
418 .split_first()
419 .ok_or_else(|| err("truncated component"))?;
420 bytes = rest;
421 let len =
422 varint::read_u64(&mut bytes).ok_or_else(|| err("truncated length"))?;
423 let len = usize::try_from(len).map_err(|_| err("component too large"))?;
424 if bytes.len() < len {
425 return Err(err("component runs past the end"));
426 }
427 let (value, rest) = bytes.split_at(len);
428 bytes = rest;
429 let Some(e) = self.entities.get(&id) else {
430 return Err(ChangeError(format!("component on missing entity {id}")));
431 };
432 let added = match e.components.get(&cid) {
433 Some(c) => len.saturating_sub(c.bytes.as_ref().map_or(0, Vec::len)),
434 None if e.components.is_empty() => {
435 COMPONENT_MAP_COST + COMPONENT_COST + len
436 }
437 None => COMPONENT_COST + len,
438 };
439 let cost = self.cost.saturating_add(added);
440 if cost > max_cost {
441 return Err(over(self.entities.len(), cost));
442 }
443 self.set_component(id, cid, value);
444 }
445 op::REMOVE => {
446 let (&cid, rest) = bytes
447 .split_first()
448 .ok_or_else(|| err("truncated component"))?;
449 bytes = rest;
450 self.remove_component(id, cid);
451 }
452 other => return Err(ChangeError(format!("unknown change tag {other}"))),
453 }
454 }
455 Ok(())
456 }
457}
458
459fn entity_cost(e: &Entity) -> usize {
461 let map = if e.components.is_empty() {
462 0
463 } else {
464 COMPONENT_MAP_COST
465 };
466 ENTITY_COST
467 + map
468 + e.components
469 .values()
470 .map(|c| COMPONENT_COST + c.bytes.as_ref().map_or(0, Vec::len))
471 .sum::<usize>()
472}
473
474fn write_pos(out: &mut Vec<u8>, pos: [f32; 3]) {
475 for v in pos {
476 out.extend_from_slice(&v.to_le_bytes());
477 }
478}
479
480fn read_pos(bytes: &mut &[u8]) -> Option<[f32; 3]> {
481 if bytes.len() < 12 {
482 return None;
483 }
484 let (head, rest) = bytes.split_at(12);
485 *bytes = rest;
486 let f = |i: usize| f32::from_le_bytes(head[i..i + 4].try_into().unwrap());
487 Some([f(0), f(4), f(8)])
488}
489
490#[cfg(test)]
491mod tests {
492 use super::*;
493
494 #[test]
495 fn changes_bump_the_counter_and_no_ops_do_not() {
496 let mut r = Replicated::new();
497 assert!(r.spawn(1, [0.0, 0.0, 0.0]));
498 assert!(!r.spawn(1, [5.0, 0.0, 0.0]));
499 let s = r.seq();
500 r.set_pos(1, [0.0, 0.0, 0.0]);
501 r.set_component(1, 3, b"x");
502 let after = r.seq();
503 assert_eq!(after, s + 1);
504 r.set_component(1, 3, b"x");
505 assert_eq!(r.seq(), after);
506 assert!(r.get(1).unwrap().changed_since(s));
507 assert!(!r.get(1).unwrap().changed_since(after));
508 assert!(r.remove_component(1, 3));
509 assert_eq!(r.get(1).unwrap().component(3), None);
510 assert!(!r.remove_component(1, 3));
511 }
512
513 #[test]
514 fn a_recorded_log_rebuilds_the_same_entities() {
515 let mut guest = Replicated::new();
516 guest.record_changes(true);
517 guest.spawn(7, [1.0, 2.0, 3.0]);
518 guest.spawn(9, [0.0, 0.0, 0.0]);
519 guest.set_pos(7, [1.5, 2.0, 3.0]);
520 guest.set_component(7, 1, b"hp=10");
521 guest.set_component(9, 2, &[]);
522 guest.remove_component(9, 2);
523 guest.despawn(9);
524 let log = guest.take_changes();
525 assert!(guest.take_changes().is_empty());
526
527 let mut host = Replicated::new();
528 host.apply_changes(&log).unwrap();
529 let summary = |r: &Replicated| {
530 r.iter()
531 .map(|(id, e)| (id, e.pos, e.component(1).map(<[u8]>::to_vec)))
532 .collect::<Vec<_>>()
533 };
534 assert_eq!(summary(&host), summary(&guest));
535 }
536
537 #[test]
538 fn a_full_dump_rebuilds_the_store() {
539 let mut a = Replicated::new();
540 a.spawn(1, [1.0, 2.0, 3.0]);
541 a.spawn(4, [0.0; 3]);
542 a.set_component(1, 2, b"x");
543 a.set_component(4, 2, b"y");
544 a.remove_component(4, 2);
545 let mut b = Replicated::new();
546 b.apply_changes(&a.full_changes()).unwrap();
547 let view = |r: &Replicated| {
548 r.iter()
549 .map(|(id, e)| (id, e.pos, e.component(2).map(<[u8]>::to_vec)))
550 .collect::<Vec<_>>()
551 };
552 assert_eq!(view(&a), view(&b));
553 }
554
555 #[test]
556 fn cost_counts_entities_entries_tombstones_and_bytes() {
557 let mut r = Replicated::new();
558 r.spawn(1, [0.0; 3]);
559 assert_eq!(r.cost(), ENTITY_COST);
560 let base = ENTITY_COST + COMPONENT_MAP_COST;
561 r.set_component(1, 1, &[0; 10]);
562 r.set_component(1, 2, &[0; 5]);
563 assert_eq!(r.cost(), base + 2 * COMPONENT_COST + 15);
564 r.set_component(1, 1, &[0; 3]);
565 assert_eq!(r.cost(), base + 2 * COMPONENT_COST + 8);
566 r.remove_component(1, 2);
568 assert_eq!(r.cost(), base + 2 * COMPONENT_COST + 3);
569 r.set_component(1, 9, &[]);
571 assert_eq!(r.cost(), base + 3 * COMPONENT_COST + 3);
572 r.despawn(1);
573 assert_eq!(r.cost(), 0);
574 }
575
576 #[test]
577 fn a_limited_apply_stops_at_the_change_that_passes_the_limit() {
578 let mut source = Replicated::new();
579 source.record_changes(true);
580 for id in 0..1000 {
581 source.spawn(id, [0.0; 3]);
582 }
583 let log = source.take_changes();
584 let mut host = Replicated::new();
585 assert!(host.apply_changes_limited(&log, 10, usize::MAX).is_err());
586 assert_eq!(host.len(), 10);
588 let mut host = Replicated::new();
589 assert!(host
590 .apply_changes_limited(&log, usize::MAX, 20 * ENTITY_COST)
591 .is_err());
592 assert_eq!(host.len(), 20);
593 assert!(host.cost() <= 20 * ENTITY_COST);
594 }
595
596 #[test]
597 fn a_limited_apply_refuses_one_large_component_before_it_applies() {
598 let mut source = Replicated::new();
599 source.record_changes(true);
600 source.spawn(1, [0.0; 3]);
601 source.set_component(1, 1, &[7; 100]);
602 source.set_component(1, 1, &vec![7; 1 << 20]);
603 let log = source.take_changes();
604 let limit = ENTITY_COST + COMPONENT_MAP_COST + COMPONENT_COST + 1000;
605 let mut host = Replicated::new();
606 assert!(host.apply_changes_limited(&log, usize::MAX, limit).is_err());
607 assert_eq!(
609 host.get(1).unwrap().component(1).map(<[u8]>::len),
610 Some(100)
611 );
612 assert!(host.cost() <= limit);
613 }
614
615 #[test]
616 fn a_clone_is_a_new_store_without_a_log() {
617 let mut a = Replicated::new();
618 a.record_changes(true);
619 a.spawn(1, [0.0; 3]);
620 let b = a.clone();
621 assert_ne!(a.store_id(), b.store_id());
622 let mut b = b;
623 assert!(b.take_changes().is_empty());
624 assert_eq!(a, b);
625 assert_eq!(a.cost(), b.cost());
626 }
627
628 #[test]
629 fn clear_keeps_the_counter_so_a_respawned_id_is_new() {
630 let mut r = Replicated::new();
631 r.spawn(7, [0.0; 3]);
632 let first = r.get(7).unwrap().spawn_seq;
633 r.clear();
634 r.spawn(7, [0.0; 3]);
635 assert!(r.get(7).unwrap().spawn_seq > first);
636 assert_ne!(Replicated::new().store_id(), Replicated::new().store_id());
637 }
638
639 #[test]
640 fn bad_logs_are_refused_without_panicking() {
641 let mut r = Replicated::new();
642 assert!(r.apply_changes(&[op::SPAWN]).is_err());
643 assert!(r.apply_changes(&[op::SPAWN, 1, 0, 0]).is_err());
644 assert!(r
645 .apply_changes(&[op::POS, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
646 .is_err());
647 assert!(r.apply_changes(&[99, 1]).is_err());
648 let mut long = vec![op::SPAWN, 1];
649 long.extend_from_slice(&[0; 12]);
650 long.extend_from_slice(&[op::SET, 1, 4, 0xff, 0xff, 0xff, 0xff, 0x0f]);
651 assert!(r.apply_changes(&long).is_err());
652 }
653}