bevy_mod_observable_timer/
lib.rs

1#![doc = include_str!("../README.md")]
2
3use core::{
4    ops::{Deref, DerefMut},
5    time::Duration,
6};
7
8use bevy::{
9    ecs::{
10        component::HookContext,
11        schedule::{InternedScheduleLabel, ScheduleLabel},
12        world::DeferredWorld,
13    },
14    prelude::*,
15};
16
17/// The [`SystemSet`] during which [`ObservableTimer`]s are updated.
18///
19/// Runs in [`Update`] by default, but this is configurable. See [`ObservableTimerPlugin::in_schedule()`].
20#[derive(SystemSet, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
21pub struct ObservableTimerSystems;
22
23/// This plugin provides functionality for the [`ObservableTimer`] component.
24///
25/// See the crate-level documentation for more information.
26pub struct ObservableTimerPlugin {
27    schedule: InternedScheduleLabel,
28}
29
30impl ObservableTimerPlugin {
31    /// Creates an `ObservableTimerPlugin` whose timers update in the given schedule.
32    ///
33    /// The default plugin updates in [`Update`].
34    ///
35    /// # Example
36    ///
37    /// ```
38    /// # use bevy::prelude::*;
39    /// # use bevy_mod_observable_timer::*;
40    /// # let mut app = App::new();
41    /// // Timers will be updated in `Last`
42    /// app.add_plugins(ObservableTimerPlugin::in_schedule(Last));
43    /// ```
44    pub fn in_schedule(schedule: impl ScheduleLabel) -> Self {
45        Self {
46            schedule: schedule.intern(),
47        }
48    }
49}
50
51impl Default for ObservableTimerPlugin {
52    fn default() -> Self {
53        Self::in_schedule(Update)
54    }
55}
56
57impl Plugin for ObservableTimerPlugin {
58    fn build(&self, app: &mut App) {
59        app.add_systems(
60            self.schedule,
61            update_observable_timers.in_set(ObservableTimerSystems),
62        );
63    }
64}
65
66/// Describes the behavior that should be taken by an [`ObservableTimer`] upon finishing.
67///
68/// # See also
69/// - [`ObservableTimer::with_finish_behavior()`]
70/// - [`ObservableTimer::finish_behavior`]
71#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
72pub enum TimerFinishBehavior {
73    /// Do nothing.
74    ///
75    /// Note that this will leave the `ObservableTimer` component in place, which means it will still be looped through
76    /// when updating timers.
77    None,
78    /// Remove only the `ObservableTimer` component.
79    RemoveComponent,
80    /// Despawn the entity that the `ObservableTimer` is attached to.
81    ///
82    /// This is the default behavior.
83    #[default]
84    DespawnEntity,
85}
86
87/// A timer component that triggers observable lifecycle events on its [`Entity`].
88///
89/// When an `ObservableTimer` is first added to an `Entity` (either by adding a new one, or replacing the current one)
90/// a [`TimerStarted`] event will be triggered. Then, each time an interval completes, a [`TimerFinished`] event will
91/// be triggered. Finally, when the timer component is removed, a [`TimerStopped`] event will be triggered.
92///
93/// By default a [`TimerMode::Once`] timer will despawn its `Entity` when it finishes. This behavior can be changed to
94/// removing only the `ObservableTimer` component, or doing nothing. See [`Self::with_finish_behavior`] for setting
95/// behavior at creation, or [`Self::finish_behavior`] for changing it after creation. Note that this behavior will
96/// not be run if the timer is removed manually before finishing.
97///
98/// To cancel a currently running timer simply remove the component. This will cause a [`TimerStopped`] event to be
99/// triggered.
100#[derive(Component, Debug, Clone)]
101#[component(on_remove = on_timer_removed)]
102pub struct ObservableTimer {
103    /// The internal [`Timer`].
104    pub timer: Timer,
105    /// The timer's [finish behavior](TimerFinishBehavior).
106    pub finish_behavior: TimerFinishBehavior,
107}
108
109impl ObservableTimer {
110    /// Create a new timer.
111    pub fn new(duration: Duration, mode: TimerMode) -> Self {
112        Self {
113            timer: Timer::new(duration, mode),
114            finish_behavior: TimerFinishBehavior::default(),
115        }
116    }
117
118    /// Create a new timer from a duration in seconds.
119    pub fn from_seconds(duration: f32, mode: TimerMode) -> Self {
120        Self {
121            timer: Timer::from_seconds(duration, mode),
122            finish_behavior: TimerFinishBehavior::default(),
123        }
124    }
125
126    /// Set the [`TimerFinishBehavior`] for this timer.
127    pub fn with_finish_behavior(self, finish_behavior: TimerFinishBehavior) -> Self {
128        Self {
129            finish_behavior,
130            ..self
131        }
132    }
133}
134
135impl Deref for ObservableTimer {
136    type Target = Timer;
137    fn deref(&self) -> &Self::Target {
138        &self.timer
139    }
140}
141
142impl DerefMut for ObservableTimer {
143    fn deref_mut(&mut self) -> &mut Self::Target {
144        &mut self.timer
145    }
146}
147
148/// A timer [`Event`] that is triggered when an [`ObservableTimer`] is inserted or spawned.
149#[derive(Event, Debug)]
150pub struct TimerStarted;
151
152/// A timer [`Event`] that is triggered when an [`ObservableTimer`] is removed or despawned.
153#[derive(Event, Debug)]
154pub struct TimerStopped {
155    /// This is `true` for [`TimerMode::Once`] timers that finished normally, and removed or
156    /// despawned themselves.
157    pub finished: bool,
158}
159
160/// A timer [`Event`] that is triggered when an [`ObservableTimer`] finishes.
161#[derive(Event, Debug)]
162pub struct TimerFinished;
163
164fn on_timer_removed(mut world: DeferredWorld, HookContext { entity, .. }: HookContext) {
165    let timer = world.get::<ObservableTimer>(entity).unwrap();
166    let finished = timer.mode() == TimerMode::Once && timer.finished();
167    world
168        .commands()
169        .trigger_targets(TimerStopped { finished }, entity);
170}
171
172fn update_observable_timers(
173    time: Res<Time>,
174    mut timers: Query<(Entity, &mut ObservableTimer)>,
175    mut commands: Commands,
176) {
177    let delta = time.delta();
178    for (entity, mut timer) in timers.iter_mut() {
179        if timer.is_added() {
180            commands.trigger_targets(TimerStarted, entity)
181        }
182
183        if timer.tick(delta).just_finished() {
184            for _ in 0..timer.times_finished_this_tick() {
185                commands.trigger_targets(TimerFinished, entity);
186            }
187
188            if timer.mode() == TimerMode::Once {
189                match timer.finish_behavior {
190                    TimerFinishBehavior::None => {}
191                    TimerFinishBehavior::RemoveComponent => {
192                        commands.entity(entity).remove::<ObservableTimer>();
193                    }
194                    TimerFinishBehavior::DespawnEntity => {
195                        commands.entity(entity).despawn();
196                    }
197                }
198            }
199        }
200    }
201}