1use std::{future::Future, time::Duration};
2
3use bevy_app::{App, Update};
4use bevy_ecs::{
5 component::Component,
6 entity::Entity,
7 system::{Commands, Query, Res},
8 world::World,
9};
10use bevy_time::Time;
11use futures::TryFutureExt;
12use tokio::sync::oneshot;
13
14use crate::TaskContext;
15
16pub fn time_plugin(app: &mut App) {
17 app.add_systems(Update, (advance_timeout_after, advance_timeout_at));
18}
19
20pub trait TimingTaskExt {
21 fn sleep(&self, duration: Duration) -> impl Future<Output = ()>;
22 fn sleep_until(&self, duration: Duration) -> impl Future<Output = ()>;
23}
24
25impl TimingTaskExt for TaskContext {
26 fn sleep(&self, duration: Duration) -> impl Future<Output = ()> {
27 let (tx, rx) = oneshot::channel();
28 self.with_world(move |world| {
29 world.spawn(TimeoutAfter(duration, tx));
30 })
31 .detach();
32 rx.unwrap_or_else(|_| ())
33 }
34
35 fn sleep_until(&self, elapsed_since_startup: Duration) -> impl Future<Output = ()> {
36 let (tx, rx) = oneshot::channel();
37 self.with_world(move |world| {
38 world.spawn(TimeoutAt(elapsed_since_startup, tx));
39 })
40 .detach();
41 rx.unwrap_or_else(|_| ())
42 }
43}
44
45#[derive(Component)]
46pub struct TimeoutAfter(Duration, oneshot::Sender<()>);
47
48#[derive(Component)]
49pub struct TimeoutAt(Duration, oneshot::Sender<()>);
50
51pub fn advance_timeout_after(
52 mut timeouts: Query<(Entity, &mut TimeoutAfter)>,
53 time: Res<Time>,
54 mut commands: Commands,
55) {
56 for (e, mut timeout) in &mut timeouts {
57 if let Some(new_timeout) = timeout.0.checked_sub(time.delta()) {
58 timeout.0 = new_timeout;
59 } else {
60 commands.queue(move |world: &mut World| {
61 let Ok(mut e) = world.get_entity_mut(e) else {
62 return;
63 };
64 if let Some(timeout) = e.take::<TimeoutAfter>() {
65 timeout.1.send(()).ok();
66 }
67 e.despawn();
68 });
69 }
70 }
71}
72
73pub fn advance_timeout_at(
74 timeouts: Query<(Entity, &TimeoutAt)>,
75 time: Res<Time>,
76 mut commands: Commands,
77) {
78 for (e, timeout) in &timeouts {
79 if time.elapsed() >= timeout.0 {
80 commands.queue(move |world: &mut World| {
81 let Ok(mut e) = world.get_entity_mut(e) else {
82 return;
83 };
84 if let Some(timeout) = e.take::<TimeoutAt>() {
85 timeout.1.send(()).ok();
86 }
87 e.despawn();
88 });
89 }
90 }
91}