zng_task/
ui.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
//! UI-thread bound tasks.

use std::{
    fmt,
    future::{Future, IntoFuture},
    mem,
    pin::Pin,
    task::{Poll, Waker},
};

enum UiTaskState<R> {
    Pending {
        future: Pin<Box<dyn Future<Output = R> + Send>>,
        event_loop_waker: Waker,
        #[cfg(debug_assertions)]
        last_update: Option<zng_var::VarUpdateId>,
    },
    Ready(R),
    Cancelled,
}
impl<R: fmt::Debug> fmt::Debug for UiTaskState<R> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::Pending { .. } => write!(f, "Pending"),
            Self::Ready(arg0) => f.debug_tuple("Ready").field(arg0).finish(),
            Self::Cancelled => unreachable!(),
        }
    }
}

/// Represents a [`Future`] running in sync with the UI.
///
/// The future [`Waker`], wakes the app event loop and causes an update, in an update handler
/// of the task owner [`update`] is called, if this task waked the app the future is polled once.
///
/// [`Waker`]: std::task::Waker
/// [`update`]: UiTask::update
#[derive(Debug)]
pub struct UiTask<R>(UiTaskState<R>);
impl<R> UiTask<R> {
    /// New task with already build event-loop waker.
    ///
    /// App crate provides an integrated `UiTaskWidget::new` that creates the waker for widgets.
    pub fn new_raw<F>(event_loop_waker: Waker, task: impl IntoFuture<IntoFuture = F>) -> Self
    where
        F: Future<Output = R> + Send + 'static,
    {
        UiTask(UiTaskState::Pending {
            future: Box::pin(task.into_future()),
            event_loop_waker,
            #[cfg(debug_assertions)]
            last_update: None,
        })
    }

    /// Polls the future if needed, returns a reference to the result if the task is done.
    ///
    /// This does not poll the future if the task is done.
    ///
    /// # App Update
    ///
    /// This method must be called only once per app update, if it is called more than once it will cause **execution bugs**,
    /// futures like [`task::yield_now`] will not work correctly, variables will have old values when a new one
    /// is expected and any other number of hard to debug issues will crop-up.
    ///
    /// In debug builds this is validated and an error message is logged if incorrect updates are detected.
    ///
    /// [`task::yield_now`]: crate::yield_now
    pub fn update(&mut self) -> Option<&R> {
        if let UiTaskState::Pending {
            future,
            event_loop_waker,
            #[cfg(debug_assertions)]
            last_update,
            ..
        } = &mut self.0
        {
            #[cfg(debug_assertions)]
            {
                let update = Some(zng_var::VARS.update_id());
                if *last_update == update {
                    tracing::error!("UiTask::update called twice in the same update");
                }
                *last_update = update;
            }

            if let Poll::Ready(r) = future.as_mut().poll(&mut std::task::Context::from_waker(event_loop_waker)) {
                self.0 = UiTaskState::Ready(r);
            }
        }

        if let UiTaskState::Ready(r) = &self.0 {
            Some(r)
        } else {
            None
        }
    }

    /// Returns `true` if the task is done.
    ///
    /// This does not poll the future.
    pub fn is_ready(&self) -> bool {
        matches!(&self.0, UiTaskState::Ready(_))
    }

    /// Returns the result if the task is completed.
    ///
    /// This does not poll the future, you must call [`update`] to poll until a result is available,
    /// then call this method to take ownership of the result.
    ///
    /// [`update`]: Self::update
    pub fn into_result(mut self) -> Result<R, Self> {
        match mem::replace(&mut self.0, UiTaskState::Cancelled) {
            UiTaskState::Ready(r) => Ok(r),
            p @ UiTaskState::Pending { .. } => Err(Self(p)),
            UiTaskState::Cancelled => unreachable!(),
        }
    }

    /// Drop the task without logging a warning if it is pending.
    pub fn cancel(mut self) {
        self.0 = UiTaskState::Cancelled;
    }
}
impl<R> Drop for UiTask<R> {
    fn drop(&mut self) {
        if let UiTaskState::Pending { .. } = &self.0 {
            #[cfg(debug_assertions)]
            {
                tracing::warn!("pending UiTask<{}> dropped", std::any::type_name::<R>());
            }
            #[cfg(not(debug_assertions))]
            {
                tracing::warn!("pending UiTask dropped");
            }
        }
    }
}