use crate::prelude::*;
use bevy::ecs::system::SystemParam;
use bevy::prelude::*;
use std::any::type_name;
use std::marker::PhantomData;
fn cleanup_reactor_data<T: EntityWorldReactor>(
In((id, entity)): In<(SystemCommand, Entity)>,
mut commands: Commands,
entities: Query<&EntityReactors>,
){
let Ok(reactor) = entities.get(entity) else { return };
if reactor.iter_reactors().find(|reactor_id| *reactor_id == id).is_some() { return }
commands.entity(entity).remove::<EntityWorldLocal<T>>();
}
#[derive(Resource)]
pub(crate) struct EntityWorldReactorRes<T: EntityWorldReactor>
{
sys_command: SystemCommand,
p: PhantomData<T>,
}
impl<T: EntityWorldReactor> EntityWorldReactorRes<T>
{
pub(crate) fn new(sys_command: SystemCommand) -> Self
{
Self{ sys_command, p: PhantomData::default() }
}
}
#[derive(Component)]
pub(crate) struct EntityWorldLocal<T: EntityWorldReactor>
{
data: T::Local,
}
impl<T: EntityWorldReactor> EntityWorldLocal<T>
{
fn new(data: T::Local) -> Self
{
Self{ data }
}
pub(crate) fn inner(&self) -> &T::Local
{
&self.data
}
pub(crate) fn inner_mut(&mut self) -> &mut T::Local
{
&mut self.data
}
}
pub trait EntityWorldReactor: Send + Sync + 'static
{
type Triggers: EntityTriggerBundle + ReactionTriggerBundle;
type Local: Send + Sync + 'static;
fn reactor(self) -> SystemCommandCallback;
}
#[derive(SystemParam)]
pub struct EntityReactor<'w, T: EntityWorldReactor>
{
inner: Option<ResMut<'w, EntityWorldReactorRes<T>>>,
}
impl<'w, T: EntityWorldReactor> EntityReactor<'w, T>
{
pub fn add(&self, c: &mut Commands, trigger_entity: Entity, data: T::Local) -> bool
{
let Some(inner) = &self.inner
else
{
tracing::warn!("failed adding listener, entity world reactor {:?} is missing; add it to your app with \
ReactAppExt::add_world_reactor", type_name::<T>());
return false;
};
let Some(mut ec) = c.get_entity(trigger_entity) else { return false };
ec.try_insert(EntityWorldLocal::<T>::new(data));
let triggers = <T as EntityWorldReactor>::Triggers::new_bundle(trigger_entity);
c.react().with(triggers, inner.sys_command, ReactorMode::Persistent);
true
}
pub fn remove(&self, c: &mut Commands, triggers: impl ReactionTriggerBundle) -> bool
{
let Some(inner) = &self.inner
else
{
tracing::warn!("failed removing triggers, entity world reactor {:?} is missing; add it to your app with \
ReactAppExt::add_world_reactor", type_name::<T>());
return false;
};
let token = RevokeToken::new_from(inner.sys_command, triggers);
c.react().revoke(token.clone());
for entity in token.iter_unique_entities()
{
c.syscall((token.id, entity), cleanup_reactor_data::<T>);
}
true
}
pub(crate) fn system(&self) -> Option<SystemCommand>
{
let Some(inner) = &self.inner
else
{
tracing::warn!("failed accessing entity world reactor {:?} because it is missing; add it to your app with \
ReactAppExt::add_entity_reactor", type_name::<T>());
return None;
};
Some(inner.sys_command)
}
}