use std::collections::VecDeque;
use bevy::{
app::{App, FixedUpdate, Plugin},
ecs::{
component::Component,
entity::{Entity, EntityHashSet},
error::Result,
message::MessageReader,
name::Name,
reflect::ReflectComponent,
schedule::IntoScheduleConfigs,
system::{Commands, Query},
},
log::warn,
platform::collections::HashSet,
prelude::Deref,
reflect::Reflect,
};
use crate::{ConnectionUpdateEvent, Connections, EntityGraphSystem};
pub struct ConnectedComponentPlugin;
impl Plugin for ConnectedComponentPlugin {
fn build(&self, app: &mut App) {
app.register_type::<HashSet<Entity>>()
.register_type::<ConnectedComponent>()
.register_type::<InConnectedComponent>()
.add_systems(
FixedUpdate,
(InConnectedComponent::update, ConnectedComponent::update)
.chain()
.in_set(EntityGraphSystem::ComputeConnectedComponents),
);
}
}
#[derive(Component, Reflect, Debug)]
#[reflect(Component)]
#[require(Connections)]
#[relationship(relationship_target = ConnectedComponent)]
pub struct InConnectedComponent(pub Entity);
impl InConnectedComponent {
pub fn get_or_spawn_connected_component(
entity: Entity,
in_connected_components: &Query<Option<&InConnectedComponent>>,
used_components: &mut EntityHashSet,
attached_to: &Connections,
commands: &mut Commands,
) -> Entity {
if let Ok(Some(in_connected_component)) = in_connected_components.get(entity) {
if used_components.insert(in_connected_component.0) {
return in_connected_component.0;
}
}
for &attachment in attached_to.iter() {
if let Ok(Some(in_connected_component)) = in_connected_components.get(attachment) {
if used_components.insert(in_connected_component.0) {
return in_connected_component.0;
}
}
}
let entity = commands.spawn(ConnectedComponent::default()).id();
used_components.insert(entity);
entity
}
pub fn update(
mut updates: MessageReader<ConnectionUpdateEvent>,
in_connected_components: Query<Option<&InConnectedComponent>>,
connections: Query<&Connections>,
mut commands: Commands,
) -> Result {
let mut visited = EntityHashSet::new();
let mut used_components = EntityHashSet::new();
let mut updated = EntityHashSet::new();
for &ConnectionUpdateEvent(entity) in updates.read() {
updated.insert(entity);
}
for entity in updated {
let Ok(conns) = connections.get(entity) else {
continue;
};
if conns.is_empty() {
commands.entity(entity).remove::<InConnectedComponent>();
continue;
}
if visited.contains(&entity) {
continue;
}
let connected_component = Self::get_or_spawn_connected_component(
entity,
&in_connected_components,
&mut used_components,
conns,
&mut commands,
);
let mut queue = VecDeque::<Entity>::new();
queue.push_front(entity);
while let Some(entity) = queue.pop_back() {
if !visited.insert(entity) {
continue;
}
let Ok(mut entity_commands) = commands.get_entity(entity) else {
warn!("Invalid entity in connections: {entity:?}");
continue;
};
entity_commands.insert(InConnectedComponent(connected_component));
if let Ok(conns) = connections.get(entity) {
for &next in conns.iter().filter(|&entity| !visited.contains(entity)) {
queue.push_front(next);
}
}
}
}
Ok(())
}
}
#[derive(Component, Reflect, Debug, Default, Deref)]
#[reflect(Component)]
#[require(Name::new("ConnectedComponent"))]
#[relationship_target(relationship = InConnectedComponent)]
pub struct ConnectedComponent(EntityHashSet);
impl ConnectedComponent {
pub fn update(query: Query<(Entity, &ConnectedComponent)>, mut commands: Commands) {
for (entity, connected_component) in query.iter() {
if connected_component.0.is_empty() {
commands.entity(entity).despawn();
}
}
}
}