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