use crate::interop::GodotNodeHandle;
use crate::plugins::core::PrePhysicsUpdate;
use crate::plugins::scene_tree::NodeEntityIndex;
use bevy_app::{App, Plugin};
use bevy_ecs::{
entity::Entity,
event::Event,
message::{Message, MessageReader, MessageWriter, message_update_system},
prelude::Resource,
schedule::IntoScheduleConfigs,
system::{Commands, Res, ResMut, SystemParam},
};
use crossbeam_channel::Receiver;
use godot::prelude::*;
use parking_lot::Mutex;
use std::collections::{HashMap, HashSet};
use tracing::trace;
pub const BODY_ENTERED: &str = "body_entered";
pub const BODY_EXITED: &str = "body_exited";
pub const AREA_ENTERED: &str = "area_entered";
pub const AREA_EXITED: &str = "area_exited";
pub const COLLISION_START_SIGNALS: &[&str] = &[BODY_ENTERED, AREA_ENTERED];
const COLLISION_NEIGHBOR_REBUILD_THRESHOLD: usize = 512;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Message, Event)]
pub struct CollisionStarted {
pub entity1: Entity,
pub entity2: Entity,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Message, Event)]
pub struct CollisionEnded {
pub entity1: Entity,
pub entity2: Entity,
}
#[derive(Resource, Default, Debug)]
pub struct CollisionState {
active_pairs: HashSet<(Entity, Entity)>,
started_this_frame: Vec<(Entity, Entity)>,
ended_this_frame: Vec<(Entity, Entity)>,
entity_collisions: HashMap<Entity, Vec<Entity>>,
}
impl CollisionState {
fn begin_frame(&mut self) {
self.started_this_frame.clear();
self.ended_this_frame.clear();
}
fn add_collision(&mut self, origin: Entity, target: Entity) -> bool {
self.add_collision_internal(origin, target, true)
}
fn add_collision_without_neighbors(&mut self, origin: Entity, target: Entity) -> bool {
self.add_collision_internal(origin, target, false)
}
fn add_collision_internal(
&mut self,
origin: Entity,
target: Entity,
update_neighbors: bool,
) -> bool {
let pair = normalize_pair(origin, target);
if self.active_pairs.insert(pair) {
self.started_this_frame.push(pair);
if update_neighbors {
self.entity_collisions
.entry(origin)
.or_default()
.push(target);
self.entity_collisions
.entry(target)
.or_default()
.push(origin);
}
true
} else {
false
}
}
fn remove_collision(&mut self, origin: Entity, target: Entity) -> bool {
self.remove_collision_internal(origin, target, true)
}
fn remove_collision_without_neighbors(&mut self, origin: Entity, target: Entity) -> bool {
self.remove_collision_internal(origin, target, false)
}
fn remove_collision_internal(
&mut self,
origin: Entity,
target: Entity,
update_neighbors: bool,
) -> bool {
let pair = normalize_pair(origin, target);
if self.active_pairs.remove(&pair) {
self.ended_this_frame.push(pair);
if update_neighbors {
if let Some(collisions) = self.entity_collisions.get_mut(&origin) {
collisions.retain(|&e| e != target);
}
if let Some(collisions) = self.entity_collisions.get_mut(&target) {
collisions.retain(|&e| e != origin);
}
}
true
} else {
false
}
}
fn rebuild_entity_collisions(&mut self) {
self.entity_collisions.clear();
self.entity_collisions.reserve(self.active_pairs.len() * 2);
for &(entity1, entity2) in &self.active_pairs {
self.entity_collisions
.entry(entity1)
.or_default()
.push(entity2);
self.entity_collisions
.entry(entity2)
.or_default()
.push(entity1);
}
}
pub fn contains(&self, a: Entity, b: Entity) -> bool {
self.active_pairs.contains(&normalize_pair(a, b))
}
pub fn colliding_with(&self, entity: Entity) -> &[Entity] {
self.entity_collisions
.get(&entity)
.map(|v| v.as_slice())
.unwrap_or(&[])
}
pub fn iter(&self) -> impl Iterator<Item = (Entity, Entity)> + '_ {
self.active_pairs.iter().copied()
}
pub fn started(&self) -> impl Iterator<Item = (Entity, Entity)> + '_ {
self.started_this_frame.iter().copied()
}
pub fn ended(&self) -> impl Iterator<Item = (Entity, Entity)> + '_ {
self.ended_this_frame.iter().copied()
}
pub fn is_empty(&self) -> bool {
self.active_pairs.is_empty()
}
pub fn len(&self) -> usize {
self.active_pairs.len()
}
}
#[inline]
fn normalize_pair(a: Entity, b: Entity) -> (Entity, Entity) {
if a < b { (a, b) } else { (b, a) }
}
#[derive(SystemParam)]
pub struct Collisions<'w> {
state: Res<'w, CollisionState>,
}
impl Collisions<'_> {
#[inline]
pub fn contains(&self, a: Entity, b: Entity) -> bool {
self.state.contains(a, b)
}
#[inline]
pub fn colliding_with(&self, entity: Entity) -> &[Entity] {
self.state.colliding_with(entity)
}
#[inline]
pub fn iter(&self) -> impl Iterator<Item = (Entity, Entity)> + '_ {
self.state.iter()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.state.is_empty()
}
#[inline]
pub fn len(&self) -> usize {
self.state.len()
}
}
#[doc(hidden)]
#[derive(Debug)]
pub struct RawCollisionMessage {
pub event_type: CollisionMessageType,
pub origin: GodotNodeHandle,
pub target: GodotNodeHandle,
}
#[derive(Resource)]
pub struct CollisionMessageReader(pub Mutex<Receiver<RawCollisionMessage>>);
impl CollisionMessageReader {
pub fn new(receiver: Receiver<RawCollisionMessage>) -> Self {
Self(Mutex::new(receiver))
}
}
#[doc(hidden)]
#[derive(Debug, GodotConvert)]
#[godot(via = GString)]
pub enum CollisionMessageType {
Started,
Ended,
}
#[derive(Default)]
pub struct GodotCollisionsPlugin;
impl Plugin for GodotCollisionsPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<CollisionState>()
.add_message::<CollisionStarted>()
.add_message::<CollisionEnded>()
.add_systems(
PrePhysicsUpdate,
(
process_godot_collisions.before(message_update_system),
trigger_collision_observers.after(process_godot_collisions),
),
);
}
}
fn process_godot_collisions(
events: Option<Res<CollisionMessageReader>>,
mut collision_state: ResMut<CollisionState>,
mut started_writer: MessageWriter<CollisionStarted>,
mut ended_writer: MessageWriter<CollisionEnded>,
node_index: Res<NodeEntityIndex>,
) {
collision_state.begin_frame();
let Some(events) = events else {
return;
};
let receiver = events.0.lock();
let use_rebuild_path = receiver.len() >= COLLISION_NEIGHBOR_REBUILD_THRESHOLD;
for event in receiver.try_iter() {
trace!(target: "godot_collisions", event = ?event);
let origin_entity = node_index.get(event.origin.instance_id());
let target_entity = node_index.get(event.target.instance_id());
let (origin, target) = match (origin_entity, target_entity) {
(Some(o), Some(t)) => (o, t),
_ => continue,
};
match event.event_type {
CollisionMessageType::Started => {
let changed = if use_rebuild_path {
collision_state.add_collision_without_neighbors(origin, target)
} else {
collision_state.add_collision(origin, target)
};
if changed {
started_writer.write(CollisionStarted {
entity1: origin,
entity2: target,
});
}
}
CollisionMessageType::Ended => {
let changed = if use_rebuild_path {
collision_state.remove_collision_without_neighbors(origin, target)
} else {
collision_state.remove_collision(origin, target)
};
if changed {
ended_writer.write(CollisionEnded {
entity1: origin,
entity2: target,
});
}
}
}
}
if use_rebuild_path {
collision_state.rebuild_entity_collisions();
}
}
fn trigger_collision_observers(
mut commands: Commands,
mut started_reader: MessageReader<CollisionStarted>,
mut ended_reader: MessageReader<CollisionEnded>,
) {
for &event in started_reader.read() {
commands.trigger(event);
}
for &event in ended_reader.read() {
commands.trigger(event);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_collision_state_add_remove() {
let mut state = CollisionState::default();
let e1 = Entity::from_bits(1);
let e2 = Entity::from_bits(2);
let e3 = Entity::from_bits(3);
state.add_collision(e1, e2);
assert!(state.contains(e1, e2));
assert!(state.contains(e2, e1)); assert!(!state.contains(e1, e3));
assert_eq!(state.colliding_with(e1), &[e2]);
assert_eq!(state.colliding_with(e2), &[e1]);
assert!(state.colliding_with(e3).is_empty());
assert_eq!(state.started_this_frame.len(), 1);
state.remove_collision(e1, e2);
assert!(!state.contains(e1, e2));
assert!(state.colliding_with(e1).is_empty());
assert_eq!(state.ended_this_frame.len(), 1);
}
#[test]
fn test_collision_state_begin_frame() {
let mut state = CollisionState::default();
let e1 = Entity::from_bits(1);
let e2 = Entity::from_bits(2);
state.add_collision(e1, e2);
assert_eq!(state.started_this_frame.len(), 1);
state.begin_frame();
assert!(state.started_this_frame.is_empty());
assert!(state.ended_this_frame.is_empty());
assert!(state.contains(e1, e2));
}
#[test]
fn test_normalize_pair() {
let e1 = Entity::from_bits(1);
let e2 = Entity::from_bits(2);
assert_eq!(normalize_pair(e1, e2), normalize_pair(e2, e1));
}
#[test]
fn test_collision_state_multiple_collisions() {
let mut state = CollisionState::default();
let e1 = Entity::from_bits(1);
let e2 = Entity::from_bits(2);
let e3 = Entity::from_bits(3);
state.add_collision(e1, e2);
state.add_collision(e1, e3);
let colliding = state.colliding_with(e1);
assert_eq!(colliding.len(), 2);
assert!(colliding.contains(&e2));
assert!(colliding.contains(&e3));
assert_eq!(state.colliding_with(e2), &[e1]);
assert_eq!(state.colliding_with(e3), &[e1]);
}
#[test]
fn test_duplicate_collision_ignored() {
let mut state = CollisionState::default();
let e1 = Entity::from_bits(1);
let e2 = Entity::from_bits(2);
state.add_collision(e1, e2);
state.add_collision(e1, e2); state.add_collision(e2, e1);
assert_eq!(state.len(), 1);
assert_eq!(state.started_this_frame.len(), 1);
}
}