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::lifecycle::ComponentHooks)
7/// for cleaning up actions from despawned agents.
8///
9/// Finally, it also contains various static methods for managing 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.",
171                            std::any::type_name::<ActionQueue>()
172                        );
173                        action.on_remove(agent.into(), world);
174                        action.on_drop(agent.into(), world, DropReason::Skipped);
175                        return;
176                    };
177
178                    action_queue.push_back(action);
179                }
180            }
181            AddOrder::Front => {
182                for mut action in actions.rev() {
183                    action.on_add(agent, world);
184
185                    let Ok(mut agent_ref) = world.get_entity_mut(agent) else {
186                        warn!(
187                            "Cannot enqueue action {action:?} to non-existent agent {agent}. \
188                            Action is therefore dropped immediately."
189                        );
190                        action.on_remove(None, world);
191                        action.on_drop(None, world, DropReason::Skipped);
192                        return;
193                    };
194
195                    let Some(mut action_queue) = agent_ref.get_mut::<ActionQueue>() else {
196                        warn!(
197                            "Cannot enqueue action {action:?} to agent {agent} due to missing component {}. \
198                            Action is therefore dropped immediately.",
199                            std::any::type_name::<ActionQueue>()
200                        );
201                        action.on_remove(agent.into(), world);
202                        action.on_drop(agent.into(), world, DropReason::Skipped);
203                        return;
204                    };
205
206                    action_queue.push_front(action);
207                }
208            }
209        }
210
211        if config.start {
212            let Some(current_action) = world.get::<CurrentAction>(agent) else {
213                warn!(
214                    "Could not start next action for agent {agent} due to missing component {}.",
215                    std::any::type_name::<CurrentAction>()
216                );
217                return;
218            };
219
220            if current_action.is_none() {
221                Self::start_next_action(agent, world);
222            }
223        }
224    }
225
226    /// [`Starts`](Action::on_start) the next [`action`](Action) in the queue for `agent`,
227    /// but only if there is no current action.
228    pub fn execute_actions(agent: Entity, world: &mut World) {
229        let Ok(agent_ref) = world.get_entity(agent) else {
230            warn!("Cannot execute actions for non-existent agent {agent}.");
231            return;
232        };
233
234        let Some(current_action) = agent_ref.get::<CurrentAction>() else {
235            warn!(
236                "Cannot execute actions for agent {agent} due to missing component {}.",
237                std::any::type_name::<CurrentAction>()
238            );
239            return;
240        };
241
242        if current_action.is_none() {
243            debug!("Executing actions for agent {agent}.");
244            Self::start_next_action(agent, world);
245        }
246    }
247
248    /// [`Stops`](Action::on_stop) the current [`action`](Action) for `agent` with specified `reason`.
249    pub fn stop_current_action(agent: Entity, reason: StopReason, world: &mut World) {
250        let Ok(mut agent_ref) = world.get_entity_mut(agent) else {
251            warn!(
252                "Cannot stop current action for non-existent agent {agent} with reason {reason:?}."
253            );
254            return;
255        };
256
257        let Some(mut current_action) = agent_ref.get_mut::<CurrentAction>() else {
258            warn!(
259                "Cannot stop current action for agent {agent} with reason {reason:?} \
260                due to missing component {}.",
261                std::any::type_name::<CurrentAction>()
262            );
263            return;
264        };
265
266        if let Some(mut action) = current_action.take() {
267            debug!("Stopping current action {action:?} for agent {agent} with reason {reason:?}.");
268            action.on_stop(agent.into(), world, reason);
269
270            match reason {
271                StopReason::Finished | StopReason::Canceled => {
272                    action.on_remove(agent.into(), world);
273                    action.on_drop(agent.into(), world, DropReason::Done);
274                }
275                StopReason::Paused => {
276                    let Ok(mut agent_ref) = world.get_entity_mut(agent) else {
277                        warn!(
278                            "Cannot enqueue paused action {action:?} to non-existent agent {agent}. \
279                            Action is therefore dropped immediately."
280                        );
281                        action.on_remove(None, world);
282                        action.on_drop(None, world, DropReason::Skipped);
283                        return;
284                    };
285
286                    let Some(mut action_queue) = agent_ref.get_mut::<ActionQueue>() else {
287                        warn!(
288                            "Cannot enqueue paused action {action:?} to agent {agent} due to missing component {}. \
289                            Action is therefore dropped immediately.",
290                            std::any::type_name::<ActionQueue>()
291                        );
292                        action.on_remove(agent.into(), world);
293                        action.on_drop(agent.into(), world, DropReason::Skipped);
294                        return;
295                    };
296
297                    action_queue.push_front(action);
298                }
299            }
300        }
301    }
302
303    /// [`Starts`](Action::on_start) the next [`action`](Action) in the queue for `agent`.
304    ///
305    /// This will loop until any next action is not immediately finished or the queue is empty.
306    /// Since this may trigger an infinite loop, a counter is used in debug build
307    /// that panics when reaching a sufficient target.
308    ///
309    /// The loop will also break if `agent` already has a current action.
310    /// This is likely a user error, and so a warning will be emitted.
311    pub fn start_next_action(agent: Entity, world: &mut World) {
312        #[cfg(debug_assertions)]
313        let mut counter: u16 = 0;
314
315        loop {
316            let Ok(mut agent_ref) = world.get_entity_mut(agent) else {
317                warn!("Cannot start next action for non-existent agent {agent}.");
318                break;
319            };
320
321            let Some(current_action) = agent_ref.get::<CurrentAction>() else {
322                warn!(
323                    "Cannot start next action for agent {agent} due to missing component {}.",
324                    std::any::type_name::<CurrentAction>()
325                );
326                break;
327            };
328
329            if let Some(action) = current_action.0.as_ref() {
330                warn!(
331                    "Cannot start next action for agent {agent} \
332                    as it already has current action {action:?}."
333                );
334                break;
335            }
336
337            let Some(mut action_queue) = agent_ref.get_mut::<ActionQueue>() else {
338                warn!(
339                    "Cannot start next action for agent {agent} due to missing component {}.",
340                    std::any::type_name::<ActionQueue>()
341                );
342                break;
343            };
344
345            let Some(mut action) = action_queue.pop_front() else {
346                break;
347            };
348
349            debug!("Starting action {action:?} for agent {agent}.");
350            if !action.on_start(agent, world) {
351                match world.get_mut::<CurrentAction>(agent) {
352                    Some(mut current_action) => {
353                        current_action.0 = Some(action);
354                    }
355                    None => {
356                        debug!("Canceling action {action:?} due to missing agent {agent}.");
357                        action.on_stop(None, world, StopReason::Canceled);
358                        action.on_remove(None, world);
359                        action.on_drop(None, world, DropReason::Done);
360                    }
361                }
362                break;
363            };
364
365            debug!("Finishing action {action:?} for agent {agent}.");
366            let agent = world.get_entity(agent).map(|_| agent).ok();
367            action.on_stop(agent, world, StopReason::Finished);
368            action.on_remove(agent, world);
369            action.on_drop(agent, world, DropReason::Done);
370
371            if agent.is_none() {
372                break;
373            }
374
375            #[cfg(debug_assertions)]
376            {
377                counter += 1;
378                if counter == u16::MAX {
379                    panic!("infinite loop detected in starting next action");
380                }
381            }
382        }
383    }
384
385    /// Skips the next `n` actions in the queue for `agent`.
386    pub fn skip_actions(agent: Entity, mut n: usize, world: &mut World) {
387        loop {
388            if n == 0 {
389                break;
390            }
391
392            let Ok(mut agent_ref) = world.get_entity_mut(agent) else {
393                warn!("Cannot skip next action for non-existent agent {agent}.");
394                break;
395            };
396
397            let Some(mut action_queue) = agent_ref.get_mut::<ActionQueue>() else {
398                warn!(
399                    "Cannot skip next action for agent {agent} due to missing component {}.",
400                    std::any::type_name::<ActionQueue>()
401                );
402                break;
403            };
404
405            let Some(mut action) = action_queue.pop_front() else {
406                break;
407            };
408
409            debug!("Skipping action {action:?} for agent {agent}.");
410            action.on_remove(agent.into(), world);
411            action.on_drop(agent.into(), world, DropReason::Skipped);
412
413            n -= 1;
414        }
415    }
416
417    /// Clears the action queue for `agent`.
418    ///
419    /// Current action is [`stopped`](Action::on_stop) as [`canceled`](StopReason::Canceled).
420    pub fn clear_actions(agent: Entity, world: &mut World) {
421        let Ok(mut agent_ref) = world.get_entity_mut(agent) else {
422            warn!("Cannot clear actions for non-existent agent {agent}.");
423            return;
424        };
425
426        let Some(mut current_action) = agent_ref.get_mut::<CurrentAction>() else {
427            warn!(
428                "Cannot clear current action for agent {agent} due to missing component {}.",
429                std::any::type_name::<CurrentAction>()
430            );
431            return;
432        };
433
434        if let Some(mut action) = current_action.take() {
435            debug!("Clearing current action {action:?} for agent {agent}.");
436            action.on_stop(agent.into(), world, StopReason::Canceled);
437            action.on_remove(agent.into(), world);
438            action.on_drop(agent.into(), world, DropReason::Cleared);
439        }
440
441        let Ok(mut agent_ref) = world.get_entity_mut(agent) else {
442            warn!("Cannot clear action queue for non-existent agent {agent}.");
443            return;
444        };
445
446        let Some(mut action_queue) = agent_ref.get_mut::<ActionQueue>() else {
447            warn!(
448                "Cannot clear action queue for agent {agent} due to missing component {}.",
449                std::any::type_name::<ActionQueue>()
450            );
451            return;
452        };
453
454        if action_queue.is_empty() {
455            return;
456        }
457
458        debug!("Clearing action queue {:?} for {agent}.", **action_queue);
459        let actions = std::mem::take(&mut action_queue.0);
460        for mut action in actions {
461            action.on_remove(agent.into(), world);
462            action.on_drop(agent.into(), world, DropReason::Cleared);
463        }
464    }
465}