1#![doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/README.md"))]
2
3use bevy_app::{App, Plugin, PostUpdate};
4use bevy_ecs::prelude::*;
5use bevy_reflect::Reflect;
6use bevy_time::{Stopwatch, Time};
7use std::time::Duration;
8
9pub struct ActivationPlugin;
11
12impl Plugin for ActivationPlugin {
13 fn build(&self, app: &mut App) {
14 app.register_type::<ActiveState>()
15 .add_event::<TimeoutEvent>()
16 .add_systems(PostUpdate, check_timeout);
17 }
18}
19
20#[derive(Event)]
22pub struct TimeoutEvent;
23
24#[derive(Debug, Component, Reflect)]
26#[reflect(Component)]
27pub struct ActiveState {
28 active: bool,
30 watch: Option<Stopwatch>,
32 time_to_idle: Duration,
34}
35
36impl Default for ActiveState {
37 fn default() -> Self {
38 Self::always()
39 }
40}
41
42impl ActiveState {
43 pub fn always() -> ActiveState {
45 ActiveState {
46 active: true,
47 watch: None,
48 time_to_idle: Duration::MAX,
49 }
50 }
51
52 #[inline]
53 pub fn is_active(&self) -> bool {
54 self.active
55 }
56
57 #[inline]
58 pub fn is_idle(&self) -> bool {
59 !self.is_active()
60 }
61
62 #[inline]
64 pub fn active_mut(&mut self) -> &mut bool {
65 &mut self.active
66 }
67
68 #[inline]
70 pub fn time_to_idle_mut(&mut self) -> &mut Duration {
71 &mut self.time_to_idle
72 }
73
74 pub fn new(timeout: Duration) -> ActiveState {
76 ActiveState {
77 active: true,
78 watch: Some(Stopwatch::new()),
79 time_to_idle: timeout,
80 }
81 }
82
83 pub fn set_tti_time(&mut self, timeout: Duration) -> &mut ActiveState {
85 self.time_to_idle = timeout;
86 if self.watch.is_none() {
87 self.watch = Some(Stopwatch::new());
88 }
89 self
90 }
91
92 pub fn toggle(&mut self) {
94 self.active = true;
95 if let Some(watch) = &mut self.watch {
96 watch.reset();
97 }
98 }
99}
100
101fn check_timeout(
103 mut commands: Commands,
104 time: Res<Time>,
105 mut active_state_query: Query<(Entity, &mut ActiveState)>,
106) {
107 for (entity, mut active_state) in active_state_query.iter_mut() {
108 if active_state.is_idle() {
109 continue;
110 }
111 let timeout = active_state.time_to_idle;
112 if let Some(watch) = &mut active_state.watch {
113 watch.tick(time.delta());
114
115 if watch.elapsed() >= timeout {
116 commands.trigger_targets(TimeoutEvent, entity);
117 watch.reset();
118 active_state.active = false;
119 }
120 }
121 }
122}