use std::marker::PhantomData;
use std::time::Duration;
use bevy::prelude::*;
use bevy_ecs::{component::{Mutable, StorageType}, entity::MapEntities};
use crate::{guards::Guards, EnterState, Transition, active::Active, StateChildOf, CurrentState};
#[derive(Reflect, Component)]
#[reflect(Component)]
#[relationship_target(relationship = Source, linked_spawn)]
pub struct Transitions(Vec<Entity>);
impl MapEntities for Transitions {
fn map_entities<E: EntityMapper>(&mut self, entity_mapper: &mut E) {
for edge in &mut self.0 {
*edge = entity_mapper.get_mapped(*edge);
}
}
}
impl Transitions {
pub fn new() -> Self {
Self(vec![])
}
pub fn get_transitions(&self) -> &[Entity] {
&self.0
}
}
#[derive(Component, Reflect)]
#[reflect(Component)]
#[relationship(relationship_target = Transitions)]
pub struct Source(pub Entity);
impl MapEntities for Source {
fn map_entities<E: EntityMapper>(&mut self, entity_mapper: &mut E) {
self.0 = entity_mapper.get_mapped(self.0);
}
}
#[derive(Component, Reflect)]
#[reflect(Component)]
pub struct Target(pub Entity);
impl MapEntities for Target {
fn map_entities<E: EntityMapper>(&mut self, entity_mapper: &mut E) {
self.0 = entity_mapper.get_mapped(self.0);
}
}
#[derive(Component, Reflect, Default, Clone, Copy)]
#[reflect(Component)]
pub enum TransitionKind {
#[default]
External,
Internal,
}
#[derive(Component, Reflect, Default)]
#[reflect(Component)]
pub struct AlwaysEdge;
#[derive(Component)]
pub struct After {
pub duration: Duration,
}
#[derive(Component)]
pub struct EdgeTimer(pub Timer);
#[derive(Reflect)]
#[reflect(Component)]
pub struct TransitionListener<E: Event> {
#[reflect(ignore)]
_marker: PhantomData<E>,
}
impl<E: Event> Default for TransitionListener<E> {
fn default() -> Self {
Self { _marker: PhantomData }
}
}
impl<T: Event> Component for TransitionListener<T> {
const STORAGE_TYPE: StorageType = StorageType::Table;
type Mutability = Mutable;
}
impl<E: Event> MapEntities for TransitionListener<E> {
fn map_entities<M: EntityMapper>(&mut self, _entity_mapper: &mut M) {}
}
pub fn transition_always(
trigger: Trigger<EnterState>,
transitions_query: Query<&Transitions>,
always_query: Query<(), With<AlwaysEdge>>,
edge_target_query: Query<&Target>,
guards_query: Query<&Guards>,
child_of_query: Query<&StateChildOf>,
mut commands: Commands,
){
let source = trigger.target();
let Ok(transitions) = transitions_query.get(source) else { return; };
for edge in transitions.get_transitions().iter().copied() {
if always_query.get(edge).is_err() { continue; }
if edge_target_query.get(edge).is_err() { continue; }
if let Ok(guards) = guards_query.get(edge) {
if !guards.check() { continue; }
}
let root = child_of_query.root_ancestor(source);
commands.trigger_targets(Transition { source, edge }, root);
break;
}
}
pub fn transition_listener<E: Event>(
trigger: Trigger<E>,
transitions_query: Query<&Transitions>,
listener_query: Query<&TransitionListener<E>>,
edge_target_query: Query<&Target>,
guards_query: Query<&Guards>,
child_of_query: Query<&StateChildOf>,
current_state_query: Query<&CurrentState>,
mut commands: Commands,
){
if let Ok(current) = current_state_query.get(trigger.target()) {
for &leaf in current.0.iter() {
try_fire_first_matching_edge(
leaf,
&transitions_query,
&listener_query,
&edge_target_query,
&guards_query,
&child_of_query,
&mut commands,
);
}
return;
}
let source = trigger.target();
try_fire_first_matching_edge(
source,
&transitions_query,
&listener_query,
&edge_target_query,
&guards_query,
&child_of_query,
&mut commands,
);
}
fn try_fire_first_matching_edge<E: Event>(
source: Entity,
transitions_query: &Query<&Transitions>,
listener_query: &Query<&TransitionListener<E>>,
edge_target_query: &Query<&Target>,
guards_query: &Query<&Guards>,
child_of_query: &Query<&StateChildOf>,
commands: &mut Commands,
) {
let Ok(transitions) = transitions_query.get(source) else { return; };
for edge in transitions.get_transitions().iter().copied() {
if listener_query.get(edge).is_err() { continue; }
if let Ok(guards) = guards_query.get(edge) {
if !guards.check() { continue; }
}
if edge_target_query.get(edge).is_err() { continue; }
let root = child_of_query.root_ancestor(source);
commands.trigger_targets(Transition { source, edge }, root);
break;
}
}
pub fn check_always_on_guards_changed(
guards_changed_query: Query<(Entity, &Guards, &Source, Has<Target>, Has<Active>), (Changed<Guards>, With<AlwaysEdge>)>,
transitions_query: Query<&Transitions>,
child_of_query: Query<&StateChildOf>,
mut commands: Commands,
) {
for (edge, guards, source, edge_target, active) in guards_changed_query.iter() {
let source = source.0;
if !guards.check() { continue; }
if !active { continue; }
let Ok(transitions) = transitions_query.get(source) else { continue; };
if !transitions.get_transitions().iter().any(|&e| e == edge) { continue; }
if !edge_target { continue; }
let root = child_of_query.root_ancestor(source);
commands.trigger_targets(Transition { source, edge }, root);
}
}
pub fn start_after_on_enter(
trigger: Trigger<EnterState>,
transitions_query: Query<&Transitions>,
after_query: Query<&After>,
mut commands: Commands,
) {
let source = trigger.target();
let Ok(transitions) = transitions_query.get(source) else { return; };
for edge in transitions.get_transitions().iter().copied() {
if let Ok(after) = after_query.get(edge) {
commands.entity(edge).insert(EdgeTimer(Timer::new(after.duration, TimerMode::Once)));
}
}
}
pub fn cancel_after_on_exit(
trigger: Trigger<crate::ExitState>,
transitions_query: Query<&Transitions>,
after_query: Query<&After>,
mut commands: Commands,
) {
let source = trigger.target();
let Ok(transitions) = transitions_query.get(source) else { return; };
for edge in transitions.get_transitions().iter().copied() {
if after_query.get(edge).is_ok() {
commands.entity(edge).remove::<EdgeTimer>();
}
}
}
pub fn tick_after_system(
time: Res<Time>,
sources_with_transitions: Query<(Entity, &Transitions), With<Active>>, mut timer_query: Query<&mut EdgeTimer>,
after_query: Query<&After>,
guards_query: Query<&Guards>,
edge_target_query: Query<&Target>,
child_of_query: Query<&StateChildOf>,
mut commands: Commands,
) {
for (source, transitions) in sources_with_transitions.iter() {
for edge in transitions.get_transitions().iter().copied() {
if after_query.get(edge).is_err() { continue; }
let Ok(mut timer) = timer_query.get_mut(edge) else { continue; };
timer.0.tick(time.delta());
if !timer.0.just_finished() { continue; }
if let Ok(guards) = guards_query.get(edge) {
if !guards.check() { continue; }
}
if edge_target_query.get(edge).is_err() { continue; }
commands.entity(edge).remove::<EdgeTimer>();
let root = child_of_query.root_ancestor(source);
commands.trigger_targets(Transition { source, edge }, root);
break; }
}
}