1use std::{marker::PhantomData, time::Duration};
2
3use bevy_app::{AppExit, ScheduleRunnerPlugin, prelude::*};
4use bevy_ecs::{prelude::*, query::QueryFilter, schedule::ScheduleLabel};
5
6use bevy_sequential_actions::*;
7
8fn main() {
9 App::new()
10 .init_schedule(EvenSchedule)
11 .init_schedule(OddSchedule)
12 .add_plugins((
13 ScheduleRunnerPlugin::run_loop(Duration::from_millis(100)),
14 CustomSequentialActionsPlugin::new(EvenSchedule)
16 .with_cleanup()
17 .with_filter::<With<EvenMarker>>(),
18 CustomSequentialActionsPlugin::new(OddSchedule)
20 .with_filter::<With<OddMarker>>(),
22 ))
23 .add_systems(Startup, setup)
24 .add_systems(Update, run_custom_schedules)
25 .run();
26}
27
28#[derive(Debug, Clone, PartialEq, Eq, Hash, ScheduleLabel)]
29struct EvenSchedule;
30
31#[derive(Debug, Clone, PartialEq, Eq, Hash, ScheduleLabel)]
32struct OddSchedule;
33
34#[derive(Component)]
35struct EvenMarker;
36
37#[derive(Component)]
38struct OddMarker;
39
40fn setup(mut commands: Commands) {
41 let agent_even = commands.spawn((SequentialActions, EvenMarker)).id();
43 commands
44 .actions(agent_even)
45 .add(PrintForeverAction::new(format!(
46 "Even: is_finished is called every even frame for agent {agent_even}."
47 )));
48
49 let agent_odd = commands.spawn((SequentialActions, OddMarker)).id();
51 commands
52 .actions(agent_odd)
53 .add(PrintForeverAction::new(format!(
54 "Odd: is_finished is called every odd frame for agent {agent_odd}."
55 )));
56}
57
58fn run_custom_schedules(
59 world: &mut World,
60 mut frame_count: Local<u32>,
61 mut agent_q: Local<QueryState<Entity, With<SequentialActions>>>,
62) {
63 if *frame_count % 2 == 0 {
64 world.run_schedule(EvenSchedule);
65 } else {
66 world.run_schedule(OddSchedule);
67 }
68
69 if *frame_count == 10 {
70 for agent in agent_q.iter(world).collect::<Vec<_>>() {
71 world.despawn(agent);
72 }
73 world.write_message(AppExit::Success);
74 }
75
76 *frame_count += 1;
77}
78
79struct PrintForeverAction {
80 message: String,
81 agent: Entity,
82}
83
84impl PrintForeverAction {
85 fn new(message: String) -> Self {
86 Self {
87 message,
88 agent: Entity::PLACEHOLDER,
89 }
90 }
91}
92
93impl Action for PrintForeverAction {
94 fn is_finished(&self, _agent: Entity, _world: &World) -> bool {
95 println!("{}", self.message);
96 false
97 }
98 fn on_start(&mut self, agent: Entity, _world: &mut World) -> bool {
99 self.agent = agent;
100 false
101 }
102 fn on_stop(&mut self, _agent: Option<Entity>, _world: &mut World, _reason: StopReason) {}
103 fn on_drop(self: Box<Self>, _agent: Option<Entity>, _world: &mut World, _reason: DropReason) {
104 println!("Dropping action for agent {}...", self.agent);
106 }
107}
108
109struct CustomSequentialActionsPlugin<S: ScheduleLabel, F: QueryFilter> {
116 schedule: S,
117 cleanup: bool,
118 filter: PhantomData<F>,
119}
120
121impl<S: ScheduleLabel> CustomSequentialActionsPlugin<S, ()> {
122 const fn new(schedule: S) -> Self {
123 Self {
124 schedule,
125 cleanup: false,
126 filter: PhantomData,
127 }
128 }
129
130 const fn with_cleanup(mut self) -> Self {
131 self.cleanup = true;
132 self
133 }
134
135 fn with_filter<F: QueryFilter>(self) -> CustomSequentialActionsPlugin<S, F> {
136 CustomSequentialActionsPlugin {
137 schedule: self.schedule,
138 cleanup: self.cleanup,
139 filter: PhantomData,
140 }
141 }
142}
143
144impl<S: ScheduleLabel, F: QueryFilter> CustomSequentialActionsPlugin<S, F> {
145 fn check_actions_exclusive(
146 world: &mut World,
147 mut finished: Local<Vec<Entity>>,
148 mut agent_q: Local<QueryState<(Entity, &CurrentAction), F>>,
149 ) {
150 finished.extend(agent_q.iter(world).filter_map(|(agent, current_action)| {
152 current_action
153 .as_ref()
154 .and_then(|action| action.is_finished(agent, world).then_some(agent))
155 }));
156
157 for agent in finished.drain(..) {
162 SequentialActionsPlugin::stop_current_action(agent, StopReason::Finished, world);
163 SequentialActionsPlugin::start_next_action(agent, world);
164 }
165 }
166}
167
168impl Default for CustomSequentialActionsPlugin<Last, ()> {
169 fn default() -> Self {
170 Self::new(Last).with_cleanup()
171 }
172}
173
174impl<S: ScheduleLabel + Clone, F: QueryFilter + Send + Sync + 'static> Plugin
175 for CustomSequentialActionsPlugin<S, F>
176{
177 fn build(&self, app: &mut App) {
178 app.add_systems(self.schedule.clone(), Self::check_actions_exclusive);
180
181 if self.cleanup {
183 app.add_observer(CurrentAction::on_remove_trigger::<F>)
184 .add_observer(ActionQueue::on_remove_trigger::<F>);
185 }
186 }
187}