Skip to main content

box3d_rust/recording/
buffer.rs

1//! Growable append-only byte buffer and little-endian writers.
2//! Port of `b3RecBuffer` / `b3RecW_*` from recording.c / recording.h.
3//!
4//! SPDX-FileCopyrightText: 2026 Erin Catto
5//! SPDX-License-Identifier: MIT
6
7use crate::geometry::{Capsule, Sphere, SurfaceMaterial};
8use crate::math_functions::{Aabb, Matrix3, Pos, Quat, Transform, Vec3, WorldTransform};
9use crate::types::Filter;
10
11/// Growable append-only byte buffer. Doubles on demand. `count_only` tallies
12/// size without allocating. (b3RecBuffer)
13#[derive(Debug, Clone, Default)]
14pub struct RecBuffer {
15    pub data: Vec<u8>,
16    pub count_only: bool,
17}
18
19impl RecBuffer {
20    /// (implicit zero-init in C)
21    pub fn new() -> Self {
22        Self {
23            data: Vec::new(),
24            count_only: false,
25        }
26    }
27
28    pub fn with_capacity(cap: usize) -> Self {
29        Self {
30            data: Vec::with_capacity(cap),
31            count_only: false,
32        }
33    }
34
35    pub fn size(&self) -> i32 {
36        self.data.len() as i32
37    }
38
39    /// (b3RecBufAppend)
40    pub fn append(&mut self, bytes: &[u8]) {
41        if bytes.is_empty() {
42            return;
43        }
44        if self.count_only {
45            // Grow logical size without allocating — emulate with reserve skip.
46            // Callers in count_only mode should track via a side length; we still
47            // push so size() stays correct without allocating large capacity.
48            let new_len = self.data.len() + bytes.len();
49            // Avoid allocating the payload: extend with zeros then overwrite is wasteful.
50            // Instead keep a separate size when count_only — but C bumps buf->size only.
51            // Mirror with a phantom length stored in data capacity trick: use len field.
52            // Simplest faithful approach: just don't copy, track size separately.
53            // We store count_only length in data.len by resizing without fill of content:
54            self.data.resize(new_len, 0);
55            return;
56        }
57        self.data.extend_from_slice(bytes);
58    }
59
60    pub fn append_u8(&mut self, v: u8) {
61        self.append(&[v]);
62    }
63
64    pub fn append_u16(&mut self, v: u16) {
65        self.append(&v.to_le_bytes());
66    }
67
68    pub fn append_u32(&mut self, v: u32) {
69        self.append(&v.to_le_bytes());
70    }
71
72    pub fn append_u64(&mut self, v: u64) {
73        self.append(&v.to_le_bytes());
74    }
75
76    pub fn append_i32(&mut self, v: i32) {
77        self.append_u32(v as u32);
78    }
79
80    pub fn append_f32(&mut self, v: f32) {
81        self.append(&v.to_le_bytes());
82    }
83
84    pub fn append_f64(&mut self, v: f64) {
85        self.append(&v.to_le_bytes());
86    }
87
88    pub fn append_bool(&mut self, v: bool) {
89        self.append_u8(u8::from(v));
90    }
91
92    pub fn append_vec3(&mut self, v: Vec3) {
93        self.append_f32(v.x);
94        self.append_f32(v.y);
95        self.append_f32(v.z);
96    }
97
98    pub fn append_quat(&mut self, v: Quat) {
99        self.append_vec3(v.v);
100        self.append_f32(v.s);
101    }
102
103    pub fn append_transform(&mut self, v: Transform) {
104        self.append_vec3(v.p);
105        self.append_quat(v.q);
106    }
107
108    pub fn append_pos(&mut self, v: Pos) {
109        #[cfg(feature = "double-precision")]
110        {
111            self.append_f64(v.x);
112            self.append_f64(v.y);
113            self.append_f64(v.z);
114        }
115        #[cfg(not(feature = "double-precision"))]
116        {
117            self.append_vec3(v);
118        }
119    }
120
121    pub fn append_world_xf(&mut self, v: WorldTransform) {
122        #[cfg(feature = "double-precision")]
123        {
124            self.append_pos(v.p);
125            self.append_quat(v.q);
126        }
127        #[cfg(not(feature = "double-precision"))]
128        {
129            self.append_transform(v);
130        }
131    }
132
133    pub fn append_matrix3(&mut self, v: Matrix3) {
134        self.append_vec3(v.cx);
135        self.append_vec3(v.cy);
136        self.append_vec3(v.cz);
137    }
138
139    pub fn append_aabb(&mut self, v: Aabb) {
140        self.append_vec3(v.lower_bound);
141        self.append_vec3(v.upper_bound);
142    }
143
144    pub fn append_sphere(&mut self, v: Sphere) {
145        self.append_vec3(v.center);
146        self.append_f32(v.radius);
147    }
148
149    pub fn append_capsule(&mut self, v: Capsule) {
150        self.append_vec3(v.center1);
151        self.append_vec3(v.center2);
152        self.append_f32(v.radius);
153    }
154
155    pub fn append_filter(&mut self, v: Filter) {
156        self.append_u64(v.category_bits);
157        self.append_u64(v.mask_bits);
158        self.append_i32(v.group_index);
159    }
160
161    pub fn append_material(&mut self, v: SurfaceMaterial) {
162        self.append_f32(v.friction);
163        self.append_f32(v.restitution);
164        self.append_f32(v.rolling_resistance);
165        self.append_vec3(v.tangent_velocity);
166        self.append_u64(v.user_material_id);
167        self.append_u32(v.custom_color);
168    }
169}
170
171/// Bounds-checked read cursor for snapshot images. (b3SnapReader)
172#[derive(Debug, Clone)]
173pub struct SnapReader<'a> {
174    data: &'a [u8],
175    cursor: usize,
176    pub ok: bool,
177}
178
179impl<'a> SnapReader<'a> {
180    pub fn new(data: &'a [u8]) -> Self {
181        Self {
182            data,
183            cursor: 0,
184            ok: true,
185        }
186    }
187
188    pub fn cursor(&self) -> usize {
189        self.cursor
190    }
191
192    pub fn set_cursor(&mut self, cursor: usize) {
193        self.cursor = cursor;
194    }
195
196    fn check(&mut self, need: usize) {
197        if !self.ok {
198            return;
199        }
200        if self
201            .cursor
202            .checked_add(need)
203            .map(|e| e > self.data.len())
204            .unwrap_or(true)
205        {
206            self.ok = false;
207        }
208    }
209
210    pub fn bytes(&mut self, n: usize) -> Option<&'a [u8]> {
211        self.check(n);
212        if !self.ok {
213            return None;
214        }
215        let start = self.cursor;
216        self.cursor += n;
217        Some(&self.data[start..self.cursor])
218    }
219
220    pub fn copy_bytes(&mut self, dst: &mut [u8]) {
221        if let Some(src) = self.bytes(dst.len()) {
222            dst.copy_from_slice(src);
223        }
224    }
225
226    pub fn u8(&mut self) -> u8 {
227        self.bytes(1).map(|b| b[0]).unwrap_or(0)
228    }
229
230    pub fn u16(&mut self) -> u16 {
231        let mut b = [0u8; 2];
232        self.copy_bytes(&mut b);
233        u16::from_le_bytes(b)
234    }
235
236    pub fn u32(&mut self) -> u32 {
237        let mut b = [0u8; 4];
238        self.copy_bytes(&mut b);
239        u32::from_le_bytes(b)
240    }
241
242    pub fn u64(&mut self) -> u64 {
243        let mut b = [0u8; 8];
244        self.copy_bytes(&mut b);
245        u64::from_le_bytes(b)
246    }
247
248    pub fn i32(&mut self) -> i32 {
249        self.u32() as i32
250    }
251
252    pub fn f32(&mut self) -> f32 {
253        f32::from_le_bytes(self.u32().to_le_bytes())
254    }
255
256    pub fn f64(&mut self) -> f64 {
257        f64::from_le_bytes(self.u64().to_le_bytes())
258    }
259
260    pub fn bool(&mut self) -> bool {
261        self.u8() != 0
262    }
263
264    pub fn vec3(&mut self) -> Vec3 {
265        Vec3 {
266            x: self.f32(),
267            y: self.f32(),
268            z: self.f32(),
269        }
270    }
271
272    pub fn quat(&mut self) -> Quat {
273        Quat {
274            v: self.vec3(),
275            s: self.f32(),
276        }
277    }
278
279    pub fn transform(&mut self) -> Transform {
280        Transform {
281            p: self.vec3(),
282            q: self.quat(),
283        }
284    }
285
286    pub fn pos(&mut self) -> Pos {
287        #[cfg(feature = "double-precision")]
288        {
289            Pos {
290                x: self.f64(),
291                y: self.f64(),
292                z: self.f64(),
293            }
294        }
295        #[cfg(not(feature = "double-precision"))]
296        {
297            self.vec3()
298        }
299    }
300
301    pub fn world_xf(&mut self) -> WorldTransform {
302        #[cfg(feature = "double-precision")]
303        {
304            WorldTransform {
305                p: self.pos(),
306                q: self.quat(),
307            }
308        }
309        #[cfg(not(feature = "double-precision"))]
310        {
311            self.transform()
312        }
313    }
314
315    pub fn matrix3(&mut self) -> Matrix3 {
316        Matrix3 {
317            cx: self.vec3(),
318            cy: self.vec3(),
319            cz: self.vec3(),
320        }
321    }
322
323    pub fn aabb(&mut self) -> Aabb {
324        Aabb {
325            lower_bound: self.vec3(),
326            upper_bound: self.vec3(),
327        }
328    }
329
330    pub fn sphere(&mut self) -> Sphere {
331        Sphere {
332            center: self.vec3(),
333            radius: self.f32(),
334        }
335    }
336
337    pub fn capsule(&mut self) -> Capsule {
338        Capsule {
339            center1: self.vec3(),
340            center2: self.vec3(),
341            radius: self.f32(),
342        }
343    }
344
345    pub fn filter(&mut self) -> Filter {
346        Filter {
347            category_bits: self.u64(),
348            mask_bits: self.u64(),
349            group_index: self.i32(),
350        }
351    }
352
353    pub fn material(&mut self) -> SurfaceMaterial {
354        SurfaceMaterial {
355            friction: self.f32(),
356            restitution: self.f32(),
357            rolling_resistance: self.f32(),
358            tangent_velocity: self.vec3(),
359            user_material_id: self.u64(),
360            custom_color: self.u32(),
361        }
362    }
363
364    /// Bounds check before allocating from image. (b3SnapCheckCount)
365    pub fn check_count(&self, count: i32, mem_size: i32, min_stream_bytes: i32) -> bool {
366        if count < 0 || mem_size < 0 || min_stream_bytes < 0 {
367            return false;
368        }
369        if mem_size > 0 && count > 0 && count > i32::MAX / mem_size {
370            return false;
371        }
372        let remaining = self.data.len() as i64 - self.cursor as i64;
373        (count as i64) * (min_stream_bytes as i64) <= remaining
374    }
375}