1use std::collections::HashMap;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
7pub struct SnapshotId(pub u64);
8
9impl SnapshotId {
10 pub fn next(self) -> Self {
11 SnapshotId(self.0.wrapping_add(1))
12 }
13
14 pub fn distance(self, other: SnapshotId) -> u64 {
15 if self.0 >= other.0 {
16 self.0 - other.0
17 } else {
18 other.0 - self.0
19 }
20 }
21
22 pub fn zero() -> Self {
23 SnapshotId(0)
24 }
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
29pub struct NetEntityId(pub u32);
30
31#[derive(Debug, Clone)]
34pub struct ComponentData {
35 pub type_id: u16,
36 pub data: Vec<u8>,
37 pub version: u32,
38}
39
40impl ComponentData {
41 pub fn new(type_id: u16, data: Vec<u8>) -> Self {
42 Self {
43 type_id,
44 data,
45 version: 1,
46 }
47 }
48
49 pub fn with_version(type_id: u16, data: Vec<u8>, version: u32) -> Self {
50 Self {
51 type_id,
52 data,
53 version,
54 }
55 }
56
57 pub fn size(&self) -> usize {
58 self.data.len() + 6 }
60
61 pub fn content_hash(&self) -> u64 {
63 let mut hash: u64 = 0xcbf29ce484222325;
64 for &byte in &self.data {
65 hash ^= byte as u64;
66 hash = hash.wrapping_mul(0x100000001b3);
67 }
68 hash
69 }
70
71 pub fn delta_against(&self, baseline: &ComponentData) -> Option<ComponentDelta> {
74 if self.type_id != baseline.type_id {
75 return Some(ComponentDelta {
76 type_id: self.type_id,
77 kind: DeltaKind::Replaced(self.data.clone()),
78 new_version: self.version,
79 });
80 }
81
82 if self.data == baseline.data {
83 return None;
84 }
85
86 if self.data.len() == baseline.data.len() {
88 let mut xor_data = Vec::with_capacity(self.data.len());
89 let mut has_diff = false;
90 for i in 0..self.data.len() {
91 let x = self.data[i] ^ baseline.data[i];
92 if x != 0 {
93 has_diff = true;
94 }
95 xor_data.push(x);
96 }
97
98 if !has_diff {
99 return None;
100 }
101
102 let rle = rle_encode(&xor_data);
104 if rle.len() < xor_data.len() {
105 return Some(ComponentDelta {
106 type_id: self.type_id,
107 kind: DeltaKind::XorRle(rle),
108 new_version: self.version,
109 });
110 }
111
112 return Some(ComponentDelta {
113 type_id: self.type_id,
114 kind: DeltaKind::XorRaw(xor_data),
115 new_version: self.version,
116 });
117 }
118
119 Some(ComponentDelta {
121 type_id: self.type_id,
122 kind: DeltaKind::Replaced(self.data.clone()),
123 new_version: self.version,
124 })
125 }
126
127 pub fn apply_delta(&self, delta: &ComponentDelta) -> ComponentData {
129 let new_data = match &delta.kind {
130 DeltaKind::Replaced(data) => data.clone(),
131 DeltaKind::XorRaw(xor) => {
132 let mut result = self.data.clone();
133 for i in 0..result.len().min(xor.len()) {
134 result[i] ^= xor[i];
135 }
136 result
137 }
138 DeltaKind::XorRle(rle) => {
139 let xor = rle_decode(rle, self.data.len());
140 let mut result = self.data.clone();
141 for i in 0..result.len().min(xor.len()) {
142 result[i] ^= xor[i];
143 }
144 result
145 }
146 };
147 ComponentData {
148 type_id: delta.type_id,
149 data: new_data,
150 version: delta.new_version,
151 }
152 }
153}
154
155fn rle_encode(data: &[u8]) -> Vec<u8> {
157 let mut result = Vec::new();
158 if data.is_empty() {
159 return result;
160 }
161 let mut current = data[0];
162 let mut count: u8 = 1;
163 for &byte in &data[1..] {
164 if byte == current && count < 255 {
165 count += 1;
166 } else {
167 result.push(count);
168 result.push(current);
169 current = byte;
170 count = 1;
171 }
172 }
173 result.push(count);
174 result.push(current);
175 result
176}
177
178fn rle_decode(rle: &[u8], expected_len: usize) -> Vec<u8> {
180 let mut result = Vec::with_capacity(expected_len);
181 let mut i = 0;
182 while i + 1 < rle.len() && result.len() < expected_len {
183 let count = rle[i] as usize;
184 let value = rle[i + 1];
185 for _ in 0..count {
186 if result.len() >= expected_len {
187 break;
188 }
189 result.push(value);
190 }
191 i += 2;
192 }
193 while result.len() < expected_len {
194 result.push(0);
195 }
196 result
197}
198
199#[derive(Debug, Clone)]
201pub struct EntitySnapshot {
202 pub entity_id: NetEntityId,
203 pub components: Vec<ComponentData>,
204 pub position: [f32; 3],
205 pub rotation: [f32; 4],
206 pub velocity: [f32; 3],
207 pub flags: u32,
208}
209
210impl EntitySnapshot {
211 pub fn new(entity_id: NetEntityId) -> Self {
212 Self {
213 entity_id,
214 components: Vec::new(),
215 position: [0.0; 3],
216 rotation: [0.0, 0.0, 0.0, 1.0],
217 velocity: [0.0; 3],
218 flags: 0,
219 }
220 }
221
222 pub fn with_position(mut self, x: f32, y: f32, z: f32) -> Self {
223 self.position = [x, y, z];
224 self
225 }
226
227 pub fn with_rotation(mut self, x: f32, y: f32, z: f32, w: f32) -> Self {
228 self.rotation = [x, y, z, w];
229 self
230 }
231
232 pub fn with_velocity(mut self, x: f32, y: f32, z: f32) -> Self {
233 self.velocity = [x, y, z];
234 self
235 }
236
237 pub fn add_component(&mut self, component: ComponentData) {
238 if let Some(existing) = self.components.iter_mut().find(|c| c.type_id == component.type_id) {
240 *existing = component;
241 } else {
242 self.components.push(component);
243 }
244 }
245
246 pub fn get_component(&self, type_id: u16) -> Option<&ComponentData> {
247 self.components.iter().find(|c| c.type_id == type_id)
248 }
249
250 pub fn remove_component(&mut self, type_id: u16) -> bool {
251 let len_before = self.components.len();
252 self.components.retain(|c| c.type_id != type_id);
253 self.components.len() < len_before
254 }
255
256 pub fn total_size(&self) -> usize {
257 let base = 4 + 12 + 16 + 12 + 4; let comp_size: usize = self.components.iter().map(|c| c.size()).sum();
259 base + comp_size
260 }
261
262 pub fn delta_against(&self, baseline: &EntitySnapshot) -> EntityDelta {
264 let mut changed_components = Vec::new();
265 let mut added_components = Vec::new();
266 let mut removed_component_types = Vec::new();
267
268 for comp in &self.components {
270 if let Some(base_comp) = baseline.get_component(comp.type_id) {
271 if let Some(delta) = comp.delta_against(base_comp) {
272 changed_components.push(delta);
273 }
274 } else {
275 added_components.push(comp.clone());
276 }
277 }
278
279 for base_comp in &baseline.components {
281 if self.get_component(base_comp.type_id).is_none() {
282 removed_component_types.push(base_comp.type_id);
283 }
284 }
285
286 let pos_changed = (self.position[0] - baseline.position[0]).abs() > f32::EPSILON
288 || (self.position[1] - baseline.position[1]).abs() > f32::EPSILON
289 || (self.position[2] - baseline.position[2]).abs() > f32::EPSILON;
290
291 let rot_changed = (self.rotation[0] - baseline.rotation[0]).abs() > f32::EPSILON
292 || (self.rotation[1] - baseline.rotation[1]).abs() > f32::EPSILON
293 || (self.rotation[2] - baseline.rotation[2]).abs() > f32::EPSILON
294 || (self.rotation[3] - baseline.rotation[3]).abs() > f32::EPSILON;
295
296 let vel_changed = (self.velocity[0] - baseline.velocity[0]).abs() > f32::EPSILON
297 || (self.velocity[1] - baseline.velocity[1]).abs() > f32::EPSILON
298 || (self.velocity[2] - baseline.velocity[2]).abs() > f32::EPSILON;
299
300 EntityDelta {
301 entity_id: self.entity_id,
302 position: if pos_changed { Some(self.position) } else { None },
303 rotation: if rot_changed { Some(self.rotation) } else { None },
304 velocity: if vel_changed { Some(self.velocity) } else { None },
305 flags_changed: self.flags != baseline.flags,
306 new_flags: self.flags,
307 changed_components,
308 added_components,
309 removed_component_types,
310 }
311 }
312
313 pub fn apply_delta(&self, delta: &EntityDelta) -> EntitySnapshot {
315 let mut result = self.clone();
316
317 if let Some(pos) = delta.position {
318 result.position = pos;
319 }
320 if let Some(rot) = delta.rotation {
321 result.rotation = rot;
322 }
323 if let Some(vel) = delta.velocity {
324 result.velocity = vel;
325 }
326 if delta.flags_changed {
327 result.flags = delta.new_flags;
328 }
329
330 for comp_delta in &delta.changed_components {
332 if let Some(existing) = result.components.iter_mut().find(|c| c.type_id == comp_delta.type_id) {
333 let original = existing.clone();
334 *existing = original.apply_delta(comp_delta);
335 }
336 }
337
338 for added in &delta.added_components {
340 result.add_component(added.clone());
341 }
342
343 for &type_id in &delta.removed_component_types {
345 result.remove_component(type_id);
346 }
347
348 result
349 }
350}
351
352#[derive(Debug, Clone)]
354pub struct ComponentDelta {
355 pub type_id: u16,
356 pub kind: DeltaKind,
357 pub new_version: u32,
358}
359
360impl ComponentDelta {
361 pub fn size(&self) -> usize {
362 2 + 4 + match &self.kind {
363 DeltaKind::Replaced(data) => data.len() + 1,
364 DeltaKind::XorRaw(data) => data.len() + 1,
365 DeltaKind::XorRle(data) => data.len() + 1,
366 }
367 }
368}
369
370#[derive(Debug, Clone)]
372pub enum DeltaKind {
373 Replaced(Vec<u8>),
374 XorRaw(Vec<u8>),
375 XorRle(Vec<u8>),
376}
377
378#[derive(Debug, Clone)]
380pub struct EntityDelta {
381 pub entity_id: NetEntityId,
382 pub position: Option<[f32; 3]>,
383 pub rotation: Option<[f32; 4]>,
384 pub velocity: Option<[f32; 3]>,
385 pub flags_changed: bool,
386 pub new_flags: u32,
387 pub changed_components: Vec<ComponentDelta>,
388 pub added_components: Vec<ComponentData>,
389 pub removed_component_types: Vec<u16>,
390}
391
392impl EntityDelta {
393 pub fn is_empty(&self) -> bool {
394 self.position.is_none()
395 && self.rotation.is_none()
396 && self.velocity.is_none()
397 && !self.flags_changed
398 && self.changed_components.is_empty()
399 && self.added_components.is_empty()
400 && self.removed_component_types.is_empty()
401 }
402
403 pub fn estimated_size(&self) -> usize {
404 let mut size = 4; if self.position.is_some() { size += 12; }
406 if self.rotation.is_some() { size += 16; }
407 if self.velocity.is_some() { size += 12; }
408 if self.flags_changed { size += 4; }
409 for cd in &self.changed_components { size += cd.size(); }
410 for ac in &self.added_components { size += ac.size(); }
411 size += self.removed_component_types.len() * 2;
412 size
413 }
414}
415
416#[derive(Debug, Clone)]
418pub struct WorldSnapshot {
419 pub id: SnapshotId,
420 pub tick: u64,
421 pub timestamp_ms: u64,
422 pub entities: HashMap<NetEntityId, EntitySnapshot>,
423}
424
425impl WorldSnapshot {
426 pub fn new(id: SnapshotId, tick: u64, timestamp_ms: u64) -> Self {
427 Self {
428 id,
429 tick,
430 timestamp_ms,
431 entities: HashMap::new(),
432 }
433 }
434
435 pub fn add_entity(&mut self, snapshot: EntitySnapshot) {
436 self.entities.insert(snapshot.entity_id, snapshot);
437 }
438
439 pub fn remove_entity(&mut self, id: NetEntityId) -> Option<EntitySnapshot> {
440 self.entities.remove(&id)
441 }
442
443 pub fn get_entity(&self, id: NetEntityId) -> Option<&EntitySnapshot> {
444 self.entities.get(&id)
445 }
446
447 pub fn entity_count(&self) -> usize {
448 self.entities.len()
449 }
450
451 pub fn total_size(&self) -> usize {
452 let header = 8 + 8 + 8; let entity_size: usize = self.entities.values().map(|e| e.total_size()).sum();
454 header + entity_size
455 }
456
457 pub fn delta_from(&self, baseline: &WorldSnapshot) -> SnapshotDelta {
459 let mut entity_deltas = Vec::new();
460 let mut spawned_entities = Vec::new();
461 let mut despawned_entities = Vec::new();
462
463 for (id, entity) in &self.entities {
465 if let Some(base_entity) = baseline.entities.get(id) {
466 let delta = entity.delta_against(base_entity);
467 if !delta.is_empty() {
468 entity_deltas.push(delta);
469 }
470 } else {
471 spawned_entities.push(entity.clone());
472 }
473 }
474
475 for id in baseline.entities.keys() {
477 if !self.entities.contains_key(id) {
478 despawned_entities.push(*id);
479 }
480 }
481
482 SnapshotDelta {
483 baseline_id: baseline.id,
484 target_id: self.id,
485 baseline_tick: baseline.tick,
486 target_tick: self.tick,
487 timestamp_ms: self.timestamp_ms,
488 entity_deltas,
489 spawned_entities,
490 despawned_entities,
491 }
492 }
493
494 pub fn apply_delta(&self, delta: &SnapshotDelta) -> WorldSnapshot {
496 let mut result = WorldSnapshot::new(delta.target_id, delta.target_tick, delta.timestamp_ms);
497
498 for (id, entity) in &self.entities {
500 result.entities.insert(*id, entity.clone());
501 }
502
503 for ed in &delta.entity_deltas {
505 if let Some(existing) = self.entities.get(&ed.entity_id) {
506 let updated = existing.apply_delta(ed);
507 result.entities.insert(ed.entity_id, updated);
508 }
509 }
510
511 for spawned in &delta.spawned_entities {
513 result.entities.insert(spawned.entity_id, spawned.clone());
514 }
515
516 for &id in &delta.despawned_entities {
518 result.entities.remove(&id);
519 }
520
521 result
522 }
523
524 pub fn merge_from(&mut self, other: &WorldSnapshot) {
526 for (id, entity) in &other.entities {
527 self.entities.insert(*id, entity.clone());
528 }
529 if other.tick > self.tick {
530 self.tick = other.tick;
531 self.id = other.id;
532 self.timestamp_ms = other.timestamp_ms;
533 }
534 }
535}
536
537#[derive(Debug, Clone)]
539pub struct SnapshotDelta {
540 pub baseline_id: SnapshotId,
541 pub target_id: SnapshotId,
542 pub baseline_tick: u64,
543 pub target_tick: u64,
544 pub timestamp_ms: u64,
545 pub entity_deltas: Vec<EntityDelta>,
546 pub spawned_entities: Vec<EntitySnapshot>,
547 pub despawned_entities: Vec<NetEntityId>,
548}
549
550impl SnapshotDelta {
551 pub fn is_empty(&self) -> bool {
552 self.entity_deltas.is_empty()
553 && self.spawned_entities.is_empty()
554 && self.despawned_entities.is_empty()
555 }
556
557 pub fn estimated_size(&self) -> usize {
558 let header = 8 + 8 + 8 + 8 + 8;
559 let deltas: usize = self.entity_deltas.iter().map(|d| d.estimated_size()).sum();
560 let spawns: usize = self.spawned_entities.iter().map(|e| e.total_size()).sum();
561 let despawns = self.despawned_entities.len() * 4;
562 header + deltas + spawns + despawns
563 }
564
565 pub fn delta_count(&self) -> usize {
566 self.entity_deltas.len() + self.spawned_entities.len() + self.despawned_entities.len()
567 }
568}
569
570pub struct SnapshotRingBuffer {
573 buffer: Vec<Option<WorldSnapshot>>,
574 capacity: usize,
575 head: usize,
576 count: usize,
577 latest_id: SnapshotId,
578 id_to_index: HashMap<SnapshotId, usize>,
579 tick_to_id: HashMap<u64, SnapshotId>,
580}
581
582impl SnapshotRingBuffer {
583 pub fn new(capacity: usize) -> Self {
584 let capacity = capacity.max(4);
585 let mut buffer = Vec::with_capacity(capacity);
586 for _ in 0..capacity {
587 buffer.push(None);
588 }
589 Self {
590 buffer,
591 capacity,
592 head: 0,
593 count: 0,
594 latest_id: SnapshotId(0),
595 id_to_index: HashMap::new(),
596 tick_to_id: HashMap::new(),
597 }
598 }
599
600 pub fn push(&mut self, snapshot: WorldSnapshot) {
601 if let Some(ref old) = self.buffer[self.head] {
603 self.id_to_index.remove(&old.id);
604 self.tick_to_id.remove(&old.tick);
605 }
606
607 let id = snapshot.id;
608 let tick = snapshot.tick;
609 self.id_to_index.insert(id, self.head);
610 self.tick_to_id.insert(tick, id);
611 self.buffer[self.head] = Some(snapshot);
612
613 if id.0 > self.latest_id.0 {
614 self.latest_id = id;
615 }
616
617 self.head = (self.head + 1) % self.capacity;
618 if self.count < self.capacity {
619 self.count += 1;
620 }
621 }
622
623 pub fn get(&self, id: SnapshotId) -> Option<&WorldSnapshot> {
624 self.id_to_index.get(&id).and_then(|&idx| self.buffer[idx].as_ref())
625 }
626
627 pub fn get_by_tick(&self, tick: u64) -> Option<&WorldSnapshot> {
628 self.tick_to_id.get(&tick).and_then(|id| self.get(*id))
629 }
630
631 pub fn latest(&self) -> Option<&WorldSnapshot> {
632 if self.count == 0 {
633 return None;
634 }
635 self.get(self.latest_id)
636 }
637
638 pub fn latest_id(&self) -> SnapshotId {
639 self.latest_id
640 }
641
642 pub fn len(&self) -> usize {
643 self.count
644 }
645
646 pub fn is_empty(&self) -> bool {
647 self.count == 0
648 }
649
650 pub fn capacity(&self) -> usize {
651 self.capacity
652 }
653
654 pub fn is_full(&self) -> bool {
655 self.count >= self.capacity
656 }
657
658 pub fn recent(&self, n: usize) -> Vec<&WorldSnapshot> {
660 let n = n.min(self.count);
661 let mut result = Vec::with_capacity(n);
662 let start = if self.head >= n {
663 self.head - n
664 } else {
665 self.capacity - (n - self.head)
666 };
667 for i in 0..n {
668 let idx = (start + i) % self.capacity;
669 if let Some(ref snap) = self.buffer[idx] {
670 result.push(snap);
671 }
672 }
673 result
674 }
675
676 pub fn closest_to_tick(&self, tick: u64) -> Option<&WorldSnapshot> {
678 let mut best: Option<&WorldSnapshot> = None;
679 let mut best_dist = u64::MAX;
680 for entry in &self.buffer {
681 if let Some(ref snap) = entry {
682 let dist = if snap.tick > tick {
683 snap.tick - tick
684 } else {
685 tick - snap.tick
686 };
687 if dist < best_dist {
688 best_dist = dist;
689 best = Some(snap);
690 }
691 }
692 }
693 best
694 }
695
696 pub fn compute_delta(&self, baseline_id: SnapshotId, target_id: SnapshotId) -> Option<SnapshotDelta> {
698 let baseline = self.get(baseline_id)?;
699 let target = self.get(target_id)?;
700 Some(target.delta_from(baseline))
701 }
702
703 pub fn prune_before(&mut self, tick: u64) {
705 for i in 0..self.capacity {
706 if let Some(ref snap) = self.buffer[i] {
707 if snap.tick < tick {
708 let id = snap.id;
709 let t = snap.tick;
710 self.id_to_index.remove(&id);
711 self.tick_to_id.remove(&t);
712 self.buffer[i] = None;
713 if self.count > 0 {
714 self.count -= 1;
715 }
716 }
717 }
718 }
719 }
720
721 pub fn clear(&mut self) {
722 for i in 0..self.capacity {
723 self.buffer[i] = None;
724 }
725 self.id_to_index.clear();
726 self.tick_to_id.clear();
727 self.head = 0;
728 self.count = 0;
729 }
730
731 pub fn stored_ids(&self) -> Vec<SnapshotId> {
733 self.id_to_index.keys().copied().collect()
734 }
735}
736
737#[derive(Debug, Clone)]
739pub struct RelevancyEntry {
740 pub entity_id: NetEntityId,
741 pub priority: f32,
742 pub distance_sq: f32,
743 pub is_relevant: bool,
744 pub last_sent_tick: u64,
745 pub update_frequency: f32,
746 pub accumulated_priority: f32,
747}
748
749impl RelevancyEntry {
750 pub fn new(entity_id: NetEntityId) -> Self {
751 Self {
752 entity_id,
753 priority: 1.0,
754 distance_sq: 0.0,
755 is_relevant: true,
756 last_sent_tick: 0,
757 update_frequency: 1.0,
758 accumulated_priority: 0.0,
759 }
760 }
761}
762
763#[derive(Debug, Clone)]
765pub struct RelevancyRegion {
766 pub center: [f32; 3],
767 pub radius: f32,
768 pub priority_falloff: f32,
769 pub min_priority: f32,
770}
771
772impl RelevancyRegion {
773 pub fn new(center: [f32; 3], radius: f32) -> Self {
774 Self {
775 center,
776 radius,
777 priority_falloff: 1.0,
778 min_priority: 0.0,
779 }
780 }
781
782 pub fn contains(&self, pos: [f32; 3]) -> bool {
783 let dx = pos[0] - self.center[0];
784 let dy = pos[1] - self.center[1];
785 let dz = pos[2] - self.center[2];
786 dx * dx + dy * dy + dz * dz <= self.radius * self.radius
787 }
788
789 pub fn distance_sq(&self, pos: [f32; 3]) -> f32 {
790 let dx = pos[0] - self.center[0];
791 let dy = pos[1] - self.center[1];
792 let dz = pos[2] - self.center[2];
793 dx * dx + dy * dy + dz * dz
794 }
795
796 pub fn priority_at(&self, pos: [f32; 3]) -> f32 {
797 let dist_sq = self.distance_sq(pos);
798 let radius_sq = self.radius * self.radius;
799 if dist_sq >= radius_sq {
800 return self.min_priority;
801 }
802 let t = 1.0 - (dist_sq / radius_sq).sqrt();
803 let p = t.powf(self.priority_falloff);
804 self.min_priority + (1.0 - self.min_priority) * p
805 }
806}
807
808#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
810pub struct ClientId(pub u32);
811
812pub struct RelevancyFilter {
815 client_id: ClientId,
816 region: RelevancyRegion,
817 entries: HashMap<NetEntityId, RelevancyEntry>,
818 max_entities_per_update: usize,
819 bandwidth_budget_bytes: usize,
820 always_relevant: Vec<NetEntityId>,
821 never_relevant: Vec<NetEntityId>,
822 priority_bias: HashMap<u16, f32>,
823 current_tick: u64,
824}
825
826impl RelevancyFilter {
827 pub fn new(client_id: ClientId, region: RelevancyRegion) -> Self {
828 Self {
829 client_id,
830 region,
831 entries: HashMap::new(),
832 max_entities_per_update: 64,
833 bandwidth_budget_bytes: 16384,
834 always_relevant: Vec::new(),
835 never_relevant: Vec::new(),
836 priority_bias: HashMap::new(),
837 current_tick: 0,
838 }
839 }
840
841 pub fn client_id(&self) -> ClientId {
842 self.client_id
843 }
844
845 pub fn set_region(&mut self, region: RelevancyRegion) {
846 self.region = region;
847 }
848
849 pub fn region(&self) -> &RelevancyRegion {
850 &self.region
851 }
852
853 pub fn set_max_entities(&mut self, max: usize) {
854 self.max_entities_per_update = max;
855 }
856
857 pub fn set_bandwidth_budget(&mut self, bytes: usize) {
858 self.bandwidth_budget_bytes = bytes;
859 }
860
861 pub fn add_always_relevant(&mut self, id: NetEntityId) {
862 if !self.always_relevant.contains(&id) {
863 self.always_relevant.push(id);
864 }
865 }
866
867 pub fn remove_always_relevant(&mut self, id: NetEntityId) {
868 self.always_relevant.retain(|&x| x != id);
869 }
870
871 pub fn add_never_relevant(&mut self, id: NetEntityId) {
872 if !self.never_relevant.contains(&id) {
873 self.never_relevant.push(id);
874 }
875 }
876
877 pub fn remove_never_relevant(&mut self, id: NetEntityId) {
878 self.never_relevant.retain(|&x| x != id);
879 }
880
881 pub fn set_component_priority_bias(&mut self, component_type: u16, bias: f32) {
882 self.priority_bias.insert(component_type, bias);
883 }
884
885 pub fn update(&mut self, snapshot: &WorldSnapshot, tick: u64) {
887 self.current_tick = tick;
888
889 self.entries.retain(|id, _| snapshot.entities.contains_key(id));
891
892 for (id, entity) in &snapshot.entities {
893 if self.never_relevant.contains(id) {
895 if let Some(entry) = self.entries.get_mut(id) {
896 entry.is_relevant = false;
897 entry.priority = 0.0;
898 }
899 continue;
900 }
901
902 let entry = self.entries.entry(*id).or_insert_with(|| RelevancyEntry::new(*id));
903 let dist_sq = self.region.distance_sq(entity.position);
904 entry.distance_sq = dist_sq;
905
906 if self.always_relevant.contains(id) {
907 entry.is_relevant = true;
908 entry.priority = 10.0;
909 } else {
910 entry.is_relevant = self.region.contains(entity.position);
911 entry.priority = self.region.priority_at(entity.position);
912 }
913
914 for comp in &entity.components {
916 if let Some(&bias) = self.priority_bias.get(&comp.type_id) {
917 entry.priority *= bias;
918 }
919 }
920
921 let ticks_since_sent = if tick > entry.last_sent_tick {
923 tick - entry.last_sent_tick
924 } else {
925 0
926 };
927 entry.accumulated_priority = entry.priority * (1.0 + ticks_since_sent as f32 * 0.1);
928 }
929 }
930
931 pub fn select_entities(&mut self, snapshot: &WorldSnapshot) -> Vec<NetEntityId> {
934 let mut candidates: Vec<(NetEntityId, f32)> = self.entries.iter()
935 .filter(|(_, entry)| entry.is_relevant)
936 .map(|(&id, entry)| (id, entry.accumulated_priority))
937 .collect();
938
939 candidates.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
941
942 let mut selected = Vec::new();
943 let mut budget_remaining = self.bandwidth_budget_bytes;
944
945 for (id, _priority) in candidates {
946 if selected.len() >= self.max_entities_per_update {
947 break;
948 }
949
950 if let Some(entity) = snapshot.get_entity(id) {
952 let est_size = entity.total_size();
953 if est_size > budget_remaining && !selected.is_empty() {
954 continue;
955 }
956 budget_remaining = budget_remaining.saturating_sub(est_size);
957 selected.push(id);
958
959 if let Some(entry) = self.entries.get_mut(&id) {
961 entry.last_sent_tick = self.current_tick;
962 entry.accumulated_priority = 0.0;
963 }
964 }
965 }
966
967 selected
968 }
969
970 pub fn filter_snapshot(&mut self, snapshot: &WorldSnapshot) -> WorldSnapshot {
972 let selected = self.select_entities(snapshot);
973 let mut filtered = WorldSnapshot::new(snapshot.id, snapshot.tick, snapshot.timestamp_ms);
974 for id in selected {
975 if let Some(entity) = snapshot.get_entity(id) {
976 filtered.add_entity(entity.clone());
977 }
978 }
979 filtered
980 }
981
982 pub fn get_entry(&self, id: NetEntityId) -> Option<&RelevancyEntry> {
984 self.entries.get(&id)
985 }
986
987 pub fn tracked_count(&self) -> usize {
989 self.entries.len()
990 }
991
992 pub fn relevant_count(&self) -> usize {
994 self.entries.values().filter(|e| e.is_relevant).count()
995 }
996
997 pub fn reset_priorities(&mut self) {
999 for entry in self.entries.values_mut() {
1000 entry.accumulated_priority = 0.0;
1001 }
1002 }
1003}
1004
1005pub fn quantize_f32(value: f32, min: f32, max: f32, bits: u32) -> u32 {
1007 let range = max - min;
1008 if range <= f32::EPSILON {
1009 return 0;
1010 }
1011 let normalized = ((value - min) / range).clamp(0.0, 1.0);
1012 let max_val = (1u32 << bits) - 1;
1013 (normalized * max_val as f32) as u32
1014}
1015
1016pub fn dequantize_f32(quantized: u32, min: f32, max: f32, bits: u32) -> f32 {
1018 let max_val = (1u32 << bits) - 1;
1019 if max_val == 0 {
1020 return min;
1021 }
1022 let normalized = quantized as f32 / max_val as f32;
1023 min + normalized * (max - min)
1024}
1025
1026pub fn pack_position(pos: [f32; 3], bounds_min: [f32; 3], bounds_max: [f32; 3], bits_per_axis: u32) -> Vec<u8> {
1028 let qx = quantize_f32(pos[0], bounds_min[0], bounds_max[0], bits_per_axis);
1029 let qy = quantize_f32(pos[1], bounds_min[1], bounds_max[1], bits_per_axis);
1030 let qz = quantize_f32(pos[2], bounds_min[2], bounds_max[2], bits_per_axis);
1031
1032 let mut bytes = Vec::new();
1033 bytes.extend_from_slice(&qx.to_le_bytes());
1034 bytes.extend_from_slice(&qy.to_le_bytes());
1035 bytes.extend_from_slice(&qz.to_le_bytes());
1036 bytes
1037}
1038
1039pub fn unpack_position(data: &[u8], bounds_min: [f32; 3], bounds_max: [f32; 3], bits_per_axis: u32) -> [f32; 3] {
1041 if data.len() < 12 {
1042 return [0.0; 3];
1043 }
1044 let qx = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
1045 let qy = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
1046 let qz = u32::from_le_bytes([data[8], data[9], data[10], data[11]]);
1047
1048 [
1049 dequantize_f32(qx, bounds_min[0], bounds_max[0], bits_per_axis),
1050 dequantize_f32(qy, bounds_min[1], bounds_max[1], bits_per_axis),
1051 dequantize_f32(qz, bounds_min[2], bounds_max[2], bits_per_axis),
1052 ]
1053}
1054
1055pub fn compress_quaternion(q: [f32; 4]) -> [u16; 3] {
1058 let mut largest_idx = 0;
1059 let mut largest_val = q[0].abs();
1060 for i in 1..4 {
1061 if q[i].abs() > largest_val {
1062 largest_val = q[i].abs();
1063 largest_idx = i;
1064 }
1065 }
1066
1067 let sign = if q[largest_idx] < 0.0 { -1.0 } else { 1.0 };
1069
1070 let mut small = Vec::with_capacity(3);
1071 for i in 0..4 {
1072 if i != largest_idx {
1073 let val = q[i] * sign;
1074 let normalized = (val * std::f32::consts::FRAC_1_SQRT_2.recip() + 1.0) * 0.5;
1076 let quantized = (normalized.clamp(0.0, 1.0) * 65535.0) as u16;
1077 small.push(quantized);
1078 }
1079 }
1080
1081 let idx_bits = (largest_idx as u16) << 14;
1083 small[0] = (small[0] & 0x3FFF) | idx_bits;
1084
1085 [small[0], small[1], small[2]]
1086}
1087
1088pub fn decompress_quaternion(packed: [u16; 3]) -> [f32; 4] {
1090 let largest_idx = ((packed[0] >> 14) & 0x03) as usize;
1091 let inv_sqrt2 = std::f32::consts::FRAC_1_SQRT_2;
1092
1093 let a_norm = (packed[0] & 0x3FFF) as f32 / 16383.0;
1094 let b_norm = packed[1] as f32 / 65535.0;
1095 let c_norm = packed[2] as f32 / 65535.0;
1096
1097 let a = (a_norm * 2.0 - 1.0) * inv_sqrt2;
1098 let b = (b_norm * 2.0 - 1.0) * inv_sqrt2;
1099 let c = (c_norm * 2.0 - 1.0) * inv_sqrt2;
1100
1101 let sum_sq = a * a + b * b + c * c;
1102 let largest = (1.0 - sum_sq).max(0.0).sqrt();
1103
1104 let mut result = [0.0f32; 4];
1105 let mut small_idx = 0;
1106 let smalls = [a, b, c];
1107 for i in 0..4 {
1108 if i == largest_idx {
1109 result[i] = largest;
1110 } else {
1111 result[i] = smalls[small_idx];
1112 small_idx += 1;
1113 }
1114 }
1115
1116 let len = (result[0] * result[0] + result[1] * result[1]
1118 + result[2] * result[2] + result[3] * result[3]).sqrt();
1119 if len > f32::EPSILON {
1120 for v in &mut result {
1121 *v /= len;
1122 }
1123 }
1124
1125 result
1126}
1127
1128#[cfg(test)]
1129mod tests {
1130 use super::*;
1131
1132 #[test]
1133 fn test_snapshot_id() {
1134 let id = SnapshotId(5);
1135 assert_eq!(id.next(), SnapshotId(6));
1136 assert_eq!(id.distance(SnapshotId(10)), 5);
1137 }
1138
1139 #[test]
1140 fn test_component_delta_identical() {
1141 let c1 = ComponentData::new(1, vec![1, 2, 3, 4]);
1142 let c2 = ComponentData::new(1, vec![1, 2, 3, 4]);
1143 assert!(c1.delta_against(&c2).is_none());
1144 }
1145
1146 #[test]
1147 fn test_component_delta_changed() {
1148 let c1 = ComponentData::new(1, vec![1, 2, 3, 4]);
1149 let c2 = ComponentData::new(1, vec![1, 2, 5, 4]);
1150 let delta = c1.delta_against(&c2);
1151 assert!(delta.is_some());
1152 let reconstructed = c2.apply_delta(&delta.unwrap());
1153 assert_eq!(reconstructed.data, c1.data);
1154 }
1155
1156 #[test]
1157 fn test_world_snapshot_delta() {
1158 let mut ws1 = WorldSnapshot::new(SnapshotId(1), 1, 100);
1159 let mut e1 = EntitySnapshot::new(NetEntityId(1));
1160 e1.position = [1.0, 2.0, 3.0];
1161 e1.add_component(ComponentData::new(1, vec![10, 20]));
1162 ws1.add_entity(e1);
1163
1164 let mut ws2 = WorldSnapshot::new(SnapshotId(2), 2, 200);
1165 let mut e1b = EntitySnapshot::new(NetEntityId(1));
1166 e1b.position = [4.0, 5.0, 6.0];
1167 e1b.add_component(ComponentData::new(1, vec![10, 20]));
1168 ws2.add_entity(e1b);
1169
1170 let delta = ws2.delta_from(&ws1);
1171 assert_eq!(delta.entity_deltas.len(), 1);
1172 assert!(delta.entity_deltas[0].position.is_some());
1173 }
1174
1175 #[test]
1176 fn test_ring_buffer() {
1177 let mut rb = SnapshotRingBuffer::new(4);
1178 for i in 0..6 {
1179 let snap = WorldSnapshot::new(SnapshotId(i), i, i * 100);
1180 rb.push(snap);
1181 }
1182 assert_eq!(rb.len(), 4);
1183 assert!(rb.get(SnapshotId(0)).is_none());
1184 assert!(rb.get(SnapshotId(1)).is_none());
1185 assert!(rb.get(SnapshotId(2)).is_some());
1186 assert!(rb.get(SnapshotId(5)).is_some());
1187 }
1188
1189 #[test]
1190 fn test_quantize_roundtrip() {
1191 let val = 3.14;
1192 let q = quantize_f32(val, 0.0, 10.0, 16);
1193 let dq = dequantize_f32(q, 0.0, 10.0, 16);
1194 assert!((dq - val).abs() < 0.001);
1195 }
1196
1197 #[test]
1198 fn test_relevancy_region() {
1199 let region = RelevancyRegion::new([0.0, 0.0, 0.0], 100.0);
1200 assert!(region.contains([50.0, 0.0, 0.0]));
1201 assert!(!region.contains([150.0, 0.0, 0.0]));
1202 let p = region.priority_at([0.0, 0.0, 0.0]);
1203 assert!((p - 1.0).abs() < 0.01);
1204 }
1205
1206 #[test]
1207 fn test_rle_roundtrip() {
1208 let data = vec![0, 0, 0, 5, 5, 0, 0, 7];
1209 let encoded = rle_encode(&data);
1210 let decoded = rle_decode(&encoded, data.len());
1211 assert_eq!(decoded, data);
1212 }
1213}