pub struct SlotTable { /* private fields */ }Expand description
Mutable collection of ReplicaSlots. Slots are addressed by id;
duplicate insertion upserts (newer state wins). Replica counts in
realistic deployments are small (< 16); a linear Vec is faster
than a HashMap at this size and avoids the cost of the hasher
the rest of the workspace uses.
use kevy_replicate::slot::SlotTable;
let mut slots = SlotTable::new();
slots.insert_or_touch("r1", 3, 0);
slots.insert_or_touch("r1", 8, 1); // upsert, not a second row
assert_eq!(slots.len(), 1);
assert_eq!(slots.min_acked_offset(), Some(8));Implementations§
Source§impl SlotTable
impl SlotTable
Sourcepub fn new() -> Self
pub fn new() -> Self
A fresh empty table.
let slots = kevy_replicate::slot::SlotTable::new();
assert!(slots.is_empty());Sourcepub fn len(&self) -> usize
pub fn len(&self) -> usize
Number of slots currently tracked.
let mut slots = kevy_replicate::slot::SlotTable::new();
slots.insert_or_touch("r1", 0, 0);
slots.insert_or_touch("r2", 0, 0);
assert_eq!(slots.len(), 2);Sourcepub fn is_empty(&self) -> bool
pub fn is_empty(&self) -> bool
Whether the table has no slots.
let mut slots = kevy_replicate::slot::SlotTable::new();
assert!(slots.is_empty());
slots.insert_or_touch("r1", 0, 0);
assert!(!slots.is_empty());Sourcepub fn get(&self, id: &str) -> Option<&ReplicaSlot>
pub fn get(&self, id: &str) -> Option<&ReplicaSlot>
Look up a slot by id.
let mut slots = kevy_replicate::slot::SlotTable::new();
slots.insert_or_touch("r1", 5, 0);
assert_eq!(slots.get("r1").map(|s| s.acked_offset), Some(5));
assert!(slots.get("r2").is_none());Sourcepub fn touch_or_insert_unacked(&mut self, id: &str, now_ns: u64)
pub fn touch_or_insert_unacked(&mut self, id: &str, now_ns: u64)
Refresh a slot’s last_seen_ns WITHOUT advancing its acked
offset — the disconnect-time variant of
Self::insert_or_touch. A closing conn knows what it SENT,
not what the peer acked; recording sent-as-acked would let
WAIT / min-replicas count bytes the replica never confirmed
and let the backlog trim frames the peer may still need. A
previously unseen id is inserted at acked_offset = 0
(never acked — the truthful floor).
let mut slots = kevy_replicate::slot::SlotTable::new();
slots.insert_or_touch("r1", 12, 100);
slots.touch_or_insert_unacked("r1", 200); // the link closed at t = 200
slots.touch_or_insert_unacked("r2", 200); // never acked anything
assert_eq!(slots.get("r1").map(|s| (s.acked_offset, s.last_seen_ns)), Some((12, 200)));
assert_eq!(slots.get("r2").map(|s| s.acked_offset), Some(0));Sourcepub fn iter(&self) -> impl Iterator<Item = &ReplicaSlot>
pub fn iter(&self) -> impl Iterator<Item = &ReplicaSlot>
Iterate over all slots.
let mut slots = kevy_replicate::slot::SlotTable::new();
slots.insert_or_touch("r1", 4, 0);
slots.insert_or_touch("r2", 6, 0);
let acked: u64 = slots.iter().map(|s| s.acked_offset).sum();
assert_eq!(acked, 10);Sourcepub fn insert_or_touch(&mut self, id: &str, acked_offset: u64, now_ns: u64)
pub fn insert_or_touch(&mut self, id: &str, acked_offset: u64, now_ns: u64)
Insert a new slot or update an existing one. Touching always
refreshes last_seen_ns and advances acked_offset if the
new value is higher (a slot’s acked offset is monotonic — a
peer reporting a lower offset than we already recorded is
almost always a bug; the silent max() here defends the
invariant).
let mut slots = kevy_replicate::slot::SlotTable::new();
slots.insert_or_touch("r1", 5, 100); // first contact inserts
slots.insert_or_touch("r1", 9, 200); // later acks advance
assert_eq!(slots.get("r1").map(|s| (s.acked_offset, s.last_seen_ns)), Some((9, 200)));Sourcepub fn remove(&mut self, id: &str) -> bool
pub fn remove(&mut self, id: &str) -> bool
Remove the slot with the given id. Returns true if a slot
was actually removed.
let mut slots = kevy_replicate::slot::SlotTable::new();
slots.insert_or_touch("r1", 0, 0);
assert!(slots.remove("r1"));
assert!(!slots.remove("r1")); // already goneSourcepub fn expire(&mut self, now_ns: u64, window_ns: u64) -> Vec<String>
pub fn expire(&mut self, now_ns: u64, window_ns: u64) -> Vec<String>
Drop slots whose last_seen_ns + window_ns ≤ now_ns. Returns
the ids of the dropped slots so callers can fire metrics or
log lines. Order is unspecified (swap-remove internally).
let mut slots = kevy_replicate::slot::SlotTable::new();
slots.insert_or_touch("stale", 0, 100);
slots.insert_or_touch("live", 0, 900);
assert_eq!(slots.expire(1_000, 500), ["stale"]); // 100 + 500 <= 1000
assert_eq!(slots.len(), 1);Sourcepub fn min_acked_offset(&self) -> Option<u64>
pub fn min_acked_offset(&self) -> Option<u64>
Lowest acked offset across all tracked slots. Useful for the
streaming loop to know how far back the backlog must still
retain frames; None when the table is empty.
use kevy_replicate::slot::SlotTable;
let mut slots = SlotTable::new();
assert_eq!(slots.min_acked_offset(), None);
slots.insert_or_touch("fast", 90, 0);
slots.insert_or_touch("slow", 40, 0);
assert_eq!(slots.min_acked_offset(), Some(40)); // trim the backlog below 40 only