macroquad/experimental/
coroutines.rs

1//! The way to emulate multitasking with macroquad's `.await`.
2//! Useful for organizing state machines, animation cutscenes and other stuff that require
3//! some evaluation over time.
4//!
5
6use std::any::{Any, TypeId};
7use std::future::Future;
8use std::marker::PhantomData;
9use std::pin::Pin;
10use std::task::{Context, Poll};
11
12use crate::exec::resume;
13use crate::get_context;
14
15mod generational_storage;
16
17use generational_storage::{GenerationalId, GenerationalStorage};
18
19struct CoroutineInternal {
20    future: Pin<Box<dyn Future<Output = Box<dyn Any>>>>,
21    manual_poll: bool,
22    manual_time: Option<f64>,
23    // if return value of a coroutine is () there is no need to
24    // keep coroutine's memory allocated until the user retrieves the data
25    // we can free the memory right away, and just return () on retrieve
26    has_value: bool,
27}
28
29enum CoroutineState {
30    Running(CoroutineInternal),
31    Value(Box<dyn Any>),
32    Nothing,
33}
34
35impl CoroutineState {
36    pub const fn is_value(&self) -> bool {
37        matches!(self, CoroutineState::Value(_))
38    }
39
40    pub const fn is_nothing(&self) -> bool {
41        matches!(self, CoroutineState::Nothing)
42    }
43
44    pub fn take_value(&mut self) -> Option<Box<dyn Any>> {
45        if self.is_value() {
46            let state = std::mem::replace(self, CoroutineState::Nothing);
47            if let CoroutineState::Value(v) = state {
48                return Some(v);
49            }
50        }
51
52        None
53    }
54}
55
56pub(crate) struct CoroutinesContext {
57    coroutines: GenerationalStorage<CoroutineState>,
58    active_coroutine_now: Option<f64>,
59    active_coroutine_delta: Option<f64>,
60}
61
62impl CoroutinesContext {
63    pub fn new() -> CoroutinesContext {
64        CoroutinesContext {
65            coroutines: GenerationalStorage::new(),
66            active_coroutine_now: None,
67            active_coroutine_delta: None,
68        }
69    }
70
71    pub fn update(&mut self) {
72        self.coroutines.retain(|coroutine| {
73            if let CoroutineState::Running(ref mut f) = coroutine {
74                if f.manual_poll == false {
75                    if let Some(v) = resume(&mut f.future) {
76                        if f.has_value {
77                            *coroutine = CoroutineState::Value(v);
78                        } else {
79                            return false;
80                        }
81                    }
82                }
83            }
84
85            true
86        });
87    }
88
89    pub(crate) fn allocated_memory(&self) -> usize {
90        self.coroutines.allocated_memory()
91    }
92
93    pub(crate) fn active_coroutines_count(&self) -> usize {
94        self.coroutines.count()
95    }
96}
97#[derive(Clone, Copy, Debug)]
98pub struct Coroutine<T = ()> {
99    id: GenerationalId,
100    _phantom: PhantomData<T>,
101}
102
103impl<T: 'static + Any> Coroutine<T> {
104    /// Returns true if the coroutine finished or was stopped.
105    pub fn is_done(&self) -> bool {
106        let context = &get_context().coroutines_context;
107
108        let coroutine = context.coroutines.get(self.id);
109
110        if let Some(coroutine) = coroutine {
111            return coroutine.is_value() || coroutine.is_nothing();
112        }
113
114        return true;
115    }
116
117    pub fn retrieve(&self) -> Option<T> {
118        let context = &mut get_context().coroutines_context;
119
120        // () is a special case. Futures with () as a return type do not keep
121        // their state after finish, so just return ()
122        if self.is_done() && TypeId::of::<()>() == TypeId::of::<T>() {
123            // well, I wish tehre was a better way to do this..
124            let res = Box::new(()) as Box<dyn Any>;
125            return Some(*res.downcast().unwrap());
126        }
127
128        let coroutine = context.coroutines.get_mut(self.id);
129        if let Some(v) = coroutine.and_then(|c| c.take_value()) {
130            let res = Some(*v.downcast().unwrap());
131            context.coroutines.free(self.id);
132            return res;
133        }
134
135        None
136    }
137
138    /// By default coroutines are being polled each frame, inside the "next_frame()"
139    ///
140    /// ```skip
141    /// start_coroutine(async move {
142    ///    println!("a");
143    ///    next_frame().await;
144    ///    println!("b");
145    /// }); // <- coroutine is created, but not yet polled
146    /// println!("c"); // <- first print, "c"
147    /// next_frame().await; // coroutine will be polled for the first time
148    ///                     // will print "a"
149    /// println!("d");      // "d"
150    /// next_frame().await; // coroutine will be polled second time, pass next_frame().await and will print "b"
151    /// ```
152    /// will print "cadb" (there is a test for it, "tests/coroutine.rs:coroutine_execution_order" )
153    ///
154    /// But, sometimes, automatic polling is not nice
155    /// good example - game pause. Imagine a player that have some "update" function
156    /// and some coroutines runned. During the pause "update" just early quit, but
157    /// what with the coroutines?
158    ///
159    /// "set_manual_poll" allows to control how coroutine is beng polled
160    /// after set_manual_poll() coroutine will never be polled automatically
161    /// so player will need to poll all its coroutines inside "update" function
162    pub fn set_manual_poll(&mut self) {
163        let context = &mut get_context().coroutines_context;
164
165        if let Some(CoroutineState::Running(coroutine)) = context.coroutines.get_mut(self.id) {
166            coroutine.manual_time = Some(0.);
167            coroutine.manual_poll = true;
168        }
169    }
170
171    /// Poll coroutine once and advance coroutine's timeline by `delta_time`
172    /// Things like `wait_for_seconds` will wait for time in this local timeline`
173    /// Will panic if coroutine.manual_poll == false
174    pub fn poll(&mut self, delta_time: f64) {
175        let context = &mut get_context().coroutines_context;
176
177        let coroutine = context.coroutines.get_mut(self.id);
178
179        // coroutine was finished already
180        if coroutine.is_none() {
181            return;
182        }
183
184        let coroutine = coroutine.unwrap();
185        if let CoroutineState::Running(f) = coroutine {
186            context.active_coroutine_now = f.manual_time;
187            context.active_coroutine_delta = Some(delta_time);
188            *f.manual_time.as_mut().unwrap() += delta_time;
189            if let Some(v) = resume(&mut f.future) {
190                if f.has_value {
191                    *coroutine = CoroutineState::Value(v);
192                } else {
193                    context.coroutines.free(self.id);
194                }
195            }
196            context.active_coroutine_now = None;
197            context.active_coroutine_delta = None;
198        }
199    }
200}
201
202pub fn start_coroutine<T: 'static + Any>(
203    future: impl Future<Output = T> + 'static + Send,
204) -> Coroutine<T> {
205    let context = &mut get_context().coroutines_context;
206
207    let has_value = TypeId::of::<()>() != TypeId::of::<T>();
208
209    let id = context
210        .coroutines
211        .push(CoroutineState::Running(CoroutineInternal {
212            future: Box::pin(async { Box::new(future.await) as _ }),
213            has_value,
214            manual_poll: false,
215            manual_time: None,
216        }));
217
218    Coroutine {
219        id,
220        _phantom: PhantomData,
221    }
222}
223
224pub fn stop_all_coroutines() {
225    let context = &mut get_context().coroutines_context;
226
227    context.coroutines.clear();
228}
229
230pub fn stop_coroutine(coroutine: Coroutine) {
231    let context = &mut get_context().coroutines_context;
232
233    context.coroutines.free(coroutine.id);
234}
235
236pub struct TimerDelayFuture {
237    pub(crate) remaining_time: f32,
238}
239
240impl Future for TimerDelayFuture {
241    type Output = Option<()>;
242
243    fn poll(mut self: Pin<&mut Self>, _: &mut Context) -> Poll<Self::Output> {
244        let delta = get_context()
245            .coroutines_context
246            .active_coroutine_delta
247            .unwrap_or(crate::time::get_frame_time() as _);
248
249        self.remaining_time -= delta as f32;
250
251        if self.remaining_time <= 0.0 {
252            Poll::Ready(Some(()))
253        } else {
254            Poll::Pending
255        }
256    }
257}
258
259pub const fn wait_seconds(time: f32) -> TimerDelayFuture {
260    TimerDelayFuture {
261        remaining_time: time,
262    }
263}
264
265/// Special built-in coroutines for modifying values over time.
266pub mod tweens {
267    use crate::experimental::scene::{Handle, Lens, Node};
268    use std::future::Future;
269    use std::pin::Pin;
270    use std::{
271        ops::{Add, Mul, Sub},
272        task::{Context, Poll},
273    };
274
275    pub struct LinearTweenFuture<T>
276    where
277        T: Copy + Add<Output = T> + Sub<Output = T> + Mul<f32, Output = T>,
278    {
279        from: T,
280        to: T,
281        lens: Lens<T>,
282        start_time: f64,
283        time: f32,
284    }
285    impl<T> Unpin for LinearTweenFuture<T> where
286        T: Copy + Add<Output = T> + Sub<Output = T> + Mul<f32, Output = T>
287    {
288    }
289
290    impl<T> Future for LinearTweenFuture<T>
291    where
292        T: Copy + Add<Output = T> + Sub<Output = T> + Mul<f32, Output = T>,
293    {
294        type Output = ();
295
296        fn poll(self: Pin<&mut Self>, _: &mut Context) -> Poll<Self::Output> {
297            let t = (miniquad::date::now() - self.start_time) / self.time as f64;
298            let this = self.get_mut();
299            let var = this.lens.get();
300
301            // node with value was deleted
302            if var.is_none() {
303                return Poll::Ready(());
304            }
305            let var = var.unwrap();
306
307            if t <= 1. {
308                *var = this.from + (this.to - this.from) * t as f32;
309
310                Poll::Pending
311            } else {
312                *var = this.to;
313
314                Poll::Ready(())
315            }
316        }
317    }
318
319    pub fn linear<T, T1, F>(
320        handle: Handle<T1>,
321        lens: F,
322        from: T,
323        to: T,
324        time: f32,
325    ) -> LinearTweenFuture<T>
326    where
327        T: Copy + Add<Output = T> + Sub<Output = T> + Mul<f32, Output = T>,
328        T1: Node,
329        F: for<'r> FnMut(&'r mut T1) -> &'r mut T,
330    {
331        LinearTweenFuture {
332            to,
333            from,
334            lens: handle.lens(lens),
335            time,
336            start_time: miniquad::date::now(),
337        }
338    }
339
340    pub async fn follow_path<T, T1, F>(handle: Handle<T1>, mut lens: F, path: Vec<T>, time: f32)
341    where
342        T: Copy + Add<Output = T> + Sub<Output = T> + Mul<f32, Output = T>,
343        T1: Node,
344        F: for<'r> FnMut(&'r mut T1) -> &'r mut T,
345    {
346        for point in path.windows(2) {
347            linear(
348                handle,
349                &mut lens,
350                point[0],
351                point[1],
352                time / path.len() as f32,
353            )
354            .await
355        }
356    }
357}