1use crate::core::{get_length_units_per_meter, set_length_units_per_meter};
7use crate::id::BodyId;
8use crate::math_functions::{Aabb, Vec3};
9use crate::recording::dispatch::{dispatch_one, RecReader};
10use crate::recording::ops::RecOp;
11use crate::recording::registry::{GeometryKind, RegistrySlot};
12use crate::recording::session::{RecHeader, RecTag, REC_MAGIC, REC_VERSION_MAJOR};
13use crate::recording::snapshot::deserialize_into_shell;
14use crate::types::default_world_def;
15use crate::world::World;
16use std::collections::HashMap;
17
18const KEYFRAME_INTERVAL_DEFAULT: i32 = 16;
19
20pub struct RecPlayer {
22 pub data: Vec<u8>,
23 pub header_end: i32,
24 pub registry_end: i32,
25 pub length_scale: f32,
26 pub previous_length_scale: f32,
27 pub frame: i32,
28 pub frame_count: i32,
29 pub recorded_dt: f32,
30 pub recorded_sub_step_count: i32,
31 pub bounds: Aabb,
32 pub at_end: bool,
33 pub at_pre_step: bool,
34 pub diverge_frame: i32,
35 pub body_ids: Vec<BodyId>,
36 pub frame0_body_ids: Vec<BodyId>,
37 pub keyframe_interval: i32,
38 pub keyframe_min_interval: i32,
39 pub keyframe_budget: usize,
40 pub keyframe_bytes: usize,
41 pub last_keyframe_frame: i32,
42 pub world: World,
43 pub frame_queries: Vec<crate::recording::query_replay::FrameQuery>,
44 reader_cursor: i32,
46 reader_ok: bool,
47 reader_diverged: bool,
48 reader_slots: Vec<RegistrySlot>,
49 reader_tags: Vec<RecTag>,
50 reader_pending_query_key: u64,
51 frame0_image_start: usize,
52 frame0_image_size: usize,
53}
54
55impl RecPlayer {
56 pub fn create(data: &[u8], _worker_count: i32) -> Option<Box<Self>> {
58 let hdr = RecHeader::from_bytes(data)?;
59 if hdr.magic != REC_MAGIC {
60 eprintln!("b3RecPlayer_Create: bad magic 0x{:08X}", hdr.magic);
61 return None;
62 }
63 if hdr.version_major != REC_VERSION_MAJOR {
64 eprintln!(
65 "b3RecPlayer_Create: version mismatch {}.{} vs {}.x",
66 hdr.version_major, hdr.version_minor, REC_VERSION_MAJOR
67 );
68 return None;
69 }
70 if hdr.pointer_width != std::mem::size_of::<*const ()>() as u8 {
71 eprintln!(
72 "b3RecPlayer_Create: pointer width mismatch {} vs {}",
73 hdr.pointer_width,
74 std::mem::size_of::<*const ()>()
75 );
76 return None;
77 }
78 if hdr.big_endian != 0 {
79 eprintln!("b3RecPlayer_Create: big-endian recording not supported");
80 return None;
81 }
82 if hdr.snapshot_size == 0 {
83 eprintln!("b3RecPlayer_Create: missing snapshot seed");
84 return None;
85 }
86
87 let header_end64 = 48u64 + hdr.snapshot_size;
88 let registry_end64 = if hdr.registry_offset != 0 {
89 hdr.registry_offset
90 } else {
91 data.len() as u64
92 };
93 if header_end64 < 48 || header_end64 > registry_end64 || registry_end64 > data.len() as u64
94 {
95 eprintln!("b3RecPlayer_Create: corrupt offsets");
96 return None;
97 }
98 let header_end = header_end64 as i32;
99 let registry_end = registry_end64 as i32;
100
101 let previous_length_scale = get_length_units_per_meter();
102 if hdr.length_scale > 0.0 {
103 set_length_units_per_meter(hdr.length_scale);
104 }
105
106 let copy = data.to_vec();
107 let snap_start = 48usize;
108 let snap_size = hdr.snapshot_size as usize;
109
110 let (mut slots, tags) =
111 load_registry_block(©, hdr.registry_offset as usize, copy.len());
112
113 let mut world = World::new(&default_world_def());
114 if !deserialize_into_shell(
115 ©[snap_start..snap_start + snap_size],
116 &mut world,
117 &mut slots,
118 ) {
119 eprintln!("b3RecPlayer_Create: snapshot deserialization failed");
120 set_length_units_per_meter(previous_length_scale);
121 return None;
122 }
123
124 let mut player = Box::new(Self {
125 data: copy,
126 header_end,
127 registry_end,
128 length_scale: hdr.length_scale,
129 previous_length_scale,
130 frame: 0,
131 frame_count: 0,
132 recorded_dt: 0.0,
133 recorded_sub_step_count: 0,
134 bounds: Aabb::default(),
135 at_end: false,
136 at_pre_step: false,
137 diverge_frame: -1,
138 body_ids: Vec::new(),
139 frame0_body_ids: Vec::new(),
140 keyframe_interval: KEYFRAME_INTERVAL_DEFAULT,
141 keyframe_min_interval: KEYFRAME_INTERVAL_DEFAULT,
142 keyframe_budget: 0,
143 keyframe_bytes: 0,
144 last_keyframe_frame: 0,
145 world,
146 frame_queries: Vec::new(),
147 reader_cursor: header_end,
148 reader_ok: true,
149 reader_diverged: false,
150 reader_slots: slots,
151 reader_tags: tags,
152 reader_pending_query_key: 0,
153 frame0_image_start: snap_start,
154 frame0_image_size: snap_size,
155 });
156
157 player.scan_file();
158 player.seed_frame0_body_ids();
159 Some(player)
160 }
161
162 fn with_reader<R>(&mut self, f: impl FnOnce(&mut RecReader<'_>) -> R) -> R {
163 let data_ptr = self.data.as_ptr();
164 let data_len = self.data.len();
165 let owner = self as *mut RecPlayer;
166 let mut rdr = RecReader {
167 data: unsafe { std::slice::from_raw_parts(data_ptr, data_len) },
169 size: self.registry_end,
170 cursor: self.reader_cursor,
171 ok: self.reader_ok,
172 diverged: self.reader_diverged,
173 world: std::ptr::null_mut(),
174 owner: Some(owner),
175 slots: std::mem::take(&mut self.reader_slots),
176 tags: std::mem::take(&mut self.reader_tags),
177 pending_query_key: self.reader_pending_query_key,
178 pending_body_create: None,
179 pending_body_destroy: None,
180 };
181 rdr.world = &mut self.world as *mut World;
182 let result = f(&mut rdr);
183 let created = rdr.pending_body_create.take();
184 let destroyed = rdr.pending_body_destroy.take();
185 self.reader_cursor = rdr.cursor;
186 self.reader_ok = rdr.ok;
187 self.reader_diverged = rdr.diverged;
188 self.reader_slots = rdr.slots;
189 self.reader_tags = rdr.tags;
190 self.reader_pending_query_key = rdr.pending_query_key;
191 if let Some(id) = created {
192 self.track_body_create(id);
193 }
194 if let Some(id) = destroyed {
195 self.track_body_destroy(id);
196 }
197 result
198 }
199
200 fn scan_file(&mut self) {
202 let mut cursor = self.header_end as usize;
203 let end = self.registry_end as usize;
204 let data = &self.data;
205 let mut first_step = true;
206 while cursor + 4 <= end {
207 let opcode = data[cursor];
208 let payload = data[cursor + 1] as u32
209 | ((data[cursor + 2] as u32) << 8)
210 | ((data[cursor + 3] as u32) << 16);
211 cursor += 4;
212 if cursor + payload as usize > end {
213 break;
214 }
215 if opcode == RecOp::Step as u8 {
216 self.frame_count += 1;
217 if first_step && payload >= 4 + 4 {
218 let base = cursor + 4; if base + 8 <= cursor + payload as usize {
221 self.recorded_dt =
222 f32::from_le_bytes(data[base..base + 4].try_into().unwrap());
223 self.recorded_sub_step_count =
224 i32::from_le_bytes(data[base + 4..base + 8].try_into().unwrap());
225 }
226 first_step = false;
227 }
228 } else if opcode == RecOp::RecordingBounds as u8 && payload >= 24 {
229 let b = cursor;
231 self.bounds = Aabb {
232 lower_bound: Vec3 {
233 x: f32::from_le_bytes(data[b..b + 4].try_into().unwrap()),
234 y: f32::from_le_bytes(data[b + 4..b + 8].try_into().unwrap()),
235 z: f32::from_le_bytes(data[b + 8..b + 12].try_into().unwrap()),
236 },
237 upper_bound: Vec3 {
238 x: f32::from_le_bytes(data[b + 12..b + 16].try_into().unwrap()),
239 y: f32::from_le_bytes(data[b + 16..b + 20].try_into().unwrap()),
240 z: f32::from_le_bytes(data[b + 20..b + 24].try_into().unwrap()),
241 },
242 };
243 }
244 if opcode == RecOp::DestroyWorld as u8 {
245 break;
246 }
247 cursor += payload as usize;
248 }
249 }
250
251 fn seed_frame0_body_ids(&mut self) {
252 self.body_ids.clear();
253 for (i, body) in self.world.bodies.iter().enumerate() {
254 if body.id == i as i32 && body.set_index != crate::core::NULL_INDEX {
255 let id = BodyId {
256 index1: i as i32 + 1,
257 world0: self.world.world_id,
258 generation: body.generation,
259 };
260 self.body_ids.push(id);
261 }
262 }
263 self.frame0_body_ids = self.body_ids.clone();
264 }
265
266 pub fn track_body_create(&mut self, id: BodyId) {
267 self.body_ids.push(id);
268 }
269
270 pub fn track_body_destroy(&mut self, id: BodyId) {
271 for slot in &mut self.body_ids {
272 if slot.index1 == id.index1 && slot.generation == id.generation {
273 *slot = BodyId::default();
274 break;
275 }
276 }
277 }
278
279 pub fn step_frame(&mut self) -> bool {
281 self.at_pre_step = false;
282 self.frame_queries.clear();
283 if self.at_end {
284 return false;
285 }
286
287 let mut stepped = false;
288 loop {
289 if self.reader_cursor >= self.registry_end || !self.reader_ok {
290 self.at_end = true;
291 return stepped;
292 }
293
294 if stepped {
295 let next = self.data[self.reader_cursor as usize];
296 if next != RecOp::StateHash as u8 {
297 return true;
298 }
299 }
300
301 let op = self.with_reader(dispatch_one);
302 if op < 0 {
303 self.at_end = true;
304 return stepped;
305 }
306 if op == RecOp::DestroyWorld as i32 {
307 self.at_end = true;
308 return stepped;
309 }
310 if op == RecOp::Step as i32 {
311 self.frame += 1;
312 stepped = true;
313 } else if op == RecOp::StateHash as i32 {
314 if self.diverge_frame < 0 && self.reader_diverged {
315 self.diverge_frame = self.frame;
316 }
317 }
318 }
319 }
320
321 pub fn sub_step_frame(&mut self) {
323 if self.at_end {
324 return;
325 }
326
327 if !self.at_pre_step {
328 self.frame_queries.clear();
329 }
330
331 let mut stepped = false;
332 let mut have_create_body = false;
333 loop {
334 if self.reader_cursor >= self.registry_end || !self.reader_ok {
335 self.at_end = true;
336 self.at_pre_step = false;
337 return;
338 }
339
340 let current = self.data[self.reader_cursor as usize];
341 if stepped && current != RecOp::StateHash as u8 {
342 return;
343 }
344
345 if !self.at_pre_step && have_create_body && current == RecOp::Step as u8 {
346 self.at_pre_step = true;
347 return;
348 }
349
350 let op = self.with_reader(dispatch_one);
351 if op < 0 {
352 self.at_end = true;
353 self.at_pre_step = false;
354 return;
355 }
356 if op == RecOp::DestroyWorld as i32 {
357 self.at_end = true;
358 self.at_pre_step = false;
359 return;
360 }
361 if op == RecOp::CreateBody as i32 {
362 have_create_body = true;
363 }
364 if op == RecOp::Step as i32 {
365 self.frame += 1;
366 stepped = true;
367 self.at_pre_step = false;
368 } else if op == RecOp::StateHash as i32 {
369 if self.diverge_frame < 0 && self.reader_diverged {
370 self.diverge_frame = self.frame;
371 }
372 }
373 }
374 }
375
376 pub fn restart(&mut self) {
378 let snap = self.data
379 [self.frame0_image_start..self.frame0_image_start + self.frame0_image_size]
380 .to_vec();
381 let mut slots = self.reader_slots.clone();
382 for s in &mut slots {
384 s.live_compound = None;
385 }
386 if !deserialize_into_shell(&snap, &mut self.world, &mut slots) {
387 self.reader_ok = false;
388 return;
389 }
390 self.reader_slots = slots;
391 self.reader_cursor = self.header_end;
392 self.reader_ok = true;
393 self.reader_diverged = false;
394 self.reader_pending_query_key = 0;
395 self.frame = 0;
396 self.at_end = false;
397 self.at_pre_step = false;
398 self.diverge_frame = -1;
399 self.body_ids = self.frame0_body_ids.clone();
400 self.last_keyframe_frame = 0;
401 }
402
403 pub fn seek_frame(&mut self, target_frame: i32) {
405 if target_frame <= self.frame {
406 self.restart();
407 }
408 while self.frame < target_frame && self.step_frame() {}
409 }
410
411 pub fn has_diverged(&self) -> bool {
412 self.reader_diverged
413 }
414
415 pub fn get_diverge_frame(&self) -> i32 {
416 self.diverge_frame
417 }
418
419 pub fn get_frame(&self) -> i32 {
420 self.frame
421 }
422
423 pub fn get_frame_count(&self) -> i32 {
424 self.frame_count
425 }
426
427 pub fn is_at_end(&self) -> bool {
428 self.at_end
429 }
430
431 pub fn is_at_pre_step(&self) -> bool {
432 self.at_pre_step
433 }
434
435 pub fn get_body_count(&self) -> i32 {
436 self.body_ids.len() as i32
437 }
438
439 pub fn get_body_id(&self, index: i32) -> BodyId {
440 if index < 0 || index as usize >= self.body_ids.len() {
441 return BodyId::default();
442 }
443 self.body_ids[index as usize]
444 }
445
446 pub fn world(&self) -> &World {
447 &self.world
448 }
449
450 pub fn world_mut(&mut self) -> &mut World {
451 &mut self.world
452 }
453
454 pub fn get_info(&self) -> RecPlayerInfo {
456 RecPlayerInfo {
457 frame_count: self.frame_count,
458 time_step: self.recorded_dt,
459 sub_step_count: self.recorded_sub_step_count,
460 bounds: self.bounds,
461 }
462 }
463
464 pub fn set_keyframe_policy(&mut self, budget_bytes: usize, min_interval_frames: i32) {
466 self.keyframe_budget = budget_bytes;
467 self.keyframe_min_interval = min_interval_frames.max(1);
468 self.keyframe_interval = self.keyframe_min_interval;
469 self.keyframe_bytes = 0;
470 }
471
472 pub fn get_keyframe_min_interval(&self) -> i32 {
473 self.keyframe_min_interval
474 }
475
476 pub fn get_keyframe_interval(&self) -> i32 {
477 self.keyframe_interval
478 }
479
480 pub fn get_keyframe_budget(&self) -> usize {
481 self.keyframe_budget
482 }
483
484 pub fn get_keyframe_bytes(&self) -> usize {
485 self.keyframe_bytes
486 }
487
488 pub fn get_frame_query_count(&self) -> i32 {
489 self.frame_queries.len() as i32
490 }
491
492 pub fn get_frame_query(
493 &self,
494 index: i32,
495 ) -> Option<&crate::recording::query_replay::FrameQuery> {
496 self.frame_queries.get(index as usize)
497 }
498
499 pub fn resolve_tag(&self, key: u64) -> Option<&crate::recording::session::RecTag> {
500 self.reader_tags.iter().find(|t| t.key == key)
501 }
502}
503
504#[derive(Debug, Clone, Copy)]
506pub struct RecPlayerInfo {
507 pub frame_count: i32,
508 pub time_step: f32,
509 pub sub_step_count: i32,
510 pub bounds: Aabb,
511}
512
513impl Drop for RecPlayer {
514 fn drop(&mut self) {
515 set_length_units_per_meter(self.previous_length_scale);
516 }
517}
518
519pub fn validate_replay(data: &[u8], worker_count: i32) -> bool {
521 let Some(mut player) = RecPlayer::create(data, worker_count) else {
522 return false;
523 };
524 while player.step_frame() {
525 if player.reader_diverged {
526 break;
527 }
528 }
529 player.reader_ok && !player.reader_diverged
530}
531
532fn load_registry_block(data: &[u8], offset: usize, end: usize) -> (Vec<RegistrySlot>, Vec<RecTag>) {
533 if offset == 0 || offset + 4 > end {
534 return (Vec::new(), Vec::new());
535 }
536 let mut cursor = offset;
537 let entry_count = u32::from_le_bytes(data[cursor..cursor + 4].try_into().unwrap()) as usize;
538 cursor += 4;
539 let mut slots = Vec::with_capacity(entry_count);
540 for _ in 0..entry_count {
541 if cursor + 5 > end {
542 break;
543 }
544 let kind = match data[cursor] {
545 0 => GeometryKind::Hull,
546 1 => GeometryKind::Mesh,
547 2 => GeometryKind::HeightField,
548 _ => GeometryKind::Compound,
549 };
550 cursor += 1;
551 let byte_count = u32::from_le_bytes(data[cursor..cursor + 4].try_into().unwrap()) as usize;
552 cursor += 4;
553 if cursor + byte_count > end {
554 break;
555 }
556 let bytes = data[cursor..cursor + byte_count].to_vec();
557 cursor += byte_count;
558 slots.push(RegistrySlot {
559 kind,
560 bytes,
561 live_compound: None,
562 });
563 }
564
565 let mut tags = Vec::new();
566 if cursor + 4 <= end {
567 let tag_count = u32::from_le_bytes(data[cursor..cursor + 4].try_into().unwrap()) as usize;
568 cursor += 4;
569 for _ in 0..tag_count {
570 if cursor + 16 > end {
571 break;
572 }
573 let key = u64::from_le_bytes(data[cursor..cursor + 8].try_into().unwrap());
574 cursor += 8;
575 let id = u64::from_le_bytes(data[cursor..cursor + 8].try_into().unwrap());
576 cursor += 8;
577 if cursor + 2 > end {
578 break;
579 }
580 let len = u16::from_le_bytes(data[cursor..cursor + 2].try_into().unwrap());
581 cursor += 2;
582 let name = if len == 0xFFFF {
583 String::new()
584 } else {
585 let n = len as usize;
586 if cursor + n > end {
587 break;
588 }
589 let s = String::from_utf8_lossy(&data[cursor..cursor + n]).into_owned();
590 cursor += n;
591 s
592 };
593 tags.push(RecTag { key, id, name });
594 }
595 }
596 let _ = cursor;
597 let _map: HashMap<u64, u32> = tags
598 .iter()
599 .enumerate()
600 .map(|(i, t)| (t.key, i as u32))
601 .collect();
602 let _ = _map;
603 (slots, tags)
604}