bevy_sequential_actions/
plugin.rs

1use super::*;
2
3/// The [`Plugin`] for this library that you can add to your [`App`].
4///
5/// This plugin adds the [`check_actions`](Self::check_actions) system to the [`Last`] schedule
6/// for action queue advancement, and also two [`hooks`](bevy_ecs::component::ComponentHooks)
7/// for cleaning up actions from despawned agents.
8///
9/// Finally, it also contains various static methods for modifying the action queue.
10pub struct SequentialActionsPlugin;
11
12impl Plugin for SequentialActionsPlugin {
13    fn build(&self, app: &mut App) {
14        app.add_systems(Last, Self::check_actions::<()>);
15        app.world_mut()
16            .register_component_hooks::<CurrentAction>()
17            .on_remove(CurrentAction::on_remove_hook);
18        app.world_mut()
19            .register_component_hooks::<ActionQueue>()
20            .on_remove(ActionQueue::on_remove_hook);
21    }
22}
23
24impl SequentialActionsPlugin {
25    /// The [`System`] used by [`SequentialActionsPlugin`].
26    /// It is responsible for checking all agents for finished actions
27    /// and advancing the action queue.
28    ///
29    /// The query filter `F` is used for filtering agents.
30    /// Use the unit type `()` for no filtering.
31    ///
32    /// # Example
33    ///
34    /// ```rust,no_run
35    /// # use bevy_ecs::prelude::*;
36    /// # use bevy_app::prelude::*;
37    /// # use bevy_sequential_actions::*;
38    /// #
39    /// # fn main() {
40    /// App::new()
41    ///     .add_systems(Last, SequentialActionsPlugin::check_actions::<()>)
42    ///     .run();
43    /// # }
44    /// ```
45    pub fn check_actions<F: QueryFilter>(
46        action_q: Query<(Entity, &CurrentAction), F>,
47        world: &World,
48        mut commands: Commands,
49    ) {
50        action_q
51            .iter()
52            .filter_map(|(agent, current_action)| {
53                current_action
54                    .as_ref()
55                    .and_then(|action| action.is_finished(agent, world).then_some(agent))
56            })
57            .for_each(|agent| {
58                commands.queue(move |world: &mut World| {
59                    Self::stop_current_action(agent, StopReason::Finished, world);
60                    Self::start_next_action(agent, world);
61                });
62            });
63    }
64
65    /// Adds a single [`action`](Action) to `agent` with specified `config`.
66    pub fn add_action(
67        agent: Entity,
68        config: AddConfig,
69        action: impl IntoBoxedAction,
70        world: &mut World,
71    ) {
72        let mut action = action.into_boxed_action();
73
74        if world.get_entity(agent).is_err() {
75            warn!("Cannot add action {action:?} to non-existent agent {agent}.");
76            return;
77        }
78
79        debug!("Adding action {action:?} for agent {agent} with {config:?}.");
80        action.on_add(agent, world);
81
82        let Ok(mut agent_ref) = world.get_entity_mut(agent) else {
83            warn!(
84                "Cannot enqueue action {action:?} to non-existent agent {agent}. \
85                Action is therefore dropped immediately."
86            );
87            action.on_remove(None, world);
88            action.on_drop(None, world, DropReason::Skipped);
89            return;
90        };
91
92        let Some(mut action_queue) = agent_ref.get_mut::<ActionQueue>() else {
93            warn!(
94                "Cannot enqueue action {action:?} to agent {agent} due to missing component {}. \
95                Action is therefore dropped immediately.",
96                std::any::type_name::<ActionQueue>()
97            );
98            action.on_remove(agent.into(), world);
99            action.on_drop(agent.into(), world, DropReason::Skipped);
100            return;
101        };
102
103        match config.order {
104            AddOrder::Back => action_queue.push_back(action),
105            AddOrder::Front => action_queue.push_front(action),
106        }
107
108        if config.start {
109            let Some(current_action) = agent_ref.get::<CurrentAction>() else {
110                warn!(
111                    "Could not start next action for agent {agent} due to missing component {}.",
112                    std::any::type_name::<CurrentAction>()
113                );
114                return;
115            };
116
117            if current_action.is_none() {
118                Self::start_next_action(agent, world);
119            }
120        }
121    }
122
123    /// Adds a collection of actions to `agent` with specified `config`.
124    /// An empty collection does nothing.
125    pub fn add_actions<I>(agent: Entity, config: AddConfig, actions: I, world: &mut World)
126    where
127        I: DoubleEndedIterator<Item = BoxedAction> + ExactSizeIterator + Debug,
128    {
129        let actions = actions.into_iter();
130        let len = actions.len();
131
132        if len == 0 {
133            return;
134        }
135
136        let Ok(mut agent_ref) = world.get_entity_mut(agent) else {
137            warn!("Cannot add actions {actions:?} to non-existent agent {agent}.");
138            return;
139        };
140
141        let Some(mut action_queue) = agent_ref.get_mut::<ActionQueue>() else {
142            warn!(
143                "Cannot add actions {actions:?} to agent {agent} due to missing component {}.",
144                std::any::type_name::<ActionQueue>()
145            );
146            return;
147        };
148
149        debug!("Adding actions {actions:?} for agent {agent} with {config:?}.");
150        action_queue.reserve(len);
151
152        match config.order {
153            AddOrder::Back => {
154                for mut action in actions {
155                    action.on_add(agent, world);
156
157                    let Ok(mut agent_ref) = world.get_entity_mut(agent) else {
158                        warn!(
159                            "Cannot enqueue action {action:?} to non-existent agent {agent}. \
160                            Action is therefore dropped immediately."
161                        );
162                        action.on_remove(None, world);
163                        action.on_drop(None, world, DropReason::Skipped);
164                        return;
165                    };
166
167                    let Some(mut action_queue) = agent_ref.get_mut::<ActionQueue>() else {
168                        warn!(
169                            "Cannot enqueue action {action:?} to agent {agent} due to missing component {}. \
170                            Action is therefore dropped immediately.", std::any::type_name::<ActionQueue>()
171                        );
172                        action.on_remove(agent.into(), world);
173                        action.on_drop(agent.into(), world, DropReason::Skipped);
174                        return;
175                    };
176
177                    action_queue.push_back(action);
178                }
179            }
180            AddOrder::Front => {
181                for mut action in actions.rev() {
182                    action.on_add(agent, world);
183
184                    let Ok(mut agent_ref) = world.get_entity_mut(agent) else {
185                        warn!(
186                            "Cannot enqueue action {action:?} to non-existent agent {agent}. \
187                            Action is therefore dropped immediately."
188                        );
189                        action.on_remove(None, world);
190                        action.on_drop(None, world, DropReason::Skipped);
191                        return;
192                    };
193
194                    let Some(mut action_queue) = agent_ref.get_mut::<ActionQueue>() else {
195                        warn!(
196                            "Cannot enqueue action {action:?} to agent {agent} due to missing component {}. \
197                            Action is therefore dropped immediately.", std::any::type_name::<ActionQueue>()
198                        );
199                        action.on_remove(agent.into(), world);
200                        action.on_drop(agent.into(), world, DropReason::Skipped);
201                        return;
202                    };
203
204                    action_queue.push_front(action);
205                }
206            }
207        }
208
209        if config.start {
210            let Some(current_action) = world.get::<CurrentAction>(agent) else {
211                warn!(
212                    "Could not start next action for agent {agent} due to missing component {}.",
213                    std::any::type_name::<CurrentAction>()
214                );
215                return;
216            };
217
218            if current_action.is_none() {
219                Self::start_next_action(agent, world);
220            }
221        }
222    }
223
224    /// [`Starts`](Action::on_start) the next [`action`](Action) in the queue for `agent`,
225    /// but only if there is no current action.
226    pub fn execute_actions(agent: Entity, world: &mut World) {
227        let Ok(agent_ref) = world.get_entity(agent) else {
228            warn!("Cannot execute actions for non-existent agent {agent}.");
229            return;
230        };
231
232        let Some(current_action) = agent_ref.get::<CurrentAction>() else {
233            warn!(
234                "Cannot execute actions for agent {agent} due to missing component {}.",
235                std::any::type_name::<CurrentAction>()
236            );
237            return;
238        };
239
240        if current_action.is_none() {
241            debug!("Executing actions for agent {agent}.");
242            Self::start_next_action(agent, world);
243        }
244    }
245
246    /// [`Stops`](Action::on_stop) the current [`action`](Action) for `agent` with specified `reason`.
247    pub fn stop_current_action(agent: Entity, reason: StopReason, world: &mut World) {
248        let Ok(mut agent_ref) = world.get_entity_mut(agent) else {
249            warn!(
250                "Cannot stop current action for non-existent agent {agent} with reason {reason:?}."
251            );
252            return;
253        };
254
255        let Some(mut current_action) = agent_ref.get_mut::<CurrentAction>() else {
256            warn!(
257                "Cannot stop current action for agent {agent} with reason {reason:?} \
258                due to missing component {}.",
259                std::any::type_name::<CurrentAction>()
260            );
261            return;
262        };
263
264        if let Some(mut action) = current_action.take() {
265            debug!("Stopping current action {action:?} for agent {agent} with reason {reason:?}.");
266            action.on_stop(agent.into(), world, reason);
267
268            match reason {
269                StopReason::Finished | StopReason::Canceled => {
270                    action.on_remove(agent.into(), world);
271                    action.on_drop(agent.into(), world, DropReason::Done);
272                }
273                StopReason::Paused => {
274                    let Ok(mut agent_ref) = world.get_entity_mut(agent) else {
275                        warn!(
276                            "Cannot enqueue paused action {action:?} to non-existent agent {agent}. \
277                            Action is therefore dropped immediately."
278                        );
279                        action.on_remove(None, world);
280                        action.on_drop(None, world, DropReason::Skipped);
281                        return;
282                    };
283
284                    let Some(mut action_queue) = agent_ref.get_mut::<ActionQueue>() else {
285                        warn!(
286                            "Cannot enqueue paused action {action:?} to agent {agent} due to missing component {}. \
287                            Action is therefore dropped immediately.", std::any::type_name::<ActionQueue>()
288                        );
289                        action.on_remove(agent.into(), world);
290                        action.on_drop(agent.into(), world, DropReason::Skipped);
291                        return;
292                    };
293
294                    action_queue.push_front(action);
295                }
296            }
297        }
298    }
299
300    /// [`Starts`](Action::on_start) the next [`action`](Action) in the queue for `agent`.
301    ///
302    /// This will loop until any next action is not immediately finished or the queue is empty.
303    /// Since this may trigger an infinite loop, a counter is used in debug build
304    /// that panics when reaching a sufficient target.
305    ///
306    /// The loop will also break if `agent` already has a current action.
307    /// This is likely a user error, and so a warning will be emitted.
308    pub fn start_next_action(agent: Entity, world: &mut World) {
309        #[cfg(debug_assertions)]
310        let mut counter: u16 = 0;
311
312        loop {
313            let Ok(mut agent_ref) = world.get_entity_mut(agent) else {
314                warn!("Cannot start next action for non-existent agent {agent}.");
315                break;
316            };
317
318            let Some(current_action) = agent_ref.get::<CurrentAction>() else {
319                warn!(
320                    "Cannot start next action for agent {agent} due to missing component {}.",
321                    std::any::type_name::<CurrentAction>()
322                );
323                break;
324            };
325
326            if let Some(action) = current_action.0.as_ref() {
327                warn!(
328                    "Cannot start next action for agent {agent} \
329                    as it already has current action {action:?}."
330                );
331                break;
332            }
333
334            let Some(mut action_queue) = agent_ref.get_mut::<ActionQueue>() else {
335                warn!(
336                    "Cannot start next action for agent {agent} due to missing component {}.",
337                    std::any::type_name::<ActionQueue>()
338                );
339                break;
340            };
341
342            let Some(mut action) = action_queue.pop_front() else {
343                break;
344            };
345
346            debug!("Starting action {action:?} for agent {agent}.");
347            if !action.on_start(agent, world) {
348                match world.get_mut::<CurrentAction>(agent) {
349                    Some(mut current_action) => {
350                        current_action.0 = Some(action);
351                    }
352                    None => {
353                        debug!("Canceling action {action:?} due to missing agent {agent}.");
354                        action.on_stop(None, world, StopReason::Canceled);
355                        action.on_remove(None, world);
356                        action.on_drop(None, world, DropReason::Done);
357                    }
358                }
359                break;
360            };
361
362            debug!("Finishing action {action:?} for agent {agent}.");
363            let agent = world.get_entity(agent).map(|_| agent).ok();
364            action.on_stop(agent, world, StopReason::Finished);
365            action.on_remove(agent, world);
366            action.on_drop(agent, world, DropReason::Done);
367
368            if agent.is_none() {
369                break;
370            }
371
372            #[cfg(debug_assertions)]
373            {
374                counter += 1;
375                if counter == u16::MAX {
376                    panic!("infinite loop detected in starting next action");
377                }
378            }
379        }
380    }
381
382    /// Skips the next `n` actions in the queue for `agent`.
383    pub fn skip_actions(agent: Entity, mut n: usize, world: &mut World) {
384        loop {
385            if n == 0 {
386                break;
387            }
388
389            let Ok(mut agent_ref) = world.get_entity_mut(agent) else {
390                warn!("Cannot skip next action for non-existent agent {agent}.");
391                break;
392            };
393
394            let Some(mut action_queue) = agent_ref.get_mut::<ActionQueue>() else {
395                warn!(
396                    "Cannot skip next action for agent {agent} due to missing component {}.",
397                    std::any::type_name::<ActionQueue>()
398                );
399                break;
400            };
401
402            let Some(mut action) = action_queue.pop_front() else {
403                break;
404            };
405
406            debug!("Skipping action {action:?} for agent {agent}.");
407            action.on_remove(agent.into(), world);
408            action.on_drop(agent.into(), world, DropReason::Skipped);
409
410            n -= 1;
411        }
412    }
413
414    /// Clears the action queue for `agent`.
415    ///
416    /// Current action is [`stopped`](Action::on_stop) as [`canceled`](StopReason::Canceled).
417    pub fn clear_actions(agent: Entity, world: &mut World) {
418        let Ok(mut agent_ref) = world.get_entity_mut(agent) else {
419            warn!("Cannot clear actions for non-existent agent {agent}.");
420            return;
421        };
422
423        let Some(mut current_action) = agent_ref.get_mut::<CurrentAction>() else {
424            warn!(
425                "Cannot clear current action for agent {agent} due to missing component {}.",
426                std::any::type_name::<CurrentAction>()
427            );
428            return;
429        };
430
431        if let Some(mut action) = current_action.take() {
432            debug!("Clearing current action {action:?} for agent {agent}.");
433            action.on_stop(agent.into(), world, StopReason::Canceled);
434            action.on_remove(agent.into(), world);
435            action.on_drop(agent.into(), world, DropReason::Cleared);
436        }
437
438        let Ok(mut agent_ref) = world.get_entity_mut(agent) else {
439            warn!("Cannot clear action queue for non-existent agent {agent}.");
440            return;
441        };
442
443        let Some(mut action_queue) = agent_ref.get_mut::<ActionQueue>() else {
444            warn!(
445                "Cannot clear action queue for agent {agent} due to missing component {}.",
446                std::any::type_name::<ActionQueue>()
447            );
448            return;
449        };
450
451        if action_queue.is_empty() {
452            return;
453        }
454
455        debug!("Clearing action queue {:?} for {agent}.", **action_queue);
456        let actions = std::mem::take(&mut action_queue.0);
457        for mut action in actions {
458            action.on_remove(agent.into(), world);
459            action.on_drop(agent.into(), world, DropReason::Cleared);
460        }
461    }
462}