use crate::prelude::*;
use bevy::prelude::*;
fn validate_rc(world: &mut World)
{
if !(
world.contains_resource::<ReactCache>() &&
world.contains_resource::<AutoDespawner>()
)
{
panic!("failed accessing ReactCommands, ReactPlugin is missing; you may need to reorder your \
plugins so ReactPlugin is added sooner");
}
}
fn register_reactors<T: ReactionTriggerBundle>(
In((triggers, syscommand, mode)): In<(T, SystemCommand, ReactorMode)>,
mut commands: Commands,
despawner: Res<AutoDespawner>,
){
let handle = mode.prepare(&despawner, syscommand);
triggers.register_triggers(&mut commands, &handle);
}
fn revoke_entity_reactor(
entity : Entity,
rtype : EntityReactionType,
reactor_id : SystemCommand,
reactors : &mut Query<&mut EntityReactors>,
){
let Ok(mut entity_reactors) = reactors.get_mut(entity) else { return; };
entity_reactors.remove(rtype, reactor_id);
}
fn revoke_reactor(
In(token) : In<RevokeToken>,
mut cache : ResMut<ReactCache>,
mut reactors : Query<&mut EntityReactors>,
){
let id = token.id;
for reactor_type in token.reactors.iter()
{
match *reactor_type
{
ReactorType::EntityInsertion(entity, comp_id) =>
{
revoke_entity_reactor(entity, EntityReactionType::Insertion(comp_id), id, &mut reactors);
}
ReactorType::EntityMutation(entity, comp_id) =>
{
revoke_entity_reactor(entity, EntityReactionType::Mutation(comp_id), id, &mut reactors);
}
ReactorType::EntityRemoval(entity, comp_id) =>
{
revoke_entity_reactor(entity, EntityReactionType::Removal(comp_id), id, &mut reactors);
}
ReactorType::EntityEvent(entity, event_id) =>
{
revoke_entity_reactor(entity, EntityReactionType::Event(event_id), id, &mut reactors);
}
ReactorType::AnyEntityEvent(event_id) =>
{
cache.revoke_any_entity_event_reactor(event_id, id);
}
ReactorType::ComponentInsertion(comp_id) =>
{
cache.revoke_component_reactor(EntityReactionType::Insertion(comp_id), id);
}
ReactorType::ComponentMutation(comp_id) =>
{
cache.revoke_component_reactor(EntityReactionType::Mutation(comp_id), id);
}
ReactorType::ComponentRemoval(comp_id) =>
{
cache.revoke_component_reactor(EntityReactionType::Removal(comp_id), id);
}
ReactorType::ResourceMutation(res_id) =>
{
cache.revoke_resource_mutation_reactor(res_id, id);
}
ReactorType::Broadcast(event_id) =>
{
cache.revoke_broadcast_reactor(event_id, id);
}
ReactorType::Despawn(entity) =>
{
cache.revoke_despawn_reactor(entity, id);
}
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum ReactorMode
{
Persistent,
Cleanup,
Revokable,
}
impl ReactorMode
{
fn prepare(&self, despawner: &AutoDespawner, sys_command: SystemCommand) -> ReactorHandle
{
match self
{
Self::Persistent => ReactorHandle::Persistent(sys_command),
Self::Cleanup |
Self::Revokable => ReactorHandle::AutoDespawn(despawner.prepare(*sys_command)),
}
}
}
pub struct ReactCommands<'w, 's>
{
pub(crate) commands: Commands<'w, 's>,
}
impl<'w, 's> ReactCommands<'w, 's>
{
pub fn commands(&mut self) -> Commands<'_, '_>
{
self.commands.reborrow()
}
pub fn reborrow(&mut self) -> ReactCommands<'_, '_>
{
ReactCommands{ commands: self.commands() }
}
pub fn insert<C: ReactComponent>(&mut self, entity: Entity, component: C)
{
let Some(mut entity_commands) = self.commands.get_entity(entity) else { return; };
entity_commands.try_insert( React{ entity, component } );
self.commands.syscall_with_validation(entity, ReactCache::schedule_insertion_reaction::<C>, validate_rc);
}
pub fn broadcast<E: Send + Sync + 'static>(&mut self, event: E)
{
self.commands.syscall_with_validation(event, ReactCache::schedule_broadcast_reaction::<E>, validate_rc);
}
pub fn entity_event<E: Send + Sync + 'static>(&mut self, entity: Entity, event: E)
{
self.commands.syscall_with_validation(
(entity, event),
ReactCache::schedule_entity_event_reaction::<E>,
validate_rc
);
}
pub fn trigger_resource_mutation<R: ReactResource + Send + Sync + 'static>(&mut self)
{
self.commands.syscall_with_validation((), ReactCache::schedule_resource_mutation_reaction::<R>, validate_rc);
}
pub fn revoke(&mut self, token: RevokeToken)
{
self.commands.syscall_with_validation(token, revoke_reactor, validate_rc);
}
pub fn on<M>(
&mut self,
triggers : impl ReactionTriggerBundle,
reactor : impl IntoSystem<(), (), M> + Send + Sync + 'static
){
let sys_command = self.commands.spawn_system_command(reactor);
let _ = self.with(triggers, sys_command, ReactorMode::Cleanup);
}
pub fn on_persistent<M>(
&mut self,
triggers : impl ReactionTriggerBundle,
reactor : impl IntoSystem<(), (), M> + Send + Sync + 'static
) -> SystemCommand
{
let sys_command = self.commands.spawn_system_command(reactor);
self.with(triggers, sys_command, ReactorMode::Persistent);
sys_command
}
pub fn on_revokable<M>(
&mut self,
triggers : impl ReactionTriggerBundle,
reactor : impl IntoSystem<(), (), M> + Send + Sync + 'static
) -> RevokeToken
{
let sys_command = self.commands.spawn_system_command(reactor);
self.with(triggers, sys_command, ReactorMode::Revokable).unwrap()
}
pub fn with(
&mut self,
triggers : impl ReactionTriggerBundle,
sys_command : SystemCommand,
mode : ReactorMode,
) -> Option<RevokeToken>
{
self.commands.syscall_with_validation((triggers, sys_command, mode), register_reactors, validate_rc);
match mode
{
ReactorMode::Revokable => Some(RevokeToken::new_from(sys_command, triggers)),
_ => None,
}
}
pub fn once<M, S: IntoSystem<(), (), M> + Send + Sync + 'static>(
&mut self,
triggers : impl ReactionTriggerBundle,
reactor : S
) -> RevokeToken
{
let entity = self.commands.spawn_empty().id();
let syscommand = SystemCommand(entity);
let mode = ReactorMode::Revokable;
let revoke_token = RevokeToken::new_from(syscommand, triggers);
self.commands.syscall_with_validation((triggers, syscommand, mode), register_reactors, validate_rc);
let revoke_token_clone = revoke_token.clone();
let mut once_reactor = Some(move |world: &mut World, cleanup: SystemCommandCleanup|
{
let mut callback = RawCallbackSystem::new(reactor);
callback.run_with_cleanup(world, (), move |w| cleanup.run(w));
world.get_entity_mut(entity).ok().map(|e| e.despawn());
world.react(|rc| rc.revoke(revoke_token_clone));
});
let once_system = move |world: &mut World, cleanup: SystemCommandCleanup|
{
if let Some(reactor) = once_reactor.take() { (reactor)(world, cleanup); };
};
self.commands.entity(entity).try_insert(SystemCommandStorage::new(SystemCommandCallback::with(once_system)));
revoke_token
}
}