use std::marker::PhantomData;
use std::time::Duration;
use bevy::prelude::*;
use bevy::platform::collections::HashSet;
use crate::StateChildren;
use crate::{guards::Guards, EnterState, Transition, active::Active, StateChildOf, StateMachine, ExitState, Parallel};
use crate::state_component::Reset;
#[derive(Component, Default, Debug, PartialEq, Eq, Reflect)]
#[relationship_target(relationship = Source, linked_spawn)]
#[reflect(Component, FromWorld, Default)]
pub struct Transitions(Vec<Entity>);
impl<'a> IntoIterator for &'a Transitions {
type Item = <Self::IntoIter as Iterator>::Item;
type IntoIter = std::slice::Iter<'a, Entity>;
#[inline(always)]
fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}
impl Transitions {
pub fn new() -> Self {
Self(Vec::new())
}
}
#[derive(Component, Clone, PartialEq, Eq, Debug, Reflect)]
#[relationship(relationship_target = Transitions)]
#[reflect(Component, PartialEq, Debug, FromWorld, Clone)]
pub struct Source(#[entities] pub Entity);
impl FromWorld for Source {
#[inline(always)]
fn from_world(_world: &mut World) -> Self {
Source(Entity::PLACEHOLDER)
}
}
#[derive(Component, Reflect, Debug)]
#[reflect(Component)]
pub struct Target(#[entities] pub Entity);
#[derive(Component, Reflect, Default, Clone, Copy, Debug)]
#[reflect(Component, Default)]
pub enum EdgeKind {
#[default]
External,
Internal,
}
#[derive(Component, Reflect, Default, Debug)]
#[reflect(Component)]
#[require(EdgeKind)]
pub struct AlwaysEdge;
#[derive(Component, Reflect, Default)]
#[reflect(Component, Default)]
pub struct After {
pub duration: Duration,
}
impl After {
#[inline]
pub fn new(duration: Duration) -> Self { Self { duration } }
pub fn from_f32(duration: f32) -> Self { Self { duration: Duration::from_secs_f32(duration) } }
}
#[derive(Component)]
pub struct EdgeTimer(pub Timer);
#[derive(Component)]
pub struct PendingEvent<E: EntityEvent + Clone> {
pub event: E,
}
#[derive(EntityEvent, Reflect, Clone)]
#[reflect(Default)]
pub struct NoEvent(Entity);
impl Default for NoEvent {
fn default() -> Self {
Self(Entity::PLACEHOLDER)
}
}
pub use bevy_gearbox_macros::SimpleTransition;
fn cleanup_edge_timer_and_pending<E: EntityEvent + Clone + 'static>(
commands: &mut Commands,
edge: Entity,
) {
commands.entity(edge).remove::<EdgeTimer>().remove::<PendingEvent<E>>();
}
pub trait TransitionEvent: EntityEvent + Clone {
type ExitEvent: EntityEvent + Clone = NoEvent;
type EffectEvent: EntityEvent + Clone = NoEvent;
type EntryEvent: EntityEvent + Clone = NoEvent;
fn to_exit_event(&self) -> Option<Self::ExitEvent> { None }
fn to_effect_event(&self) -> Option<Self::EffectEvent> { None }
fn to_entry_event(&self) -> Option<Self::EntryEvent> { None }
}
#[derive(Clone, Default)]
pub struct PhaseEvents<Exit: EntityEvent + Clone = NoEvent, Effect: EntityEvent + Clone = NoEvent, Entry: EntityEvent + Clone = NoEvent> {
pub exit: Option<Exit>,
pub effect: Option<Effect>,
pub entry: Option<Entry>,
}
pub trait PhasePayload: Clone + Send + Sync + 'static {
fn on_exit(&self, _commands: &mut Commands, _source: Entity, _children: &Query<&StateChildren>, _state_machine: &StateMachine) {}
fn on_effect(&self, _commands: &mut Commands, _edge: Entity, _children: &Query<&StateChildren>, _state_machine: &StateMachine) {}
fn on_entry(&self, _commands: &mut Commands, _target: Entity, _children: &Query<&StateChildren>, _state_machine: &StateMachine) {}
}
impl PhasePayload for () {}
impl<Exit, Effect, Entry> PhasePayload for PhaseEvents<Exit, Effect, Entry>
where
Exit: EntityEvent + Clone,
Effect: EntityEvent + Clone,
Entry: EntityEvent + Clone,
for<'a> <Exit as Event>::Trigger<'a>: Default,
for<'a> <Effect as Event>::Trigger<'a>: Default,
for<'a> <Entry as Event>::Trigger<'a>: Default,
{
fn on_exit(&self, commands: &mut Commands, source: Entity, children: &Query<&StateChildren>, state_machine: &StateMachine) {
if let Some(mut ev) = self.exit.clone() {
*ev.event_target_mut() = source;
commands.trigger(ev);
}
for child in children.iter_descendants(source) {
if !state_machine.is_active(&child) { continue; }
if let Some(mut ev) = self.exit.clone() {
*ev.event_target_mut() = child;
commands.trigger(ev);
}
}
}
fn on_effect(&self, commands: &mut Commands, edge: Entity, children: &Query<&StateChildren>, state_machine: &StateMachine) {
if let Some(mut ev) = self.effect.clone() {
*ev.event_target_mut() = edge;
commands.trigger(ev);
}
for child in children.iter_descendants(edge) {
if !state_machine.is_active(&child) { continue; }
if let Some(mut ev) = self.effect.clone() {
*ev.event_target_mut() = child;
commands.trigger(ev);
}
}
}
fn on_entry(&self, commands: &mut Commands, target: Entity, children: &Query<&StateChildren>, state_machine: &StateMachine) {
if let Some(mut ev) = self.entry.clone() {
*ev.event_target_mut() = target;
commands.trigger(ev);
}
for child in children.iter_descendants(target) {
if !state_machine.is_active(&child) { continue; }
if let Some(mut ev) = self.entry.clone() {
*ev.event_target_mut() = child;
commands.trigger(ev);
}
}
}
}
pub trait TransitionEventAppExt {
fn add_transition_event<E: TransitionEvent + Clone + 'static>(&mut self) -> &mut Self
where
for<'a> <E as Event>::Trigger<'a>: Default,
for<'a> <<E as TransitionEvent>::ExitEvent as Event>::Trigger<'a>: Default,
for<'a> <<E as TransitionEvent>::EffectEvent as Event>::Trigger<'a>: Default,
for<'a> <<E as TransitionEvent>::EntryEvent as Event>::Trigger<'a>: Default;
}
impl TransitionEventAppExt for App {
fn add_transition_event<E: TransitionEvent + Clone + 'static>(&mut self) -> &mut Self
where
for<'a> <E as Event>::Trigger<'a>: Default,
for<'a> <<E as TransitionEvent>::ExitEvent as Event>::Trigger<'a>: Default,
for<'a> <<E as TransitionEvent>::EffectEvent as Event>::Trigger<'a>: Default,
for<'a> <<E as TransitionEvent>::EntryEvent as Event>::Trigger<'a>: Default,
{
self.add_observer(edge_event_listener::<E>)
.add_observer(crate::transition_observer::<PhaseEvents<E::ExitEvent, E::EffectEvent, E::EntryEvent>>)
.add_systems(Update, tick_after_event_timers::<E>)
.add_observer(cancel_pending_event_on_exit::<E>)
.add_observer(replay_deferred_event::<E>)
}
}
fn validate_edge_basic(
edge: Entity,
q_guards: &Query<&Guards>,
q_target: &Query<&Target>,
) -> bool {
if let Ok(guards) = q_guards.get(edge) {
if !guards.check() { return false; }
}
q_target.get(edge).is_ok()
}
fn try_fire_first_matching_edge_generic<E: TransitionEvent + Clone>(
source: Entity,
event: &E,
q_transitions: &Query<&Transitions>,
q_listener: &Query<&EventEdge<E>>,
q_edge_target: &Query<&Target>,
q_guards: &Query<&Guards>,
q_child_of: &Query<&StateChildOf>,
q_defer: &mut Query<&mut DeferEvent<E>>,
q_active: &Query<(), With<Active>>,
q_after: &Query<&After>,
q_timer: &mut Query<&mut EdgeTimer>,
commands: &mut Commands,
) -> bool {
if let Ok(mut defer_event) = q_defer.get_mut(source) {
if q_active.get(source).is_ok() {
defer_event.defer_event(event.clone());
return false;
}
}
let Ok(transitions) = q_transitions.get(source) else { return false; };
for edge in transitions.into_iter().copied() {
if q_listener.get(edge).is_err() { continue; }
if !validate_edge_basic(edge, q_guards, q_edge_target) { continue; }
if let Ok(after) = q_after.get(edge) {
if let Ok(mut timer) = q_timer.get_mut(edge) {
timer.0.set_duration(after.duration);
timer.0.reset();
} else {
commands.entity(edge).insert(EdgeTimer(Timer::new(after.duration, TimerMode::Once)));
}
commands.entity(edge).insert(PendingEvent::<E> { event: event.clone() });
return true;
}
let payload = PhaseEvents {
exit: event.to_exit_event(),
effect: event.to_effect_event(),
entry: event.to_entry_event(),
};
let root = q_child_of.root_ancestor(source);
commands.trigger(Transition { machine: root, source, edge, payload });
return true;
}
false
}
#[derive(Reflect, Component)]
#[reflect(Component)]
#[require(EdgeKind)]
pub struct EventEdge<E: EntityEvent> {
#[reflect(ignore)]
_marker: PhantomData<E>,
}
impl<E: EntityEvent> Default for EventEdge<E> {
fn default() -> Self {
Self { _marker: PhantomData }
}
}
#[derive(Component)]
pub struct DeferEvent<E: EntityEvent> {
pub deferred: Option<E>,
}
impl<E: EntityEvent> Default for DeferEvent<E> {
fn default() -> Self {
Self { deferred: None }
}
}
impl<E: EntityEvent> DeferEvent<E> {
pub fn new() -> Self {
Self::default()
}
pub fn defer_event(&mut self, event: E) {
self.deferred = Some(event);
}
pub fn take_deferred(&mut self) -> Option<E> {
std::mem::take(&mut self.deferred)
}
}
#[derive(Component, Reflect, Default)]
#[reflect(Component, Default)]
pub struct ResetEdge(pub ResetScope);
#[derive(Reflect, Default, Clone, Copy)]
pub enum ResetScope { #[default] Source, Target, Both }
pub fn always_edge_listener(
enter_state: On<EnterState>,
q_transitions: Query<&Transitions>,
q_always: Query<(), With<AlwaysEdge>>,
q_edge_target: Query<&Target>,
q_guards: Query<&Guards>,
q_after: Query<&After>,
q_child_of: Query<&StateChildOf>,
mut commands: Commands,
){
let source = enter_state.target;
let Ok(transitions) = q_transitions.get(source) else { return; };
for edge in transitions.into_iter().copied() {
if q_always.get(edge).is_err() { continue; }
if q_after.get(edge).is_ok() { continue; }
if !validate_edge_basic(edge, &q_guards, &q_edge_target) { continue; }
let root = q_child_of.root_ancestor(source);
commands.trigger(Transition { machine: root, source, edge, payload: () });
break;
}
}
fn find_parallel_region_root(
state: Entity,
q_child_of: &Query<&StateChildOf>,
q_parallel: &Query<&Parallel>,
) -> Entity {
let mut previous_ancestor = state;
for ancestor in q_child_of.iter_ancestors(state) {
if q_parallel.contains(ancestor) {
return previous_ancestor;
}
previous_ancestor = ancestor;
}
state
}
fn edge_event_listener<E: TransitionEvent + Clone>(
transition_event: On<E>,
q_transitions: Query<&Transitions>,
q_listener: Query<&EventEdge<E>>,
q_edge_target: Query<&Target>,
q_guards: Query<&Guards>,
q_child_of: Query<&StateChildOf>,
q_sm: Query<&StateMachine>,
mut q_defer: Query<&mut DeferEvent<E>>,
q_active: Query<(), With<Active>>,
q_parallel: Query<&Parallel>,
q_after: Query<&After>,
mut q_timer: Query<&mut EdgeTimer>,
mut commands: Commands,
) {
let event = transition_event.event();
let machine_root = transition_event.event().event_target();
if let Ok(current) = q_sm.get(machine_root) {
let mut visited: HashSet<Entity> = HashSet::new();
let mut fired_regions: HashSet<Entity> = HashSet::new();
for &leaf in current.active_leaves.iter() {
let region_root = find_parallel_region_root(leaf, &q_child_of, &q_parallel);
if fired_regions.contains(®ion_root) { continue; }
if try_fire_first_matching_edge_on_branch(
leaf, event, machine_root,
&q_transitions, &q_listener, &q_edge_target, &q_guards,
&q_child_of, &mut q_defer, &q_active, &q_after,
&mut q_timer, &mut visited, &mut commands,
) {
fired_regions.insert(region_root);
}
}
if fired_regions.is_empty() {
let _ = try_fire_first_matching_edge(
machine_root, event, &q_transitions, &q_listener, &q_edge_target,
&q_guards, &q_child_of, &mut q_defer, &q_active,
&q_after, &mut q_timer, &mut commands,
);
}
return;
}
try_fire_first_matching_edge(
machine_root, event, &q_transitions, &q_listener, &q_edge_target,
&q_guards, &q_child_of, &mut q_defer, &q_active,
&q_after, &mut q_timer, &mut commands,
);
}
fn try_fire_first_matching_edge<E: TransitionEvent + Clone>(
source: Entity,
event: &E,
q_transitions: &Query<&Transitions>,
q_listener: &Query<&EventEdge<E>>,
q_edge_target: &Query<&Target>,
q_guards: &Query<&Guards>,
q_child_of: &Query<&StateChildOf>,
q_defer: &mut Query<&mut DeferEvent<E>>,
q_active: &Query<(), With<Active>>,
q_after: &Query<&After>,
q_timer: &mut Query<&mut EdgeTimer>,
commands: &mut Commands,
) -> bool {
try_fire_first_matching_edge_generic(
source, event, q_transitions, q_listener, q_edge_target,
q_guards, q_child_of, q_defer, q_active, q_after,
q_timer, commands,
)
}
fn try_fire_first_matching_edge_on_branch<E: EntityEvent + Clone + TransitionEvent>(
start: Entity,
event: &E,
machine_root: Entity,
q_transitions: &Query<&Transitions>,
q_listener: &Query<&EventEdge<E>>,
q_edge_target: &Query<&Target>,
q_guards: &Query<&Guards>,
q_child_of: &Query<&StateChildOf>,
q_defer: &mut Query<&mut DeferEvent<E>>,
q_active: &Query<(), With<Active>>,
q_after: &Query<&After>,
q_timer: &mut Query<&mut EdgeTimer>,
visited: &mut HashSet<Entity>,
commands: &mut Commands,
) -> bool {
let mut current = Some(start);
while let Some(state) = current {
if !visited.insert(state) {
if state == machine_root { break; }
current = q_child_of.get(state).ok().map(|rel| rel.0);
continue;
}
if try_fire_first_matching_edge(
state,
event,
q_transitions,
q_listener,
q_edge_target,
q_guards,
q_child_of,
q_defer,
q_active,
q_after,
q_timer,
commands,
) {
return true;
}
if state == machine_root { break; }
current = q_child_of.get(state).ok().map(|rel| rel.0);
}
false
}
pub fn check_always_on_guards_changed(
q_guards_changed: Query<(Entity, &Guards, &Source, Has<Target>), (Changed<Guards>, With<AlwaysEdge>)>,
q_transitions: Query<&Transitions>,
q_child_of: Query<&StateChildOf>,
q_active: Query<(), With<Active>>,
q_after: Query<&After>,
mut commands: Commands,
) {
for (edge, guards, source, edge_target) in q_guards_changed.iter() {
let source = source.0;
if !q_active.contains(source) { continue; }
if !guards.check() { continue; }
let Ok(transitions) = q_transitions.get(source) else { continue; };
if !transitions.into_iter().any(|&e| e == edge) { continue; }
if !edge_target { continue; }
let root = q_child_of.root_ancestor(source);
if q_after.get(edge).is_ok() {
let after = q_after.get(edge).unwrap();
commands.entity(edge).insert(EdgeTimer(Timer::new(after.duration, TimerMode::Once)));
} else {
commands.trigger(Transition { machine: root, source, edge, payload: () });
}
}
}
pub fn start_after_on_enter(
enter_state: On<EnterState>,
q_transitions: Query<&Transitions>,
q_after: Query<&After>,
q_always: Query<(), With<AlwaysEdge>>,
mut commands: Commands,
) {
let source = enter_state.target;
let Ok(transitions) = q_transitions.get(source) else { return; };
for edge in transitions.into_iter().copied() {
if q_after.get(edge).is_ok() && q_always.get(edge).is_ok() {
let after = q_after.get(edge).unwrap();
commands.entity(edge).insert(EdgeTimer(Timer::new(after.duration, TimerMode::Once)));
}
}
}
pub fn cancel_after_on_exit(
exit_state: On<crate::ExitState>,
q_transitions: Query<&Transitions>,
q_after: Query<&After>,
mut commands: Commands,
) {
let source = exit_state.target;
let Ok(transitions) = q_transitions.get(source) else { return; };
for edge in transitions.into_iter().copied() {
if q_after.get(edge).is_ok() {
commands.entity(edge).remove::<EdgeTimer>();
}
}
}
pub(crate) fn reset_on_transition_actions(
transition_action: On<crate::TransitionActions>,
q_reset_edge: Query<&ResetEdge>,
q_edge: Query<(&Source, &Target)>,
q_children: Query<&crate::StateChildren>,
mut commands: Commands,
) {
let edge = transition_action.target;
let Ok(reset) = q_reset_edge.get(edge) else { return; };
let Ok((Source(source), Target(target))) = q_edge.get(edge) else { return; };
let mut entities = vec![];
match reset.0 {
ResetScope::Source => {
entities.push(*source);
entities.extend(q_children.iter_descendants(*source));
}
ResetScope::Target => {
entities.push(*target);
entities.extend(q_children.iter_descendants(*target));
}
ResetScope::Both => {
entities.push(*source);
entities.push(*target);
entities.extend(q_children.iter_descendants(*source));
entities.extend(q_children.iter_descendants(*target));
}
}
for entity in entities {
commands.trigger(Reset::new(entity));
}
}
pub fn tick_after_system(
time: Res<Time>,
q_transitions: Query<(Entity, &Transitions), With<Active>>, mut q_timer: Query<&mut EdgeTimer>,
q_after: Query<&After>,
q_always: Query<(), With<AlwaysEdge>>,
q_guards: Query<&Guards>,
q_edge_target: Query<&Target>,
q_child_of: Query<&StateChildOf>,
mut commands: Commands,
) {
for (source, transitions) in q_transitions.iter() {
for edge in transitions.into_iter().copied() {
if q_after.get(edge).is_err() { continue; }
if q_always.get(edge).is_err() { continue; }
let Ok(mut timer) = q_timer.get_mut(edge) else { continue; };
timer.0.tick(time.delta());
if !timer.0.just_finished() { continue; }
if !validate_edge_basic(edge, &q_guards, &q_edge_target) {
commands.entity(edge).remove::<EdgeTimer>();
continue;
}
commands.entity(edge).remove::<EdgeTimer>();
let root = q_child_of.root_ancestor(source);
commands.trigger(Transition { machine: root, source, edge, payload: () });
break; }
}
}
pub fn replay_deferred_event<E: EntityEvent + Clone>(
exit_state: On<ExitState>,
mut q_defer: Query<&mut DeferEvent<E>>,
mut commands: Commands,
)
where
for<'a> <E as Event>::Trigger<'a>: Default,
{
let exited_state = exit_state.target;
if let Ok(mut defer_event) = q_defer.get_mut(exited_state) {
if let Some(deferred) = defer_event.take_deferred() {
commands.trigger(deferred);
}
}
}
pub fn tick_after_event_timers<E: TransitionEvent + Clone + 'static>(
time: Res<Time>,
mut q_timer: Query<(Entity, &mut EdgeTimer, &PendingEvent<E>), With<EventEdge<E>>>,
q_after: Query<&After>,
q_guards: Query<&Guards>,
q_edge_target: Query<&Target>,
q_edge_source: Query<&Source>,
q_child_of: Query<&StateChildOf>,
q_active: Query<(), With<Active>>,
mut commands: Commands,
) {
for (edge, mut timer, pending) in q_timer.iter_mut() {
if q_after.get(edge).is_err() { continue; }
let Ok(Source(source)) = q_edge_source.get(edge) else { continue; };
if q_active.get(*source).is_err() {
cleanup_edge_timer_and_pending::<E>(&mut commands, edge);
continue;
}
timer.0.tick(time.delta());
if !timer.0.just_finished() { continue; }
if !validate_edge_basic(edge, &q_guards, &q_edge_target) {
cleanup_edge_timer_and_pending::<E>(&mut commands, edge);
continue;
}
let payload = PhaseEvents {
exit: pending.event.to_exit_event(),
effect: pending.event.to_effect_event(),
entry: pending.event.to_entry_event(),
};
cleanup_edge_timer_and_pending::<E>(&mut commands, edge);
let root = q_child_of.root_ancestor(*source);
commands.trigger(Transition { machine: root, source: *source, edge, payload });
}
}
pub fn cancel_pending_event_on_exit<E: EntityEvent + Clone + 'static>(
exit_state: On<ExitState>,
q_transitions: Query<&Transitions>,
q_pending: Query<&PendingEvent<E>>,
mut commands: Commands,
){
let source = exit_state.target;
let Ok(transitions) = q_transitions.get(source) else { return; };
for &edge in transitions.into_iter() {
if q_pending.get(edge).is_ok() {
cleanup_edge_timer_and_pending::<E>(&mut commands, edge);
}
}
}