Skip to main content

ferroui_xaml_interactions/
action_task.rs

1//! Not from the reference: the `Task` of the asynchronous actions of the crate.
2//!
3//! The reference returns a `System.Threading.Tasks.Task` from the actions that run asynchronously
4//! (`DelayAction`, `AsyncActionGroup`, the picker actions), and `AsyncActionGroup` awaits the task
5//! an action returns. Here such an action returns a [`TaskHandle`]: a task of the dispatcher of the
6//! user interface ([`DispatcherTask`]) that can be held in an untyped value (it compares by
7//! identity) and awaited.
8//!
9//! Deviation (DEVIATIONS.md, Behaviors): `Task` and `Task.Delay` are [`TaskHandle`] and [`delay`],
10//! futures run by the dispatcher of the user interface.
11
12use ferroui_base::reactive::IDisposable;
13use ferroui_base::threading::{Dispatcher, DispatcherPriority, DispatcherTask, DispatcherTimer, OperationCanceledError};
14use std::cell::RefCell;
15use std::fmt;
16use std::future::Future;
17use std::pin::Pin;
18use std::rc::Rc;
19use std::task::{Context, Poll, Waker};
20use std::time::Duration;
21
22/// A task of the dispatcher of the user interface, held as an untyped value (`Task<T>`).
23///
24/// Clones refer to the same task and compare equal; the result of the task can be taken once
25/// (awaiting it takes it).
26pub struct TaskHandle<T: 'static> {
27    task: DispatcherTask<T>,
28    identity: Rc<()>,
29}
30
31/// The task of an action that produces no value (`Task`).
32pub type ActionTask = TaskHandle<()>;
33
34impl<T: 'static> TaskHandle<T> {
35    /// Runs `future` on the dispatcher of the user interface and returns its task
36    /// (`Dispatcher.UIThread.InvokeAsync(async () => ..)`). The future starts in a job of the
37    /// dispatcher.
38    ///
39    /// # Panics
40    /// Panics when called from a thread other than the thread of the user interface.
41    pub fn spawn<Fut>(future: Fut) -> Self
42    where
43        Fut: Future<Output = T> + 'static,
44    {
45        Self::from_task(Dispatcher::ui_thread().invoke_async_task_local(move || future))
46    }
47
48    /// The handle of an existing dispatcher task.
49    pub fn from_task(task: DispatcherTask<T>) -> Self {
50        Self { task, identity: Rc::new(()) }
51    }
52
53    /// The dispatcher task.
54    pub fn task(&self) -> &DispatcherTask<T> {
55        &self.task
56    }
57
58    /// Whether the task has finished: completed, failed or canceled (`IsCompleted`).
59    pub fn is_completed(&self) -> bool {
60        self.task.is_completed()
61    }
62
63    /// Whether the task ran to the end (`IsCompletedSuccessfully`).
64    pub fn is_completed_successfully(&self) -> bool {
65        self.task.is_completed_successfully()
66    }
67
68    /// Whether the future of the task panicked (`IsFaulted`).
69    pub fn is_faulted(&self) -> bool {
70        self.task.is_faulted()
71    }
72
73    /// Whether the task was canceled (`IsCanceled`).
74    pub fn is_canceled(&self) -> bool {
75        self.task.is_canceled()
76    }
77}
78
79impl ActionTask {
80    /// A task that has already completed (`Task.CompletedTask`).
81    pub fn completed() -> Self {
82        Self::spawn(std::future::ready(()))
83    }
84}
85
86impl<T: 'static> Clone for TaskHandle<T> {
87    fn clone(&self) -> Self {
88        Self { task: self.task.clone(), identity: self.identity.clone() }
89    }
90}
91
92impl<T: 'static> PartialEq for TaskHandle<T> {
93    fn eq(&self, other: &Self) -> bool {
94        Rc::ptr_eq(&self.identity, &other.identity)
95    }
96}
97
98impl<T: 'static> fmt::Debug for TaskHandle<T> {
99    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100        write!(f, "TaskHandle({:?})", self.task)
101    }
102}
103
104impl<T: 'static> Future for TaskHandle<T> {
105    type Output = Result<T, OperationCanceledError>;
106
107    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
108        Pin::new(&mut self.get_mut().task).poll(cx)
109    }
110}
111
112/// The state of a [`delay`]: whether the timer fired, and who waits for it.
113#[derive(Default)]
114struct DelayState {
115    elapsed: bool,
116    waker: Option<Waker>,
117    timer: Option<Rc<dyn IDisposable>>,
118}
119
120/// The future of [`delay`].
121pub struct Delay {
122    duration: Duration,
123    state: Rc<RefCell<DelayState>>,
124}
125
126/// A future that completes after `duration` (`Task.Delay(duration)`), measured by a timer of the
127/// dispatcher of the thread that first polls it. A zero duration completes at the first poll.
128pub fn delay(duration: Duration) -> Delay {
129    Delay { duration, state: Rc::new(RefCell::new(DelayState::default())) }
130}
131
132impl Future for Delay {
133    type Output = ();
134
135    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
136        let this = self.get_mut();
137        if this.duration.is_zero() {
138            return Poll::Ready(());
139        }
140
141        let mut state = this.state.borrow_mut();
142        if state.elapsed {
143            return Poll::Ready(());
144        }
145
146        state.waker = Some(cx.waker().clone());
147        if state.timer.is_none() {
148            let shared = Rc::downgrade(&this.state);
149            state.timer = Some(DispatcherTimer::run_once(
150                move || {
151                    let Some(shared) = shared.upgrade() else {
152                        return;
153                    };
154                    let waker = {
155                        let mut state = shared.borrow_mut();
156                        state.elapsed = true;
157                        state.waker.take()
158                    };
159                    if let Some(waker) = waker {
160                        waker.wake();
161                    }
162                },
163                this.duration,
164                DispatcherPriority::BACKGROUND,
165            ));
166        }
167
168        Poll::Pending
169    }
170}
171
172impl Drop for Delay {
173    fn drop(&mut self) {
174        let timer = self.state.borrow_mut().timer.take();
175        if let Some(timer) = timer {
176            timer.dispose();
177        }
178    }
179}
180
181/// A future that polls `future` and swallows a panic of it: what a continuation that only owns
182/// clean-up needs (`try { await operation; } catch { }`). Resolves to `None` when the future
183/// panicked.
184pub(crate) struct CatchUnwind<F> {
185    future: Pin<Box<F>>,
186}
187
188impl<F: Future> CatchUnwind<F> {
189    pub(crate) fn new(future: F) -> Self {
190        Self { future: Box::pin(future) }
191    }
192}
193
194impl<F: Future> Future for CatchUnwind<F> {
195    type Output = Option<F::Output>;
196
197    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
198        let this = self.get_mut();
199        match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| this.future.as_mut().poll(cx))) {
200            Ok(Poll::Ready(output)) => Poll::Ready(Some(output)),
201            Ok(Poll::Pending) => Poll::Pending,
202            Err(_) => Poll::Ready(None),
203        }
204    }
205}