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        Self::new_raw_boxed(event_loop_waker, Box::pin(task.into_future()))
47    }
48
49    /// Like like `new_raw` with already boxed and pinned future.
50    pub fn new_raw_boxed(event_loop_waker: Waker, task: Pin<Box<dyn Future<Output = R> + Send + 'static>>) -> Self {
51        UiTask(UiTaskState::Pending {
52            future: task,
53            event_loop_waker,
54            #[cfg(debug_assertions)]
55            last_update: None,
56        })
57    }
58
59    /// Polls the future if needed, returns a reference to the result if the task is done.
60    ///
61    /// This does not poll the future if the task is done.
62    ///
63    /// # App Update
64    ///
65    /// This method must be called only once per app update, if it is called more than once it will cause **execution bugs**,
66    /// futures like [`task::yield_now`] will not work correctly, variables will have old values when a new one
67    /// is expected and any other number of hard to debug issues will crop-up.
68    ///
69    /// In debug builds this is validated and an error message is logged if incorrect updates are detected.
70    ///
71    /// [`task::yield_now`]: crate::yield_now
72    pub fn update(&mut self) -> Option<&R> {
73        if let UiTaskState::Pending {
74            future,
75            event_loop_waker,
76            #[cfg(debug_assertions)]
77            last_update,
78            ..
79        } = &mut self.0
80        {
81            #[cfg(debug_assertions)]
82            {
83                let update = Some(zng_var::VARS.update_id());
84                if *last_update == update {
85                    tracing::error!("UiTask::update called twice in the same update");
86                }
87                *last_update = update;
88            }
89
90            if let Poll::Ready(r) = future.as_mut().poll(&mut std::task::Context::from_waker(event_loop_waker)) {
91                self.0 = UiTaskState::Ready(r);
92            }
93        }
94
95        if let UiTaskState::Ready(r) = &self.0 { Some(r) } else { None }
96    }
97
98    /// Returns `true` if the task is done.
99    ///
100    /// This does not poll the future.
101    pub fn is_ready(&self) -> bool {
102        matches!(&self.0, UiTaskState::Ready(_))
103    }
104
105    /// Returns the result if the task is completed.
106    ///
107    /// This does not poll the future, you must call [`update`] to poll until a result is available,
108    /// then call this method to take ownership of the result.
109    ///
110    /// [`update`]: Self::update
111    pub fn into_result(mut self) -> Result<R, Self> {
112        match mem::replace(&mut self.0, UiTaskState::Cancelled) {
113            UiTaskState::Ready(r) => Ok(r),
114            p @ UiTaskState::Pending { .. } => Err(Self(p)),
115            UiTaskState::Cancelled => unreachable!(),
116        }
117    }
118
119    /// Drop the task without logging a warning if it is pending.
120    pub fn cancel(mut self) {
121        self.0 = UiTaskState::Cancelled;
122    }
123}
124impl<R: Send + 'static> IntoFuture for UiTask<R> {
125    type Output = R;
126
127    type IntoFuture = Pin<Box<dyn Future<Output = R> + Send>>;
128
129    fn into_future(mut self) -> Self::IntoFuture {
130        match mem::replace(&mut self.0, UiTaskState::Cancelled) {
131            UiTaskState::Pending { future, .. } => future,
132            UiTaskState::Ready(r) => Box::pin(async move { r }),
133            UiTaskState::Cancelled => unreachable!(),
134        }
135    }
136}
137impl<R> Drop for UiTask<R> {
138    fn drop(&mut self) {
139        if let UiTaskState::Pending { .. } = &self.0 {
140            #[cfg(debug_assertions)]
141            {
142                tracing::warn!("pending UiTask<{}> dropped", std::any::type_name::<R>());
143            }
144            #[cfg(not(debug_assertions))]
145            {
146                tracing::warn!("pending UiTask dropped");
147            }
148        }
149    }
150}