zng_task/
ui.rs

1//! UI-thread bound tasks.
2
3use std::{
4    fmt, mem,
5    pin::Pin,
6    task::{Poll, Waker},
7};
8
9enum UiTaskState<R> {
10    Pending {
11        future: Pin<Box<dyn Future<Output = R> + Send>>,
12        event_loop_waker: Waker,
13        #[cfg(debug_assertions)]
14        last_update: Option<zng_var::VarUpdateId>,
15    },
16    Ready(R),
17    Cancelled,
18}
19impl<R: fmt::Debug> fmt::Debug for UiTaskState<R> {
20    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21        match self {
22            Self::Pending { .. } => write!(f, "Pending"),
23            Self::Ready(arg0) => f.debug_tuple("Ready").field(arg0).finish(),
24            Self::Cancelled => unreachable!(),
25        }
26    }
27}
28
29/// Represents a [`Future`] running in sync with the UI.
30///
31/// The future [`Waker`], wakes the app event loop and causes an update, in an update handler
32/// of the task owner [`update`] is called, if this task waked the app the future is polled once.
33///
34/// [`Waker`]: std::task::Waker
35/// [`update`]: UiTask::update
36#[derive(Debug)]
37pub struct UiTask<R>(UiTaskState<R>);
38impl<R> UiTask<R> {
39    /// New task with already build event-loop waker.
40    ///
41    /// App crate provides an integrated `UiTaskWidget::new` that creates the waker for widgets.
42    pub fn new_raw<F>(event_loop_waker: Waker, task: impl IntoFuture<IntoFuture = F>) -> Self
43    where
44        F: Future<Output = R> + Send + 'static,
45    {
46        UiTask(UiTaskState::Pending {
47            future: Box::pin(task.into_future()),
48            event_loop_waker,
49            #[cfg(debug_assertions)]
50            last_update: None,
51        })
52    }
53
54    /// Polls the future if needed, returns a reference to the result if the task is done.
55    ///
56    /// This does not poll the future if the task is done.
57    ///
58    /// # App Update
59    ///
60    /// This method must be called only once per app update, if it is called more than once it will cause **execution bugs**,
61    /// futures like [`task::yield_now`] will not work correctly, variables will have old values when a new one
62    /// is expected and any other number of hard to debug issues will crop-up.
63    ///
64    /// In debug builds this is validated and an error message is logged if incorrect updates are detected.
65    ///
66    /// [`task::yield_now`]: crate::yield_now
67    pub fn update(&mut self) -> Option<&R> {
68        if let UiTaskState::Pending {
69            future,
70            event_loop_waker,
71            #[cfg(debug_assertions)]
72            last_update,
73            ..
74        } = &mut self.0
75        {
76            #[cfg(debug_assertions)]
77            {
78                let update = Some(zng_var::VARS.update_id());
79                if *last_update == update {
80                    tracing::error!("UiTask::update called twice in the same update");
81                }
82                *last_update = update;
83            }
84
85            if let Poll::Ready(r) = future.as_mut().poll(&mut std::task::Context::from_waker(event_loop_waker)) {
86                self.0 = UiTaskState::Ready(r);
87            }
88        }
89
90        if let UiTaskState::Ready(r) = &self.0 { Some(r) } else { None }
91    }
92
93    /// Returns `true` if the task is done.
94    ///
95    /// This does not poll the future.
96    pub fn is_ready(&self) -> bool {
97        matches!(&self.0, UiTaskState::Ready(_))
98    }
99
100    /// Returns the result if the task is completed.
101    ///
102    /// This does not poll the future, you must call [`update`] to poll until a result is available,
103    /// then call this method to take ownership of the result.
104    ///
105    /// [`update`]: Self::update
106    pub fn into_result(mut self) -> Result<R, Self> {
107        match mem::replace(&mut self.0, UiTaskState::Cancelled) {
108            UiTaskState::Ready(r) => Ok(r),
109            p @ UiTaskState::Pending { .. } => Err(Self(p)),
110            UiTaskState::Cancelled => unreachable!(),
111        }
112    }
113
114    /// Drop the task without logging a warning if it is pending.
115    pub fn cancel(mut self) {
116        self.0 = UiTaskState::Cancelled;
117    }
118}
119impl<R> Drop for UiTask<R> {
120    fn drop(&mut self) {
121        if let UiTaskState::Pending { .. } = &self.0 {
122            #[cfg(debug_assertions)]
123            {
124                tracing::warn!("pending UiTask<{}> dropped", std::any::type_name::<R>());
125            }
126            #[cfg(not(debug_assertions))]
127            {
128                tracing::warn!("pending UiTask dropped");
129            }
130        }
131    }
132}