1use std::collections::{BTreeMap, VecDeque};
31
32use crate::EntityId;
33
34#[derive(Debug, Clone, Copy, PartialEq)]
35struct Sample {
36 tick: u64,
37 pos: [f64; 3],
38 components: u64,
41}
42
43#[derive(Debug, Clone)]
44struct Life {
45 spawn_tick: u64,
46 despawn_tick: Option<u64>,
47 samples: VecDeque<Sample>,
48}
49
50#[derive(Debug, Clone, Copy, PartialEq)]
52pub struct Sent {
53 pub id: EntityId,
54 pub q: [i64; 3],
56 pub components_changed: bool,
58}
59
60#[derive(Debug, Clone)]
63pub struct ViewHistory {
64 keep_ticks: u64,
65 lives: BTreeMap<EntityId, Vec<Life>>,
66 last_tick: Option<u64>,
67 pruned_at: u64,
70}
71
72const PRUNE_EVERY: u64 = 8;
74
75impl ViewHistory {
76 pub fn new(keep_ticks: u64) -> Self {
79 Self {
80 keep_ticks,
81 lives: BTreeMap::new(),
82 last_tick: None,
83 pruned_at: 0,
84 }
85 }
86
87 pub fn keep_ticks(&self) -> u64 {
89 self.keep_ticks
90 }
91
92 pub fn last_tick(&self) -> Option<u64> {
94 self.last_tick
95 }
96
97 pub fn clear(&mut self) {
100 self.lives.clear();
101 self.last_tick = None;
102 self.pruned_at = 0;
103 }
104
105 pub fn record(
109 &mut self,
110 tick: u64,
111 precision: f32,
112 full: bool,
113 spawned: &[Sent],
114 updated: &[Sent],
115 despawned: &[EntityId],
116 ) {
117 if self.last_tick.is_some_and(|last| tick < last) {
118 self.clear();
120 }
121 self.last_tick = Some(tick);
122 let p = precision as f64;
123 let pos = |q: [i64; 3]| [q[0] as f64 * p, q[1] as f64 * p, q[2] as f64 * p];
124
125 if full {
126 let present: std::collections::HashSet<EntityId> =
127 spawned.iter().map(|s| s.id).collect();
128 for (id, lives) in self.lives.iter_mut() {
129 if let Some(open) = lives.last_mut().filter(|l| l.despawn_tick.is_none()) {
130 if !present.contains(id) {
131 open.despawn_tick = Some(tick);
132 }
133 }
134 }
135 for s in spawned {
136 match self.open_life(s.id) {
137 Some(life) => Self::push(life, tick, pos(s.q), s.components_changed),
138 None => self.spawn(s.id, tick, pos(s.q)),
139 }
140 }
141 } else {
142 for id in despawned {
143 if let Some(open) = self.open_life(*id) {
144 open.despawn_tick = Some(tick);
145 }
146 }
147 for s in spawned {
148 self.spawn(s.id, tick, pos(s.q));
149 }
150 if updated.windows(2).all(|w| w[0].id < w[1].id) {
151 let mut lives = self.lives.iter_mut().peekable();
154 for s in updated {
155 while lives.next_if(|(id, _)| **id < s.id).is_some() {}
156 if let Some((_, l)) = lives.next_if(|(id, _)| **id == s.id) {
157 if let Some(life) = l.last_mut().filter(|l| l.despawn_tick.is_none()) {
158 Self::push(life, tick, pos(s.q), s.components_changed);
159 }
160 }
161 }
162 } else {
163 for s in updated {
164 if let Some(life) = self.open_life(s.id) {
165 Self::push(life, tick, pos(s.q), s.components_changed);
166 }
167 }
168 }
169 }
170 if tick >= self.pruned_at + PRUNE_EVERY || tick < self.pruned_at {
171 self.prune(tick);
172 self.pruned_at = tick;
173 }
174 }
175
176 pub fn position_at(&self, id: EntityId, render_tick: f64) -> Option<[f64; 3]> {
180 let lives = self.lives.get(&id)?;
181 let life = lives
182 .iter()
183 .find(|l| l.despawn_tick.is_none_or(|d| (d as f64) > render_tick))?;
184 if (life.spawn_tick as f64) > render_tick {
185 return None;
186 }
187 Some(place(&life.samples, render_tick))
188 }
189
190 pub fn entities_near(
193 &self,
194 center: [f64; 3],
195 radius: f64,
196 render_tick: f64,
197 ) -> Vec<(EntityId, [f64; 3])> {
198 let r2 = radius * radius;
199 let mut out = Vec::new();
200 for id in self.lives.keys() {
201 if let Some(p) = self.position_at(*id, render_tick) {
202 let d = [p[0] - center[0], p[1] - center[1], p[2] - center[2]];
203 if d[0] * d[0] + d[1] * d[1] + d[2] * d[2] <= r2 {
204 out.push((*id, p));
205 }
206 }
207 }
208 out
209 }
210
211 pub fn ids(&self) -> impl Iterator<Item = EntityId> + '_ {
213 self.lives.keys().copied()
214 }
215
216 fn open_life(&mut self, id: EntityId) -> Option<&mut Life> {
217 self.lives
218 .get_mut(&id)?
219 .last_mut()
220 .filter(|l| l.despawn_tick.is_none())
221 }
222
223 fn spawn(&mut self, id: EntityId, tick: u64, pos: [f64; 3]) {
224 let lives = self.lives.entry(id).or_default();
225 if let Some(open) = lives.last_mut().filter(|l| l.despawn_tick.is_none()) {
226 open.despawn_tick = Some(tick);
227 }
228 let mut samples = VecDeque::with_capacity(4);
229 samples.push_back(Sample {
230 tick,
231 pos,
232 components: 0,
233 });
234 lives.push(Life {
235 spawn_tick: tick,
236 despawn_tick: None,
237 samples,
238 });
239 }
240
241 fn push(life: &mut Life, tick: u64, pos: [f64; 3], components_changed: bool) {
243 let samples = &mut life.samples;
244 let last = *samples.back().expect("a life has a sample");
245 let moved = last.pos != pos;
246 if !moved && !components_changed {
247 return;
248 }
249 let components = if components_changed {
250 last.components + 1
251 } else {
252 last.components
253 };
254 if last.tick == tick {
255 *samples.back_mut().expect("checked") = Sample {
256 tick,
257 pos,
258 components,
259 };
260 return;
261 }
262 if moved && last.tick + 1 < tick {
263 samples.push_back(Sample {
265 tick: tick - 1,
266 ..last
267 });
268 }
269 samples.push_back(Sample {
270 tick,
271 pos,
272 components,
273 });
274 }
275
276 fn prune(&mut self, tick: u64) {
279 let oldest = tick.saturating_sub(self.keep_ticks);
280 self.lives.retain(|_, lives| {
281 lives.retain(|l| l.despawn_tick.is_none_or(|d| d > oldest));
282 for life in lives.iter_mut() {
283 while life.samples.len() > 1 && life.samples[1].tick <= oldest {
284 life.samples.pop_front();
285 }
286 }
287 !lives.is_empty()
288 });
289 }
290}
291
292fn place(samples: &VecDeque<Sample>, render_tick: f64) -> [f64; 3] {
296 let mut i = samples.len() - 1;
297 while i > 0 && (samples[i].tick as f64) > render_tick {
298 i -= 1;
299 }
300 let from = samples[i];
301 let to = samples.get(i + 1).copied().unwrap_or(from);
302 let t = if to.tick == from.tick {
303 0.0
304 } else {
305 ((render_tick - from.tick as f64) / (to.tick as f64 - from.tick as f64)).clamp(0.0, 1.0)
306 };
307 let d = [
308 to.pos[0] - from.pos[0],
309 to.pos[1] - from.pos[1],
310 to.pos[2] - from.pos[2],
311 ];
312 [
313 from.pos[0] + d[0] * t,
314 from.pos[1] + d[1] * t,
315 from.pos[2] + d[2] * t,
316 ]
317}
318
319#[cfg(test)]
320mod tests {
321 use super::*;
322
323 fn s(id: EntityId, q: [i64; 3]) -> Sent {
324 Sent {
325 id,
326 q,
327 components_changed: false,
328 }
329 }
330
331 #[test]
332 fn an_entity_is_placed_between_samples_and_held_after_the_last() {
333 let mut v = ViewHistory::new(20);
334 v.record(1, 1.0, true, &[s(7, [0, 0, 0])], &[], &[]);
335 v.record(2, 1.0, false, &[], &[s(7, [10, 0, 0])], &[]);
336 v.record(3, 1.0, false, &[], &[s(7, [30, 0, 0])], &[]);
337 assert_eq!(v.position_at(7, 1.0), Some([0.0, 0.0, 0.0]));
338 assert_eq!(v.position_at(7, 1.5), Some([5.0, 0.0, 0.0]));
339 assert_eq!(v.position_at(7, 2.25), Some([15.0, 0.0, 0.0]));
340 assert_eq!(v.position_at(7, 9.0), Some([30.0, 0.0, 0.0]));
341 assert_eq!(v.position_at(7, 0.5), None);
343 assert_eq!(v.position_at(8, 2.0), None);
344 }
345
346 #[test]
347 fn a_quiet_entity_stands_still_until_the_tick_before_it_moves() {
348 let mut v = ViewHistory::new(20);
349 v.record(10, 0.5, false, &[s(1, [0, 0, 0])], &[], &[]);
350 v.record(15, 0.5, false, &[], &[s(1, [20, 0, 0])], &[]);
352 assert_eq!(v.position_at(1, 13.0), Some([0.0, 0.0, 0.0]));
353 assert_eq!(v.position_at(1, 14.0), Some([0.0, 0.0, 0.0]));
354 assert_eq!(v.position_at(1, 14.5), Some([5.0, 0.0, 0.0]));
355 assert_eq!(v.position_at(1, 15.0), Some([10.0, 0.0, 0.0]));
356 }
357
358 #[test]
359 fn a_despawn_ends_the_life_and_a_respawn_starts_another() {
360 let mut v = ViewHistory::new(20);
361 v.record(1, 1.0, false, &[s(4, [1, 1, 1])], &[], &[]);
362 v.record(3, 1.0, false, &[], &[], &[4]);
363 v.record(5, 1.0, false, &[s(4, [9, 9, 9])], &[], &[]);
364 assert_eq!(v.position_at(4, 2.9), Some([1.0, 1.0, 1.0]));
365 assert_eq!(v.position_at(4, 3.0), None);
366 assert_eq!(v.position_at(4, 4.0), None);
367 assert_eq!(v.position_at(4, 5.0), Some([9.0, 9.0, 9.0]));
368 }
369
370 #[test]
371 fn a_full_frame_ends_lives_it_does_not_list() {
372 let mut v = ViewHistory::new(20);
373 v.record(1, 1.0, true, &[s(1, [0, 0, 0]), s(2, [5, 0, 0])], &[], &[]);
374 v.record(4, 1.0, true, &[s(1, [3, 0, 0])], &[], &[]);
375 assert_eq!(v.position_at(2, 3.5), Some([5.0, 0.0, 0.0]));
376 assert_eq!(v.position_at(2, 4.0), None);
377 assert_eq!(v.position_at(1, 3.0), Some([0.0, 0.0, 0.0]));
379 assert_eq!(v.position_at(1, 4.0), Some([3.0, 0.0, 0.0]));
380 }
381
382 #[test]
383 fn a_component_change_without_a_move_adds_a_sample() {
384 let mut v = ViewHistory::new(20);
385 v.record(1, 1.0, false, &[s(1, [0, 0, 0])], &[], &[]);
386 let hp = Sent {
387 components_changed: true,
388 ..s(1, [0, 0, 0])
389 };
390 v.record(5, 1.0, false, &[], &[hp], &[]);
391 v.record(6, 1.0, false, &[], &[s(1, [10, 0, 0])], &[]);
392 assert_eq!(v.position_at(1, 5.0), Some([0.0, 0.0, 0.0]));
394 assert_eq!(v.position_at(1, 5.5), Some([5.0, 0.0, 0.0]));
395 }
396
397 #[test]
398 fn old_samples_and_lives_are_pruned() {
399 let mut v = ViewHistory::new(5);
400 v.record(1, 1.0, false, &[s(1, [0, 0, 0]), s(2, [0, 0, 0])], &[], &[]);
401 v.record(2, 1.0, false, &[], &[], &[2]);
402 for t in 3..=30 {
403 v.record(t, 1.0, false, &[], &[s(1, [t as i64, 0, 0])], &[]);
404 }
405 assert!(!v.lives.contains_key(&2), "an ended life long gone");
406 let samples = &v.lives[&1][0].samples;
407 assert!(
409 samples.len() <= 5 + PRUNE_EVERY as usize + 2,
410 "{} samples",
411 samples.len()
412 );
413 assert_eq!(v.position_at(1, 25.5), Some([25.5, 0.0, 0.0]));
415 }
416
417 #[test]
418 fn updates_in_any_order_land_on_their_entities() {
419 let mut v = ViewHistory::new(20);
420 v.record(
421 1,
422 1.0,
423 false,
424 &[s(1, [0, 0, 0]), s(2, [0, 0, 0]), s(3, [0, 0, 0])],
425 &[],
426 &[],
427 );
428 v.record(2, 1.0, false, &[], &[s(3, [3, 0, 0]), s(1, [1, 0, 0])], &[]);
429 v.record(3, 1.0, false, &[], &[s(1, [2, 0, 0]), s(3, [6, 0, 0])], &[]);
430 assert_eq!(v.position_at(1, 3.0), Some([2.0, 0.0, 0.0]));
431 assert_eq!(v.position_at(2, 3.0), Some([0.0, 0.0, 0.0]));
432 assert_eq!(v.position_at(3, 2.0), Some([3.0, 0.0, 0.0]));
433 assert_eq!(v.position_at(3, 3.0), Some([6.0, 0.0, 0.0]));
434 }
435
436 #[test]
437 fn near_finds_the_entities_within_the_radius_at_the_tick() {
438 let mut v = ViewHistory::new(20);
439 v.record(
440 1,
441 1.0,
442 false,
443 &[s(1, [0, 0, 0]), s(2, [100, 0, 0])],
444 &[],
445 &[],
446 );
447 v.record(2, 1.0, false, &[], &[s(2, [10, 0, 0])], &[]);
448 assert_eq!(
449 v.entities_near([0.0, 0.0, 0.0], 20.0, 1.0),
450 vec![(1, [0.0, 0.0, 0.0])]
451 );
452 assert_eq!(
453 v.entities_near([0.0, 0.0, 0.0], 20.0, 2.0),
454 vec![(1, [0.0, 0.0, 0.0]), (2, [10.0, 0.0, 0.0])]
455 );
456 }
457}