#![allow(missing_docs)]
#![allow(dead_code)]
#[derive(Debug, Clone, PartialEq)]
pub struct ContactInfo {
pub body_a: usize,
pub body_b: usize,
pub contact_normal: [f64; 3],
pub penetration_depth: f64,
pub contact_point: [f64; 3],
}
#[derive(Debug, Clone, PartialEq)]
pub enum PhysicsEvent {
CollisionBegin {
body_a: usize,
body_b: usize,
contact_normal: [f64; 3],
penetration_depth: f64,
},
CollisionEnd { body_a: usize, body_b: usize },
BodySleep { body: usize },
BodyWake { body: usize },
ConstraintBreak {
constraint_id: usize,
accumulated_impulse: f64,
},
BodyEnterRegion {
body: usize,
region_id: usize,
},
BodyLeaveRegion { body: usize, region_id: usize },
StepCompleted {
step: u64,
sim_time: f64,
dt: f64,
},
UserDefined {
id: u32,
data: Vec<u8>,
},
}
impl PhysicsEvent {
pub fn kind_name(&self) -> &'static str {
match self {
PhysicsEvent::CollisionBegin { .. } => "CollisionBegin",
PhysicsEvent::CollisionEnd { .. } => "CollisionEnd",
PhysicsEvent::BodySleep { .. } => "BodySleep",
PhysicsEvent::BodyWake { .. } => "BodyWake",
PhysicsEvent::ConstraintBreak { .. } => "ConstraintBreak",
PhysicsEvent::BodyEnterRegion { .. } => "BodyEnterRegion",
PhysicsEvent::BodyLeaveRegion { .. } => "BodyLeaveRegion",
PhysicsEvent::StepCompleted { .. } => "StepCompleted",
PhysicsEvent::UserDefined { .. } => "UserDefined",
}
}
pub fn is_collision(&self) -> bool {
matches!(
self,
PhysicsEvent::CollisionBegin { .. } | PhysicsEvent::CollisionEnd { .. }
)
}
}
impl std::fmt::Display for PhysicsEvent {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PhysicsEvent::CollisionBegin {
body_a,
body_b,
penetration_depth,
..
} => write!(
f,
"CollisionBegin({body_a}↔{body_b}, depth={penetration_depth:.4}m)"
),
PhysicsEvent::CollisionEnd { body_a, body_b } => {
write!(f, "CollisionEnd({body_a}↔{body_b})")
}
PhysicsEvent::BodySleep { body } => write!(f, "BodySleep(body={body})"),
PhysicsEvent::BodyWake { body } => write!(f, "BodyWake(body={body})"),
PhysicsEvent::ConstraintBreak {
constraint_id,
accumulated_impulse,
} => write!(
f,
"ConstraintBreak(id={constraint_id}, impulse={accumulated_impulse:.2}N·s)"
),
PhysicsEvent::BodyEnterRegion { body, region_id } => {
write!(f, "BodyEnterRegion(body={body}, region={region_id})")
}
PhysicsEvent::BodyLeaveRegion { body, region_id } => {
write!(f, "BodyLeaveRegion(body={body}, region={region_id})")
}
PhysicsEvent::StepCompleted { step, sim_time, dt } => write!(
f,
"StepCompleted(step={step}, t={sim_time:.4}s, dt={dt:.6}s)"
),
PhysicsEvent::UserDefined { id, data } => {
write!(f, "UserDefined(id={id}, {n}B)", n = data.len())
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ListenerId(u64);
type ListenerFn = Box<dyn Fn(&PhysicsEvent) + Send + 'static>;
pub struct EventBus {
queue: Vec<PhysicsEvent>,
listeners: Vec<(ListenerId, ListenerFn)>,
next_id: u64,
published_count: u64,
dispatch_count: u64,
}
impl Default for EventBus {
fn default() -> Self {
Self::new()
}
}
impl EventBus {
pub fn new() -> Self {
Self {
queue: Vec::new(),
listeners: Vec::new(),
next_id: 0,
published_count: 0,
dispatch_count: 0,
}
}
pub fn publish(&mut self, event: PhysicsEvent) {
self.published_count += 1;
self.queue.push(event);
}
pub fn publish_all(&mut self, events: impl IntoIterator<Item = PhysicsEvent>) {
for e in events {
self.publish(e);
}
}
pub fn subscribe(&mut self, f: impl Fn(&PhysicsEvent) + Send + 'static) -> ListenerId {
let id = ListenerId(self.next_id);
self.next_id += 1;
self.listeners.push((id, Box::new(f)));
id
}
pub fn subscribe_filtered(
&mut self,
predicate: impl Fn(&PhysicsEvent) -> bool + Send + 'static,
handler: impl Fn(&PhysicsEvent) + Send + 'static,
) -> ListenerId {
self.subscribe(move |ev| {
if predicate(ev) {
handler(ev);
}
})
}
pub fn unsubscribe(&mut self, id: ListenerId) -> bool {
if let Some(pos) = self.listeners.iter().position(|(lid, _)| *lid == id) {
let _ = self.listeners.remove(pos);
true
} else {
false
}
}
pub fn flush(&mut self) {
for event in &self.queue {
for (_, listener) in &self.listeners {
listener(event);
self.dispatch_count += 1;
}
}
self.queue.clear();
}
pub fn drain(&mut self) -> Vec<PhysicsEvent> {
std::mem::take(&mut self.queue)
}
pub fn pending(&self) -> &[PhysicsEvent] {
&self.queue
}
pub fn pending_count(&self) -> usize {
self.queue.len()
}
pub fn clear(&mut self) {
self.queue.clear();
}
pub fn listener_count(&self) -> usize {
self.listeners.len()
}
pub fn total_published(&self) -> u64 {
self.published_count
}
pub fn total_dispatched(&self) -> u64 {
self.dispatch_count
}
pub fn count_pending<F>(&self, predicate: F) -> usize
where
F: Fn(&PhysicsEvent) -> bool,
{
self.queue.iter().filter(|e| predicate(e)).count()
}
}
impl std::fmt::Debug for EventBus {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("EventBus")
.field("pending_count", &self.queue.len())
.field("listener_count", &self.listeners.len())
.field("published_count", &self.published_count)
.field("dispatch_count", &self.dispatch_count)
.finish()
}
}
pub fn collect_collision_begins(events: &[PhysicsEvent]) -> Vec<(usize, usize)> {
events
.iter()
.filter_map(|e| {
if let PhysicsEvent::CollisionBegin { body_a, body_b, .. } = e {
Some((*body_a, *body_b))
} else {
None
}
})
.collect()
}
pub fn region_crossing_events(events: &[PhysicsEvent]) -> Vec<(usize, usize, bool)> {
events
.iter()
.filter_map(|e| match e {
PhysicsEvent::BodyEnterRegion { body, region_id } => Some((*body, *region_id, true)),
PhysicsEvent::BodyLeaveRegion { body, region_id } => Some((*body, *region_id, false)),
_ => None,
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::{Arc, Mutex};
#[test]
fn publish_and_drain() {
let mut bus = EventBus::new();
bus.publish(PhysicsEvent::BodySleep { body: 0 });
bus.publish(PhysicsEvent::BodyWake { body: 1 });
assert_eq!(bus.pending_count(), 2);
let events = bus.drain();
assert_eq!(events.len(), 2);
assert_eq!(bus.pending_count(), 0);
}
#[test]
fn flush_calls_listeners() {
let mut bus = EventBus::new();
let counter = Arc::new(Mutex::new(0u32));
let c = Arc::clone(&counter);
bus.subscribe(move |_| {
*c.lock().unwrap_or_else(|e| e.into_inner()) += 1;
});
bus.publish(PhysicsEvent::BodySleep { body: 0 });
bus.publish(PhysicsEvent::BodySleep { body: 1 });
bus.flush();
assert_eq!(*counter.lock().unwrap_or_else(|e| e.into_inner()), 2);
assert_eq!(bus.pending_count(), 0);
}
#[test]
fn unsubscribe_stops_delivery() {
let mut bus = EventBus::new();
let counter = Arc::new(Mutex::new(0u32));
let c = Arc::clone(&counter);
let id = bus.subscribe(move |_| {
*c.lock().unwrap_or_else(|e| e.into_inner()) += 1;
});
bus.publish(PhysicsEvent::BodySleep { body: 0 });
bus.flush();
assert_eq!(*counter.lock().unwrap_or_else(|e| e.into_inner()), 1);
bus.unsubscribe(id);
bus.publish(PhysicsEvent::BodySleep { body: 0 });
bus.flush();
assert_eq!(*counter.lock().unwrap_or_else(|e| e.into_inner()), 1); }
#[test]
fn filtered_subscription() {
let mut bus = EventBus::new();
let hits = Arc::new(Mutex::new(Vec::new()));
let h = Arc::clone(&hits);
bus.subscribe_filtered(
|ev| matches!(ev, PhysicsEvent::CollisionBegin { .. }),
move |ev| {
h.lock()
.unwrap_or_else(|e| e.into_inner())
.push(ev.kind_name());
},
);
bus.publish(PhysicsEvent::CollisionBegin {
body_a: 0,
body_b: 1,
contact_normal: [0.0, 1.0, 0.0],
penetration_depth: 0.1,
});
bus.publish(PhysicsEvent::BodySleep { body: 0 });
bus.flush();
let hits = hits.lock().unwrap_or_else(|e| e.into_inner());
assert_eq!(hits.len(), 1);
assert_eq!(hits[0], "CollisionBegin");
}
#[test]
fn multiple_listeners_order() {
let mut bus = EventBus::new();
let log = Arc::new(Mutex::new(Vec::new()));
for i in 0..3u32 {
let l = Arc::clone(&log);
bus.subscribe(move |_| {
l.lock().unwrap_or_else(|e| e.into_inner()).push(i);
});
}
bus.publish(PhysicsEvent::BodyWake { body: 0 });
bus.flush();
assert_eq!(
*log.lock().unwrap_or_else(|e| e.into_inner()),
vec![0, 1, 2]
);
}
#[test]
fn clear_discards_queue() {
let mut bus = EventBus::new();
bus.publish(PhysicsEvent::BodySleep { body: 0 });
bus.clear();
assert_eq!(bus.pending_count(), 0);
}
#[test]
fn event_display() {
let ev = PhysicsEvent::CollisionBegin {
body_a: 3,
body_b: 7,
contact_normal: [0.0, 1.0, 0.0],
penetration_depth: 0.005,
};
let s = format!("{}", ev);
assert!(s.contains("CollisionBegin"));
assert!(s.contains('3'));
assert!(s.contains('7'));
}
#[test]
fn collect_collision_begins_helper() {
let events = vec![
PhysicsEvent::CollisionBegin {
body_a: 0,
body_b: 1,
contact_normal: [0., 1., 0.],
penetration_depth: 0.01,
},
PhysicsEvent::BodySleep { body: 2 },
PhysicsEvent::CollisionBegin {
body_a: 3,
body_b: 4,
contact_normal: [1., 0., 0.],
penetration_depth: 0.02,
},
];
let pairs = collect_collision_begins(&events);
assert_eq!(pairs, vec![(0, 1), (3, 4)]);
}
#[test]
fn total_stats() {
let mut bus = EventBus::new();
bus.subscribe(|_| {});
bus.publish(PhysicsEvent::BodySleep { body: 0 });
bus.publish(PhysicsEvent::BodyWake { body: 0 });
bus.flush();
assert_eq!(bus.total_published(), 2);
assert_eq!(bus.total_dispatched(), 2);
}
}