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}