Skip to main content

boxddd/
events.rs

1use crate::core::{box3d_lock, callback_state};
2use crate::error::Result;
3use crate::types::{BodyId, ContactData, ContactId, JointId, Pos, ShapeId, Vec3, WorldTransform};
4use crate::world::World;
5use boxddd_sys::ffi;
6use std::ffi::c_void;
7
8#[derive(Copy, Clone)]
9/// Borrowed view of a body movement event from the latest world step.
10///
11/// The view borrows Box3D's transient event buffer and is only valid for the
12/// callback passed to `with_body_events_view`. Box3D reports bodies moved by
13/// simulation here, not bodies moved directly by the user.
14pub struct BodyMove<'a>(&'a ffi::b3BodyMoveEvent);
15
16impl BodyMove<'_> {
17    /// Returns the body that moved.
18    pub fn body_id(&self) -> BodyId {
19        BodyId::from_raw(self.0.bodyId)
20    }
21
22    /// Returns the body's world transform after the movement.
23    pub fn transform(&self) -> WorldTransform {
24        WorldTransform::from_raw(self.0.transform)
25    }
26
27    /// Returns true when the body went to sleep during the step.
28    pub fn fell_asleep(&self) -> bool {
29        self.0.fellAsleep
30    }
31
32    /// Returns the raw Box3D `userData` pointer value observed in this event.
33    ///
34    /// The pointer is untyped and not owned by `boxddd`; interpreting or dereferencing it is the
35    /// caller's unsafe interop responsibility.
36    pub fn raw_user_data(&self) -> *mut c_void {
37        self.0.userData
38    }
39}
40
41/// Iterator over borrowed body movement events.
42///
43/// The iterator borrows Box3D's transient event buffer and cannot safely
44/// outlive the `with_body_events_view` closure.
45pub struct BodyMoveIter<'a>(std::slice::Iter<'a, ffi::b3BodyMoveEvent>);
46
47impl<'a> Iterator for BodyMoveIter<'a> {
48    type Item = BodyMove<'a>;
49
50    fn next(&mut self) -> Option<Self::Item> {
51        self.0.next().map(BodyMove)
52    }
53
54    fn size_hint(&self) -> (usize, Option<usize>) {
55        self.0.size_hint()
56    }
57}
58
59#[derive(Clone, Debug, PartialEq)]
60/// Owned body movement event snapshot.
61///
62/// Box3D reports bodies moved by simulation here, not bodies moved directly by
63/// the user.
64pub struct BodyMoveEvent {
65    /// Body that moved.
66    pub body_id: BodyId,
67    /// Body transform after the movement.
68    pub transform: WorldTransform,
69    /// Whether the body went to sleep during the step.
70    pub fell_asleep: bool,
71    /// Raw Box3D `userData` pointer value observed in the event.
72    pub raw_user_data: *mut c_void,
73}
74
75#[derive(Copy, Clone)]
76/// Borrowed view of a sensor begin-touch event.
77///
78/// This view borrows Box3D's transient event buffer and is only valid inside
79/// the `with_sensor_events_view` closure that produced it.
80pub struct SensorBeginTouch<'a>(&'a ffi::b3SensorBeginTouchEvent);
81
82impl SensorBeginTouch<'_> {
83    /// Returns the sensor shape receiving the touch.
84    pub fn sensor_shape(&self) -> ShapeId {
85        ShapeId::from_raw(self.0.sensorShapeId)
86    }
87
88    /// Returns the non-sensor shape entering the sensor.
89    pub fn visitor_shape(&self) -> ShapeId {
90        ShapeId::from_raw(self.0.visitorShapeId)
91    }
92}
93
94#[derive(Copy, Clone)]
95/// Borrowed view of a sensor end-touch event.
96///
97/// This view borrows Box3D's transient event buffer and is only valid inside
98/// the `with_sensor_events_view` closure that produced it. End-touch ids may
99/// already be invalid if the sensor or visitor shape was destroyed or if the
100/// overlap was invalidated by a world mutation.
101pub struct SensorEndTouch<'a>(&'a ffi::b3SensorEndTouchEvent);
102
103impl SensorEndTouch<'_> {
104    /// Returns the sensor shape ending the touch.
105    pub fn sensor_shape(&self) -> ShapeId {
106        ShapeId::from_raw(self.0.sensorShapeId)
107    }
108
109    /// Returns the non-sensor shape leaving the sensor.
110    pub fn visitor_shape(&self) -> ShapeId {
111        ShapeId::from_raw(self.0.visitorShapeId)
112    }
113}
114
115/// Iterator over borrowed sensor begin-touch events.
116///
117/// The iterator borrows Box3D's transient event buffer and cannot safely
118/// outlive the `with_sensor_events_view` closure.
119pub struct SensorBeginIter<'a>(std::slice::Iter<'a, ffi::b3SensorBeginTouchEvent>);
120
121impl<'a> Iterator for SensorBeginIter<'a> {
122    type Item = SensorBeginTouch<'a>;
123
124    fn next(&mut self) -> Option<Self::Item> {
125        self.0.next().map(SensorBeginTouch)
126    }
127
128    fn size_hint(&self) -> (usize, Option<usize>) {
129        self.0.size_hint()
130    }
131}
132
133/// Iterator over borrowed sensor end-touch events.
134///
135/// The iterator borrows Box3D's transient event buffer and cannot safely
136/// outlive the `with_sensor_events_view` closure.
137pub struct SensorEndIter<'a>(std::slice::Iter<'a, ffi::b3SensorEndTouchEvent>);
138
139impl<'a> Iterator for SensorEndIter<'a> {
140    type Item = SensorEndTouch<'a>;
141
142    fn next(&mut self) -> Option<Self::Item> {
143        self.0.next().map(SensorEndTouch)
144    }
145
146    fn size_hint(&self) -> (usize, Option<usize>) {
147        self.0.size_hint()
148    }
149}
150
151#[derive(Clone, Debug, PartialEq, Eq)]
152/// Owned sensor begin-touch event snapshot.
153pub struct SensorBeginTouchEvent {
154    /// Sensor shape receiving the touch.
155    pub sensor_shape: ShapeId,
156    /// Shape entering the sensor.
157    pub visitor_shape: ShapeId,
158}
159
160#[derive(Clone, Debug, PartialEq, Eq)]
161/// Owned sensor end-touch event snapshot.
162pub struct SensorEndTouchEvent {
163    /// Sensor shape ending the touch.
164    pub sensor_shape: ShapeId,
165    /// Shape leaving the sensor.
166    pub visitor_shape: ShapeId,
167}
168
169#[derive(Clone, Debug, Default, PartialEq, Eq)]
170/// Owned sensor event snapshot for the latest world step.
171pub struct SensorEvents {
172    /// Sensor touches that began during the step.
173    pub begin: Vec<SensorBeginTouchEvent>,
174    /// Sensor touches that ended during the step.
175    pub end: Vec<SensorEndTouchEvent>,
176}
177
178#[derive(Copy, Clone)]
179/// Borrowed view of a contact begin-touch event.
180///
181/// This view borrows Box3D's transient event buffer and is only valid inside
182/// the `with_contact_events_view` closure that produced it.
183pub struct ContactBeginTouch<'a>(&'a ffi::b3ContactBeginTouchEvent);
184
185impl ContactBeginTouch<'_> {
186    /// Returns the first shape in the contact pair.
187    pub fn shape_a(&self) -> ShapeId {
188        ShapeId::from_raw(self.0.shapeIdA)
189    }
190
191    /// Returns the second shape in the contact pair.
192    pub fn shape_b(&self) -> ShapeId {
193        ShapeId::from_raw(self.0.shapeIdB)
194    }
195
196    /// Returns the transient contact identifier for the pair.
197    ///
198    /// Validate the id before later use; Box3D may destroy contacts when the
199    /// world is stepped or modified.
200    pub fn contact_id(&self) -> ContactId {
201        ContactId::from_raw(self.0.contactId)
202    }
203}
204
205#[derive(Copy, Clone)]
206/// Borrowed view of a contact end-touch event.
207///
208/// This view borrows Box3D's transient event buffer and is only valid inside
209/// the `with_contact_events_view` closure that produced it. End-touch shape and
210/// contact ids may already be invalid.
211pub struct ContactEndTouch<'a>(&'a ffi::b3ContactEndTouchEvent);
212
213impl ContactEndTouch<'_> {
214    /// Returns the first shape in the contact pair.
215    ///
216    /// The shape may have been destroyed before this event is consumed.
217    pub fn shape_a(&self) -> ShapeId {
218        ShapeId::from_raw(self.0.shapeIdA)
219    }
220
221    /// Returns the second shape in the contact pair.
222    ///
223    /// The shape may have been destroyed before this event is consumed.
224    pub fn shape_b(&self) -> ShapeId {
225        ShapeId::from_raw(self.0.shapeIdB)
226    }
227
228    /// Returns the transient contact identifier for the pair.
229    ///
230    /// The contact may have been destroyed before this event is consumed.
231    pub fn contact_id(&self) -> ContactId {
232        ContactId::from_raw(self.0.contactId)
233    }
234}
235
236#[derive(Copy, Clone)]
237/// Borrowed view of a contact hit event.
238///
239/// This view borrows Box3D's transient event buffer and is only valid inside
240/// the `with_contact_events_view` closure that produced it. Hit events can be
241/// reported for speculative contacts that later receive a confirmed impulse.
242pub struct ContactHit<'a>(&'a ffi::b3ContactHitEvent);
243
244impl ContactHit<'_> {
245    /// Returns the first shape in the contact pair.
246    pub fn shape_a(&self) -> ShapeId {
247        ShapeId::from_raw(self.0.shapeIdA)
248    }
249
250    /// Returns the second shape in the contact pair.
251    pub fn shape_b(&self) -> ShapeId {
252        ShapeId::from_raw(self.0.shapeIdB)
253    }
254
255    /// Returns the transient contact identifier for the pair.
256    pub fn contact_id(&self) -> ContactId {
257        ContactId::from_raw(self.0.contactId)
258    }
259
260    /// Returns the hit point in world coordinates.
261    pub fn point(&self) -> Pos {
262        Pos::from_raw(self.0.point)
263    }
264
265    /// Returns the hit normal pointing from shape A to shape B.
266    pub fn normal(&self) -> Vec3 {
267        Vec3::from_raw(self.0.normal)
268    }
269
270    /// Returns the relative approach speed at impact.
271    ///
272    /// Box3D reports this as a positive speed, typically in meters per second.
273    pub fn approach_speed(&self) -> f32 {
274        self.0.approachSpeed
275    }
276
277    /// Returns the user material identifier for shape A.
278    pub fn user_material_id_a(&self) -> u64 {
279        self.0.userMaterialIdA
280    }
281
282    /// Returns the user material identifier for shape B.
283    pub fn user_material_id_b(&self) -> u64 {
284        self.0.userMaterialIdB
285    }
286}
287
288/// Iterator over borrowed contact begin-touch events.
289///
290/// The iterator borrows Box3D's transient event buffer and cannot safely
291/// outlive the `with_contact_events_view` closure.
292pub struct ContactBeginIter<'a>(std::slice::Iter<'a, ffi::b3ContactBeginTouchEvent>);
293
294impl<'a> Iterator for ContactBeginIter<'a> {
295    type Item = ContactBeginTouch<'a>;
296
297    fn next(&mut self) -> Option<Self::Item> {
298        self.0.next().map(ContactBeginTouch)
299    }
300
301    fn size_hint(&self) -> (usize, Option<usize>) {
302        self.0.size_hint()
303    }
304}
305
306/// Iterator over borrowed contact end-touch events.
307///
308/// The iterator borrows Box3D's transient event buffer and cannot safely
309/// outlive the `with_contact_events_view` closure.
310pub struct ContactEndIter<'a>(std::slice::Iter<'a, ffi::b3ContactEndTouchEvent>);
311
312impl<'a> Iterator for ContactEndIter<'a> {
313    type Item = ContactEndTouch<'a>;
314
315    fn next(&mut self) -> Option<Self::Item> {
316        self.0.next().map(ContactEndTouch)
317    }
318
319    fn size_hint(&self) -> (usize, Option<usize>) {
320        self.0.size_hint()
321    }
322}
323
324/// Iterator over borrowed contact hit events.
325///
326/// The iterator borrows Box3D's transient event buffer and cannot safely
327/// outlive the `with_contact_events_view` closure.
328pub struct ContactHitIter<'a>(std::slice::Iter<'a, ffi::b3ContactHitEvent>);
329
330impl<'a> Iterator for ContactHitIter<'a> {
331    type Item = ContactHit<'a>;
332
333    fn next(&mut self) -> Option<Self::Item> {
334        self.0.next().map(ContactHit)
335    }
336
337    fn size_hint(&self) -> (usize, Option<usize>) {
338        self.0.size_hint()
339    }
340}
341
342#[derive(Clone, Debug, PartialEq, Eq)]
343/// Owned contact begin-touch event snapshot.
344pub struct ContactBeginTouchEvent {
345    /// First shape in the contact pair.
346    pub shape_a: ShapeId,
347    /// Second shape in the contact pair.
348    pub shape_b: ShapeId,
349    /// Contact identifier for the pair.
350    pub contact_id: ContactId,
351}
352
353#[derive(Clone, Debug, PartialEq, Eq)]
354/// Owned contact end-touch event snapshot.
355pub struct ContactEndTouchEvent {
356    /// First shape in the contact pair.
357    pub shape_a: ShapeId,
358    /// Second shape in the contact pair.
359    pub shape_b: ShapeId,
360    /// Contact identifier for the pair.
361    pub contact_id: ContactId,
362}
363
364#[derive(Clone, Debug, PartialEq)]
365/// Owned contact hit event snapshot.
366pub struct ContactHitEvent {
367    /// First shape in the contact pair.
368    pub shape_a: ShapeId,
369    /// Second shape in the contact pair.
370    pub shape_b: ShapeId,
371    /// Contact identifier for the pair.
372    pub contact_id: ContactId,
373    /// Hit point in world coordinates.
374    pub point: Pos,
375    /// Hit normal.
376    pub normal: Vec3,
377    /// Relative approach speed at impact.
378    pub approach_speed: f32,
379    /// User material identifier for shape A.
380    pub user_material_id_a: u64,
381    /// User material identifier for shape B.
382    pub user_material_id_b: u64,
383}
384
385#[derive(Clone, Debug, Default, PartialEq)]
386/// Owned contact event snapshot for the latest world step.
387pub struct ContactEvents {
388    /// Contacts that began touching during the step.
389    pub begin: Vec<ContactBeginTouchEvent>,
390    /// Contacts that stopped touching during the step.
391    pub end: Vec<ContactEndTouchEvent>,
392    /// Contact hit events reported during the step.
393    pub hit: Vec<ContactHitEvent>,
394}
395
396#[derive(Copy, Clone)]
397/// Borrowed view of a joint event.
398///
399/// This view borrows Box3D's transient event buffer and is only valid inside
400/// the `with_joint_events_view` closure that produced it.
401pub struct JointEventView<'a>(&'a ffi::b3JointEvent);
402
403impl JointEventView<'_> {
404    /// Returns the joint that generated the event.
405    pub fn joint_id(&self) -> JointId {
406        JointId::from_raw(self.0.jointId)
407    }
408
409    /// Returns the raw Box3D `userData` pointer value observed in this event.
410    ///
411    /// The pointer is untyped and not owned by `boxddd`; interpreting or dereferencing it is the
412    /// caller's unsafe interop responsibility.
413    pub fn raw_user_data(&self) -> *mut c_void {
414        self.0.userData
415    }
416}
417
418/// Iterator over borrowed joint events.
419///
420/// The iterator borrows Box3D's transient event buffer and cannot safely
421/// outlive the `with_joint_events_view` closure.
422pub struct JointEventIter<'a>(std::slice::Iter<'a, ffi::b3JointEvent>);
423
424impl<'a> Iterator for JointEventIter<'a> {
425    type Item = JointEventView<'a>;
426
427    fn next(&mut self) -> Option<Self::Item> {
428        self.0.next().map(JointEventView)
429    }
430
431    fn size_hint(&self) -> (usize, Option<usize>) {
432        self.0.size_hint()
433    }
434}
435
436#[derive(Clone, Debug, PartialEq)]
437/// Owned joint event snapshot.
438pub struct JointEvent {
439    /// Joint that generated the event.
440    pub joint_id: JointId,
441    /// Raw Box3D `userData` pointer value observed in the event.
442    pub raw_user_data: *mut c_void,
443}
444
445fn map_snapshot_into<T, U>(out: &mut Vec<U>, input: &[T], mut map: impl FnMut(&T) -> U) {
446    out.clear();
447    out.reserve(input.len());
448    out.extend(input.iter().map(|value| map(value)));
449}
450
451fn raw_event_slice<'a, T>(ptr: *const T, count: i32) -> &'a [T] {
452    if count > 0 && !ptr.is_null() {
453        unsafe { std::slice::from_raw_parts(ptr, count as usize) }
454    } else {
455        &[]
456    }
457}
458
459fn body_event_slice<'a>(raw: ffi::b3BodyEvents) -> &'a [ffi::b3BodyMoveEvent] {
460    raw_event_slice(raw.moveEvents, raw.moveCount)
461}
462
463fn sensor_begin_slice<'a>(raw: ffi::b3SensorEvents) -> &'a [ffi::b3SensorBeginTouchEvent] {
464    raw_event_slice(raw.beginEvents, raw.beginCount)
465}
466
467fn sensor_end_slice<'a>(raw: ffi::b3SensorEvents) -> &'a [ffi::b3SensorEndTouchEvent] {
468    raw_event_slice(raw.endEvents, raw.endCount)
469}
470
471fn contact_begin_slice<'a>(raw: ffi::b3ContactEvents) -> &'a [ffi::b3ContactBeginTouchEvent] {
472    raw_event_slice(raw.beginEvents, raw.beginCount)
473}
474
475fn contact_end_slice<'a>(raw: ffi::b3ContactEvents) -> &'a [ffi::b3ContactEndTouchEvent] {
476    raw_event_slice(raw.endEvents, raw.endCount)
477}
478
479fn contact_hit_slice<'a>(raw: ffi::b3ContactEvents) -> &'a [ffi::b3ContactHitEvent] {
480    raw_event_slice(raw.hitEvents, raw.hitCount)
481}
482
483fn joint_event_slice<'a>(raw: ffi::b3JointEvents) -> &'a [ffi::b3JointEvent] {
484    raw_event_slice(raw.jointEvents, raw.count)
485}
486
487impl World {
488    /// Returns owned body movement events from the latest completed world step.
489    ///
490    /// Box3D stores event buffers as transient step-local arrays. This method copies the
491    /// body movement records into Rust-owned values so the returned vector can be kept after
492    /// the next step or after later world mutations.
493    pub fn body_events(&self) -> Vec<BodyMoveEvent> {
494        self.try_body_events().expect("invalid Box3D world")
495    }
496
497    /// Tries to return owned body movement events from the latest completed world step.
498    ///
499    /// Unlike [`Self::try_with_body_events_view`], this allocates an owned snapshot that
500    /// is independent from Box3D's transient event buffer.
501    pub fn try_body_events(&self) -> Result<Vec<BodyMoveEvent>> {
502        let mut out = Vec::new();
503        self.try_body_events_into(&mut out)?;
504        Ok(out)
505    }
506
507    /// Writes owned body movement events into `out`, clearing it first.
508    ///
509    /// Existing capacity is reused, and every copied event is independent from Box3D's
510    /// transient event buffer.
511    pub fn body_events_into(&self, out: &mut Vec<BodyMoveEvent>) {
512        self.try_body_events_into(out).expect("invalid Box3D world");
513    }
514
515    /// Tries to write owned body movement events into `out`, clearing it first.
516    pub fn try_body_events_into(&self, out: &mut Vec<BodyMoveEvent>) -> Result<()> {
517        callback_state::check_not_in_callback()?;
518        let _guard = box3d_lock::lock();
519        self.check_world_valid_locked()?;
520        let raw = unsafe { ffi::b3World_GetBodyEvents(self.raw()) };
521        let events = body_event_slice(raw);
522        map_snapshot_into(out, events, |event| BodyMoveEvent {
523            body_id: BodyId::from_raw(event.bodyId),
524            transform: WorldTransform::from_raw(event.transform),
525            fell_asleep: event.fellAsleep,
526            raw_user_data: event.userData,
527        });
528        Ok(())
529    }
530
531    /// Borrows body movement events for the duration of `f`.
532    ///
533    /// Use this when you want to avoid allocation and only need to inspect Box3D's
534    /// transient event buffer inside the closure. Safe Rust cannot return the borrowed
535    /// iterator from the closure:
536    ///
537    /// ```compile_fail
538    /// use boxddd::{BodyMoveIter, World};
539    ///
540    /// fn leak_events(world: &World) -> BodyMoveIter<'_> {
541    ///     world.with_body_events_view(|events| events)
542    /// }
543    /// ```
544    pub fn try_with_body_events_view<T>(&self, f: impl FnOnce(BodyMoveIter<'_>) -> T) -> Result<T> {
545        callback_state::check_not_in_callback()?;
546        let _guard = box3d_lock::lock();
547        self.check_world_valid_locked()?;
548        let raw = unsafe { ffi::b3World_GetBodyEvents(self.raw()) };
549        let events = body_event_slice(raw);
550        drop(_guard);
551        Ok(f(BodyMoveIter(events.iter())))
552    }
553
554    /// Borrows body movement events for the duration of `f`.
555    ///
556    /// Panics if Box3D rejects the world handle. Use
557    /// [`Self::try_with_body_events_view`] on fallible paths.
558    pub fn with_body_events_view<T>(&self, f: impl FnOnce(BodyMoveIter<'_>) -> T) -> T {
559        self.try_with_body_events_view(f)
560            .expect("invalid Box3D world")
561    }
562
563    /// Borrows raw Box3D body movement events for the duration of `f`.
564    ///
565    /// # Safety
566    ///
567    /// The raw event records contain untyped `userData` pointers owned by the application.
568    /// The callback must not copy out raw event pointers, retain references to the slice, or
569    /// dereference `userData` unless it can uphold the original pointer validity and aliasing
570    /// requirements. Box3D may invalidate the native buffer after a later step or world
571    /// mutation.
572    pub unsafe fn try_with_body_events_raw<T>(
573        &self,
574        f: impl FnOnce(&[ffi::b3BodyMoveEvent]) -> T,
575    ) -> Result<T> {
576        callback_state::check_not_in_callback()?;
577        let _guard = box3d_lock::lock();
578        self.check_world_valid_locked()?;
579        let raw = unsafe { ffi::b3World_GetBodyEvents(self.raw()) };
580        let events = body_event_slice(raw);
581        drop(_guard);
582        Ok(f(events))
583    }
584
585    /// Borrows raw Box3D body movement events for the duration of `f`.
586    ///
587    /// # Safety
588    ///
589    /// See [`Self::try_with_body_events_raw`].
590    pub unsafe fn with_body_events_raw<T>(
591        &self,
592        f: impl FnOnce(&[ffi::b3BodyMoveEvent]) -> T,
593    ) -> T {
594        unsafe { self.try_with_body_events_raw(f) }.expect("invalid Box3D world")
595    }
596
597    /// Returns owned sensor events from the latest completed world step.
598    ///
599    /// End-touch shape identifiers may refer to shapes that were destroyed or had their
600    /// contacts invalidated during the step. Validate ids before using them for later
601    /// world operations.
602    pub fn sensor_events(&self) -> SensorEvents {
603        self.try_sensor_events().expect("invalid Box3D world")
604    }
605
606    /// Tries to return owned sensor events from the latest completed world step.
607    pub fn try_sensor_events(&self) -> Result<SensorEvents> {
608        let mut out = SensorEvents::default();
609        self.try_sensor_events_into(&mut out)?;
610        Ok(out)
611    }
612
613    /// Writes owned sensor events into `out`, clearing its vectors first.
614    pub fn sensor_events_into(&self, out: &mut SensorEvents) {
615        self.try_sensor_events_into(out)
616            .expect("invalid Box3D world");
617    }
618
619    /// Tries to write owned sensor events into `out`, clearing its vectors first.
620    pub fn try_sensor_events_into(&self, out: &mut SensorEvents) -> Result<()> {
621        callback_state::check_not_in_callback()?;
622        let _guard = box3d_lock::lock();
623        self.check_world_valid_locked()?;
624        let raw = unsafe { ffi::b3World_GetSensorEvents(self.raw()) };
625        map_snapshot_into(&mut out.begin, sensor_begin_slice(raw), |event| {
626            SensorBeginTouchEvent {
627                sensor_shape: ShapeId::from_raw(event.sensorShapeId),
628                visitor_shape: ShapeId::from_raw(event.visitorShapeId),
629            }
630        });
631        map_snapshot_into(&mut out.end, sensor_end_slice(raw), |event| {
632            SensorEndTouchEvent {
633                sensor_shape: ShapeId::from_raw(event.sensorShapeId),
634                visitor_shape: ShapeId::from_raw(event.visitorShapeId),
635            }
636        });
637        Ok(())
638    }
639
640    /// Borrows sensor begin and end events for the duration of `f`.
641    ///
642    /// The borrowed iterators view Box3D's transient event arrays and cannot safely escape
643    /// the closure. Use [`Self::try_sensor_events`] when events must be stored or processed
644    /// after additional world mutations.
645    ///
646    /// ```compile_fail
647    /// use boxddd::{SensorBeginIter, World};
648    ///
649    /// fn leak_sensor_events(world: &World) -> SensorBeginIter<'_> {
650    ///     world.with_sensor_events_view(|begin, _end| begin)
651    /// }
652    /// ```
653    pub fn try_with_sensor_events_view<T>(
654        &self,
655        f: impl FnOnce(SensorBeginIter<'_>, SensorEndIter<'_>) -> T,
656    ) -> Result<T> {
657        callback_state::check_not_in_callback()?;
658        let _guard = box3d_lock::lock();
659        self.check_world_valid_locked()?;
660        let raw = unsafe { ffi::b3World_GetSensorEvents(self.raw()) };
661        let begin = sensor_begin_slice(raw);
662        let end = sensor_end_slice(raw);
663        drop(_guard);
664        Ok(f(SensorBeginIter(begin.iter()), SensorEndIter(end.iter())))
665    }
666
667    /// Borrows sensor begin and end events for the duration of `f`.
668    ///
669    /// Panics if Box3D rejects the world handle. Use
670    /// [`Self::try_with_sensor_events_view`] on fallible paths.
671    pub fn with_sensor_events_view<T>(
672        &self,
673        f: impl FnOnce(SensorBeginIter<'_>, SensorEndIter<'_>) -> T,
674    ) -> T {
675        self.try_with_sensor_events_view(f)
676            .expect("invalid Box3D world")
677    }
678
679    /// Borrows raw Box3D sensor events for the duration of `f`.
680    ///
681    /// # Safety
682    ///
683    /// The callback must not copy out raw event pointers or retain references to the slices
684    /// beyond its call. The raw records must be interpreted according to Box3D's event
685    /// layout, including the possibility that end-touch ids are already invalid.
686    pub unsafe fn try_with_sensor_events_raw<T>(
687        &self,
688        f: impl FnOnce(&[ffi::b3SensorBeginTouchEvent], &[ffi::b3SensorEndTouchEvent]) -> T,
689    ) -> Result<T> {
690        callback_state::check_not_in_callback()?;
691        let _guard = box3d_lock::lock();
692        self.check_world_valid_locked()?;
693        let raw = unsafe { ffi::b3World_GetSensorEvents(self.raw()) };
694        let begin = sensor_begin_slice(raw);
695        let end = sensor_end_slice(raw);
696        drop(_guard);
697        Ok(f(begin, end))
698    }
699
700    /// Borrows raw Box3D sensor events for the duration of `f`.
701    ///
702    /// # Safety
703    ///
704    /// See [`Self::try_with_sensor_events_raw`].
705    pub unsafe fn with_sensor_events_raw<T>(
706        &self,
707        f: impl FnOnce(&[ffi::b3SensorBeginTouchEvent], &[ffi::b3SensorEndTouchEvent]) -> T,
708    ) -> T {
709        unsafe { self.try_with_sensor_events_raw(f) }.expect("invalid Box3D world")
710    }
711
712    /// Returns owned contact events from the latest completed world step.
713    ///
714    /// End-touch contact and shape ids can become invalid when contacts are destroyed by
715    /// filtering, body/shape destruction, body type changes, or a later world step.
716    pub fn contact_events(&self) -> ContactEvents {
717        self.try_contact_events().expect("invalid Box3D world")
718    }
719
720    /// Tries to return owned contact events from the latest completed world step.
721    pub fn try_contact_events(&self) -> Result<ContactEvents> {
722        let mut out = ContactEvents::default();
723        self.try_contact_events_into(&mut out)?;
724        Ok(out)
725    }
726
727    /// Writes owned contact events into `out`, clearing its vectors first.
728    pub fn contact_events_into(&self, out: &mut ContactEvents) {
729        self.try_contact_events_into(out)
730            .expect("invalid Box3D world");
731    }
732
733    /// Returns the current data for a live contact.
734    ///
735    /// Contact ids reported by begin or end events are transient. This method panics if the
736    /// contact is no longer valid; use [`Self::try_contact_data`] when consuming stale event
737    /// data.
738    pub fn contact_data(&self, contact_id: ContactId) -> ContactData {
739        self.try_contact_data(contact_id)
740            .expect("invalid ContactId or Box3D world")
741    }
742
743    /// Tries to return the current data for a live contact.
744    ///
745    /// Returns an error when `contact_id` is stale or belongs to a different world.
746    pub fn try_contact_data(&self, contact_id: ContactId) -> Result<ContactData> {
747        callback_state::check_not_in_callback()?;
748        let _guard = box3d_lock::lock();
749        self.check_contact_belongs_locked(contact_id)?;
750        let raw = unsafe { ffi::b3Contact_GetData(contact_id.into_raw()) };
751        Ok(unsafe { ContactData::from_raw(raw) })
752    }
753
754    /// Tries to write owned contact events into `out`, clearing its vectors first.
755    pub fn try_contact_events_into(&self, out: &mut ContactEvents) -> Result<()> {
756        callback_state::check_not_in_callback()?;
757        let _guard = box3d_lock::lock();
758        self.check_world_valid_locked()?;
759        let raw = unsafe { ffi::b3World_GetContactEvents(self.raw()) };
760        map_snapshot_into(&mut out.begin, contact_begin_slice(raw), |event| {
761            ContactBeginTouchEvent {
762                shape_a: ShapeId::from_raw(event.shapeIdA),
763                shape_b: ShapeId::from_raw(event.shapeIdB),
764                contact_id: ContactId::from_raw(event.contactId),
765            }
766        });
767        map_snapshot_into(&mut out.end, contact_end_slice(raw), |event| {
768            ContactEndTouchEvent {
769                shape_a: ShapeId::from_raw(event.shapeIdA),
770                shape_b: ShapeId::from_raw(event.shapeIdB),
771                contact_id: ContactId::from_raw(event.contactId),
772            }
773        });
774        map_snapshot_into(&mut out.hit, contact_hit_slice(raw), |event| {
775            ContactHitEvent {
776                shape_a: ShapeId::from_raw(event.shapeIdA),
777                shape_b: ShapeId::from_raw(event.shapeIdB),
778                contact_id: ContactId::from_raw(event.contactId),
779                point: Pos::from_raw(event.point),
780                normal: Vec3::from_raw(event.normal),
781                approach_speed: event.approachSpeed,
782                user_material_id_a: event.userMaterialIdA,
783                user_material_id_b: event.userMaterialIdB,
784            }
785        });
786        Ok(())
787    }
788
789    /// Borrows contact begin, end, and hit events for the duration of `f`.
790    ///
791    /// The borrowed iterators view Box3D's transient event arrays. Use
792    /// [`Self::try_contact_events`] when events must outlive the closure or be processed
793    /// after additional world mutations.
794    ///
795    /// ```compile_fail
796    /// use boxddd::{ContactBeginIter, World};
797    ///
798    /// fn leak_contact_events(world: &World) -> ContactBeginIter<'_> {
799    ///     world.with_contact_events_view(|begin, _end, _hit| begin)
800    /// }
801    /// ```
802    pub fn try_with_contact_events_view<T>(
803        &self,
804        f: impl FnOnce(ContactBeginIter<'_>, ContactEndIter<'_>, ContactHitIter<'_>) -> T,
805    ) -> Result<T> {
806        callback_state::check_not_in_callback()?;
807        let _guard = box3d_lock::lock();
808        self.check_world_valid_locked()?;
809        let raw = unsafe { ffi::b3World_GetContactEvents(self.raw()) };
810        let begin = contact_begin_slice(raw);
811        let end = contact_end_slice(raw);
812        let hit = contact_hit_slice(raw);
813        drop(_guard);
814        Ok(f(
815            ContactBeginIter(begin.iter()),
816            ContactEndIter(end.iter()),
817            ContactHitIter(hit.iter()),
818        ))
819    }
820
821    /// Borrows contact begin, end, and hit events for the duration of `f`.
822    ///
823    /// Panics if Box3D rejects the world handle. Use
824    /// [`Self::try_with_contact_events_view`] on fallible paths.
825    pub fn with_contact_events_view<T>(
826        &self,
827        f: impl FnOnce(ContactBeginIter<'_>, ContactEndIter<'_>, ContactHitIter<'_>) -> T,
828    ) -> T {
829        self.try_with_contact_events_view(f)
830            .expect("invalid Box3D world")
831    }
832
833    /// Borrows raw Box3D contact events for the duration of `f`.
834    ///
835    /// # Safety
836    ///
837    /// The callback must not copy out raw event pointers or retain references to the slices
838    /// beyond its call. The raw records must be interpreted according to Box3D's event
839    /// layout, including transient contact ids and application-owned material ids.
840    pub unsafe fn try_with_contact_events_raw<T>(
841        &self,
842        f: impl FnOnce(
843            &[ffi::b3ContactBeginTouchEvent],
844            &[ffi::b3ContactEndTouchEvent],
845            &[ffi::b3ContactHitEvent],
846        ) -> T,
847    ) -> Result<T> {
848        callback_state::check_not_in_callback()?;
849        let _guard = box3d_lock::lock();
850        self.check_world_valid_locked()?;
851        let raw = unsafe { ffi::b3World_GetContactEvents(self.raw()) };
852        let begin = contact_begin_slice(raw);
853        let end = contact_end_slice(raw);
854        let hit = contact_hit_slice(raw);
855        drop(_guard);
856        Ok(f(begin, end, hit))
857    }
858
859    /// Borrows raw Box3D contact events for the duration of `f`.
860    ///
861    /// # Safety
862    ///
863    /// See [`Self::try_with_contact_events_raw`].
864    pub unsafe fn with_contact_events_raw<T>(
865        &self,
866        f: impl FnOnce(
867            &[ffi::b3ContactBeginTouchEvent],
868            &[ffi::b3ContactEndTouchEvent],
869            &[ffi::b3ContactHitEvent],
870        ) -> T,
871    ) -> T {
872        unsafe { self.try_with_contact_events_raw(f) }.expect("invalid Box3D world")
873    }
874
875    /// Returns owned joint events from the latest completed world step.
876    ///
877    /// Joint events report joints that were awake and exceeded their configured force or
878    /// torque thresholds. Box3D does not include the observed force or torque in the event
879    /// record.
880    pub fn joint_events(&self) -> Vec<JointEvent> {
881        self.try_joint_events().expect("invalid Box3D world")
882    }
883
884    /// Tries to return owned joint events from the latest completed world step.
885    pub fn try_joint_events(&self) -> Result<Vec<JointEvent>> {
886        let mut out = Vec::new();
887        self.try_joint_events_into(&mut out)?;
888        Ok(out)
889    }
890
891    /// Writes owned joint events into `out`, clearing it first.
892    pub fn joint_events_into(&self, out: &mut Vec<JointEvent>) {
893        self.try_joint_events_into(out)
894            .expect("invalid Box3D world");
895    }
896
897    /// Tries to write owned joint events into `out`, clearing it first.
898    pub fn try_joint_events_into(&self, out: &mut Vec<JointEvent>) -> Result<()> {
899        callback_state::check_not_in_callback()?;
900        let _guard = box3d_lock::lock();
901        self.check_world_valid_locked()?;
902        let raw = unsafe { ffi::b3World_GetJointEvents(self.raw()) };
903        map_snapshot_into(out, joint_event_slice(raw), |event| JointEvent {
904            joint_id: JointId::from_raw(event.jointId),
905            raw_user_data: event.userData,
906        });
907        Ok(())
908    }
909
910    /// Borrows joint events for the duration of `f`.
911    ///
912    /// The borrowed iterator views Box3D's transient joint event array. Use
913    /// [`Self::try_joint_events`] when events must be stored after the closure.
914    ///
915    /// ```compile_fail
916    /// use boxddd::{JointEventIter, World};
917    ///
918    /// fn leak_joint_events(world: &World) -> JointEventIter<'_> {
919    ///     world.with_joint_events_view(|events| events)
920    /// }
921    /// ```
922    pub fn try_with_joint_events_view<T>(
923        &self,
924        f: impl FnOnce(JointEventIter<'_>) -> T,
925    ) -> Result<T> {
926        callback_state::check_not_in_callback()?;
927        let _guard = box3d_lock::lock();
928        self.check_world_valid_locked()?;
929        let raw = unsafe { ffi::b3World_GetJointEvents(self.raw()) };
930        let events = joint_event_slice(raw);
931        drop(_guard);
932        Ok(f(JointEventIter(events.iter())))
933    }
934
935    /// Borrows joint events for the duration of `f`.
936    ///
937    /// Panics if Box3D rejects the world handle. Use
938    /// [`Self::try_with_joint_events_view`] on fallible paths.
939    pub fn with_joint_events_view<T>(&self, f: impl FnOnce(JointEventIter<'_>) -> T) -> T {
940        self.try_with_joint_events_view(f)
941            .expect("invalid Box3D world")
942    }
943
944    /// Borrows raw Box3D joint events for the duration of `f`.
945    ///
946    /// # Safety
947    ///
948    /// The callback must not copy out raw event pointers or retain references to the slice
949    /// beyond its call. Raw `userData` pointers are application-owned and must only be
950    /// dereferenced when their validity is known.
951    pub unsafe fn try_with_joint_events_raw<T>(
952        &self,
953        f: impl FnOnce(&[ffi::b3JointEvent]) -> T,
954    ) -> Result<T> {
955        callback_state::check_not_in_callback()?;
956        let _guard = box3d_lock::lock();
957        self.check_world_valid_locked()?;
958        let raw = unsafe { ffi::b3World_GetJointEvents(self.raw()) };
959        let events = joint_event_slice(raw);
960        drop(_guard);
961        Ok(f(events))
962    }
963
964    /// Borrows raw Box3D joint events for the duration of `f`.
965    ///
966    /// # Safety
967    ///
968    /// See [`Self::try_with_joint_events_raw`].
969    pub unsafe fn with_joint_events_raw<T>(&self, f: impl FnOnce(&[ffi::b3JointEvent]) -> T) -> T {
970        unsafe { self.try_with_joint_events_raw(f) }.expect("invalid Box3D world")
971    }
972}