1use crate::constants::BODY_NAME_LENGTH;
8use crate::core::get_length_units_per_meter;
9use crate::id::WorldId;
10use crate::math_functions::{aabb_union, Aabb};
11use crate::recording::buffer::RecBuffer;
12use crate::recording::hash::hash_world_state;
13use crate::recording::registry::GeometryRegistry;
14use crate::recording::snapshot::serialize_world;
15use crate::world::World;
16use std::collections::HashMap;
17
18pub const REC_MAGIC: u32 = 0x4352_3342;
20pub const REC_VERSION_MAJOR: u16 = 3;
22pub const REC_VERSION_MINOR: u16 = 2;
24
25#[repr(C)]
27#[derive(Debug, Clone, Copy)]
28pub struct RecHeader {
29 pub magic: u32,
30 pub version_major: u16,
31 pub version_minor: u16,
32 pub pointer_width: u8,
33 pub big_endian: u8,
34 pub validation_enabled: u8,
35 pub reserved: u8,
36 pub length_scale: f32,
37 pub reserved2: u32,
38 pub reserved3: u32,
39 pub snapshot_size: u64,
40 pub registry_offset: u64,
41 pub registry_byte_count: u64,
42}
43
44const _: () = assert!(std::mem::size_of::<RecHeader>() == 48);
45
46impl RecHeader {
47 pub fn to_bytes(self) -> [u8; 48] {
48 let mut b = [0u8; 48];
49 b[0..4].copy_from_slice(&self.magic.to_le_bytes());
50 b[4..6].copy_from_slice(&self.version_major.to_le_bytes());
51 b[6..8].copy_from_slice(&self.version_minor.to_le_bytes());
52 b[8] = self.pointer_width;
53 b[9] = self.big_endian;
54 b[10] = self.validation_enabled;
55 b[11] = self.reserved;
56 b[12..16].copy_from_slice(&self.length_scale.to_le_bytes());
57 b[16..20].copy_from_slice(&self.reserved2.to_le_bytes());
58 b[20..24].copy_from_slice(&self.reserved3.to_le_bytes());
59 b[24..32].copy_from_slice(&self.snapshot_size.to_le_bytes());
60 b[32..40].copy_from_slice(&self.registry_offset.to_le_bytes());
61 b[40..48].copy_from_slice(&self.registry_byte_count.to_le_bytes());
62 b
63 }
64
65 pub fn from_bytes(data: &[u8]) -> Option<Self> {
66 if data.len() < 48 {
67 return None;
68 }
69 Some(Self {
70 magic: u32::from_le_bytes(data[0..4].try_into().ok()?),
71 version_major: u16::from_le_bytes(data[4..6].try_into().ok()?),
72 version_minor: u16::from_le_bytes(data[6..8].try_into().ok()?),
73 pointer_width: data[8],
74 big_endian: data[9],
75 validation_enabled: data[10],
76 reserved: data[11],
77 length_scale: f32::from_le_bytes(data[12..16].try_into().ok()?),
78 reserved2: u32::from_le_bytes(data[16..20].try_into().ok()?),
79 reserved3: u32::from_le_bytes(data[20..24].try_into().ok()?),
80 snapshot_size: u64::from_le_bytes(data[24..32].try_into().ok()?),
81 registry_offset: u64::from_le_bytes(data[32..40].try_into().ok()?),
82 registry_byte_count: u64::from_le_bytes(data[40..48].try_into().ok()?),
83 })
84 }
85}
86
87#[derive(Debug, Clone)]
89pub struct RecTag {
90 pub key: u64,
91 pub id: u64,
92 pub name: String,
93}
94
95#[derive(Debug)]
97pub struct Recording {
98 pub buffer: RecBuffer,
99 pub record_start: i32,
101 pub registry: GeometryRegistry,
102 pub tags: Vec<RecTag>,
103 tag_map: HashMap<u64, u32>,
104 pub accumulated_bounds: Aabb,
105 pub have_bounds: bool,
106}
107
108impl Recording {
109 pub fn new(byte_capacity: i32) -> Self {
111 let init_cap = if byte_capacity > 0 {
112 byte_capacity as usize
113 } else {
114 65536
115 };
116 Self {
117 buffer: RecBuffer::with_capacity(init_cap),
118 record_start: 0,
119 registry: GeometryRegistry::new(),
120 tags: Vec::new(),
121 tag_map: HashMap::new(),
122 accumulated_bounds: Aabb::default(),
123 have_bounds: false,
124 }
125 }
126
127 pub fn data(&self) -> &[u8] {
128 &self.buffer.data
129 }
130
131 pub fn size(&self) -> i32 {
132 self.buffer.size()
133 }
134
135 pub fn begin_record(&mut self, opcode: u8) {
137 self.buffer.append_u8(opcode);
138 self.record_start = self.buffer.size();
139 self.buffer.append(&[0, 0, 0]);
140 }
141
142 pub fn end_record(&mut self) {
144 let payload_size = self.buffer.size() - self.record_start - 3;
145 debug_assert!(payload_size >= 0 && payload_size < (1 << 24));
146 let p = self.record_start as usize;
147 let sz = payload_size as u32;
148 self.buffer.data[p] = sz as u8;
149 self.buffer.data[p + 1] = (sz >> 8) as u8;
150 self.buffer.data[p + 2] = (sz >> 16) as u8;
151 }
152
153 pub fn commit_record(&mut self, opcode: u8, payload: &[u8]) {
155 debug_assert!(payload.len() < (1 << 24));
156 self.buffer.append_u8(opcode);
157 let sz = payload.len() as u32;
158 self.buffer
159 .append(&[sz as u8, (sz >> 8) as u8, (sz >> 16) as u8]);
160 self.buffer.append(payload);
161 }
162
163 pub fn accumulate_bounds(&mut self, bounds: Aabb) {
165 self.accumulated_bounds = if self.have_bounds {
166 aabb_union(self.accumulated_bounds, bounds)
167 } else {
168 bounds
169 };
170 self.have_bounds = true;
171 }
172
173 pub fn hash_query_tag(id: u64, name: &str) -> u64 {
175 use crate::recording::hash::{SNAP_FNV_INIT, SNAP_FNV_PRIME};
176 let mut h = SNAP_FNV_INIT;
177 for i in 0..8 {
178 h = (h ^ ((id >> (8 * i)) & 0xFF)).wrapping_mul(SNAP_FNV_PRIME);
179 }
180 for b in name.bytes() {
181 h = (h ^ (b as u64)).wrapping_mul(SNAP_FNV_PRIME);
182 }
183 if h != 0 {
184 h
185 } else {
186 1
187 }
188 }
189
190 pub fn intern_tag(&mut self, key: u64, id: u64, name: &str) {
192 if self.tag_map.contains_key(&key) {
193 return;
194 }
195 let index = self.tags.len() as u32;
196 let mut clamped = String::new();
197 for (i, ch) in name.chars().enumerate() {
198 if i >= BODY_NAME_LENGTH {
199 break;
200 }
201 clamped.push(ch);
202 }
203 self.tags.push(RecTag {
204 key,
205 id,
206 name: clamped,
207 });
208 self.tag_map.insert(key, index);
209 }
210
211 pub fn write_registry(&mut self) {
213 self.buffer.append_u32(self.registry.entries.len() as u32);
214 for e in &self.registry.entries {
215 self.buffer.append_u8(e.kind as u8);
216 self.buffer.append_u32(e.bytes.len() as u32);
217 self.buffer.append(&e.bytes);
218 }
219 self.buffer.append_u32(self.tags.len() as u32);
220 for t in &self.tags {
221 self.buffer.append_u64(t.key);
222 self.buffer.append_u64(t.id);
223 self.buffer.append_str(&t.name);
224 }
225 }
226
227 fn reset_for_session(&mut self) {
228 self.buffer.data.clear();
229 self.record_start = 0;
230 self.have_bounds = false;
231 self.registry = GeometryRegistry::new();
232 self.tags.clear();
233 self.tag_map.clear();
234 }
235}
236
237pub fn world_public_id(world: &World) -> WorldId {
239 WorldId {
240 index1: world.world_id.wrapping_add(1),
241 generation: world.generation,
242 }
243}
244
245pub fn start_recording(world: &mut World, recording: &mut Recording) {
248 recording.reset_for_session();
249
250 let mut hdr = RecHeader {
251 magic: REC_MAGIC,
252 version_major: REC_VERSION_MAJOR,
253 version_minor: REC_VERSION_MINOR,
254 pointer_width: std::mem::size_of::<*const ()>() as u8,
255 big_endian: 0,
256 validation_enabled: if cfg!(debug_assertions) { 1 } else { 0 },
257 reserved: 0,
258 length_scale: get_length_units_per_meter(),
259 reserved2: 0,
260 reserved3: 0,
261 snapshot_size: 0,
262 registry_offset: 0,
263 registry_byte_count: 0,
264 };
265
266 world.recording = Some(recording as *mut Recording);
268
269 let mut snap_buf = RecBuffer::new();
270 serialize_world(world, &mut snap_buf, &mut recording.registry);
271 hdr.snapshot_size = snap_buf.size() as u64;
272
273 recording.buffer.append(&hdr.to_bytes());
274 recording.buffer.append(&snap_buf.data);
275
276 let wid = world_public_id(world);
277 let hash = hash_world_state(world);
278 recording.write_state_hash(wid, hash);
279}
280
281pub fn stop_recording(world: &mut World) {
284 let Some(rec_ptr) = world.recording.take() else {
285 return;
286 };
287 let rec = unsafe { &mut *rec_ptr };
289
290 let bounds = if rec.have_bounds {
291 rec.accumulated_bounds
292 } else {
293 Aabb::default()
294 };
295 rec.write_recording_bounds(bounds);
296
297 let wid = world_public_id(world);
298 rec.write_destroy_world(wid);
299
300 let registry_offset = rec.buffer.size();
301 rec.write_registry();
302 let registry_byte_count = rec.buffer.size() - registry_offset;
303
304 let data = &mut rec.buffer.data;
306 debug_assert!(data.len() >= 48);
307 data[32..40].copy_from_slice(&(registry_offset as u64).to_le_bytes());
308 data[40..48].copy_from_slice(&(registry_byte_count as u64).to_le_bytes());
309}
310
311#[inline]
313pub fn with_recording(world: &mut World, f: impl FnOnce(&mut Recording)) {
314 if let Some(p) = world.recording {
315 unsafe { f(&mut *p) }
317 }
318}