#![doc = include_str!("../README.md")]
use core::{
ops::{Deref, DerefMut},
time::Duration,
};
use bevy::{
ecs::{
lifecycle::HookContext,
schedule::{InternedScheduleLabel, ScheduleLabel},
world::DeferredWorld,
},
prelude::*,
};
#[derive(SystemSet, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ObservableTimerSystems;
pub struct ObservableTimerPlugin {
schedule: InternedScheduleLabel,
}
impl ObservableTimerPlugin {
pub fn in_schedule(schedule: impl ScheduleLabel) -> Self {
Self {
schedule: schedule.intern(),
}
}
}
impl Default for ObservableTimerPlugin {
fn default() -> Self {
Self::in_schedule(Update)
}
}
impl Plugin for ObservableTimerPlugin {
fn build(&self, app: &mut App) {
app.add_systems(
self.schedule,
update_observable_timers.in_set(ObservableTimerSystems),
);
}
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum TimerFinishBehavior {
None,
RemoveComponent,
#[default]
DespawnEntity,
}
#[derive(Component, Debug, Clone)]
#[component(on_remove = on_timer_removed)]
pub struct ObservableTimer {
pub timer: Timer,
pub finish_behavior: TimerFinishBehavior,
}
impl ObservableTimer {
pub fn new(duration: Duration, mode: TimerMode) -> Self {
Self {
timer: Timer::new(duration, mode),
finish_behavior: TimerFinishBehavior::default(),
}
}
pub fn from_seconds(duration: f32, mode: TimerMode) -> Self {
Self {
timer: Timer::from_seconds(duration, mode),
finish_behavior: TimerFinishBehavior::default(),
}
}
pub fn with_finish_behavior(self, finish_behavior: TimerFinishBehavior) -> Self {
Self {
finish_behavior,
..self
}
}
}
impl Deref for ObservableTimer {
type Target = Timer;
fn deref(&self) -> &Self::Target {
&self.timer
}
}
impl DerefMut for ObservableTimer {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.timer
}
}
#[derive(EntityEvent, Debug)]
pub struct TimerStarted {
#[entity_event]
pub entity: Entity,
}
#[derive(EntityEvent, Debug)]
pub struct TimerStopped {
#[entity_event]
pub entity: Entity,
pub finished: bool,
}
#[derive(EntityEvent, Debug)]
pub struct TimerFinished {
#[entity_event]
pub entity: Entity,
}
fn on_timer_removed(mut world: DeferredWorld, HookContext { entity, .. }: HookContext) {
let timer = world.get::<ObservableTimer>(entity).unwrap();
let finished = timer.mode() == TimerMode::Once && timer.is_finished();
world.commands().trigger(TimerStopped { entity, finished });
}
fn update_observable_timers(
time: Res<Time>,
mut timers: Query<(Entity, &mut ObservableTimer)>,
mut commands: Commands,
) {
let delta = time.delta();
for (entity, mut timer) in timers.iter_mut() {
if timer.is_added() {
commands.trigger(TimerStarted { entity })
}
if timer.tick(delta).just_finished() {
for _ in 0..timer.times_finished_this_tick() {
commands.trigger(TimerFinished { entity });
}
if timer.mode() == TimerMode::Once {
match timer.finish_behavior {
TimerFinishBehavior::None => {}
TimerFinishBehavior::RemoveComponent => {
commands.entity(entity).remove::<ObservableTimer>();
}
TimerFinishBehavior::DespawnEntity => {
commands.entity(entity).despawn();
}
}
}
}
}
}