Skip to main content

lgui_core/core/
task.rs

1use std::{future::Future, pin::Pin, sync::Arc};
2
3#[cfg(feature = "async")]
4use std::{
5    sync::{
6        atomic::{AtomicBool, Ordering},
7        Mutex,
8    },
9    task::{Context, Poll, Waker},
10};
11
12pub type UiTask = Pin<Box<dyn Future<Output = ()> + Send + 'static>>;
13
14pub trait UiExecutor: Send + Sync + 'static {
15    fn spawn(&self, task: UiTask);
16}
17
18impl<F> UiExecutor for F
19where
20    F: Fn(UiTask) + Send + Sync + 'static,
21{
22    fn spawn(&self, task: UiTask) {
23        self(task);
24    }
25}
26
27pub type UiTaskSpawner = Arc<dyn UiExecutor>;
28
29pub fn noop_task_spawner() -> UiTaskSpawner {
30    Arc::new(|_| {})
31}
32
33#[cfg(feature = "tokio")]
34#[derive(Clone)]
35pub struct TokioExecutor {
36    handle: tokio::runtime::Handle,
37}
38
39#[cfg(feature = "tokio")]
40impl TokioExecutor {
41    pub fn new(handle: tokio::runtime::Handle) -> Self {
42        Self { handle }
43    }
44
45    pub fn current() -> Self {
46        Self::new(tokio::runtime::Handle::current())
47    }
48}
49
50#[cfg(feature = "tokio")]
51impl UiExecutor for TokioExecutor {
52    fn spawn(&self, task: UiTask) {
53        self.handle.spawn(task);
54    }
55}
56
57#[cfg(feature = "async")]
58#[derive(Default)]
59struct CancellationState {
60    cancelled: AtomicBool,
61    waker: Mutex<Option<Waker>>,
62}
63
64#[cfg(feature = "async")]
65pub(crate) struct UiTaskCancellation {
66    state: Arc<CancellationState>,
67}
68
69#[cfg(feature = "async")]
70impl UiTaskCancellation {
71    pub(crate) fn cancel(self) {
72        self.state.cancelled.store(true, Ordering::Release);
73        if let Some(waker) = self
74            .state
75            .waker
76            .lock()
77            .expect("task cancellation waker poisoned")
78            .take()
79        {
80            waker.wake();
81        }
82    }
83}
84
85#[cfg(feature = "async")]
86struct CancellationFuture {
87    state: Arc<CancellationState>,
88}
89
90#[cfg(feature = "async")]
91impl Future for CancellationFuture {
92    type Output = ();
93
94    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
95        if self.state.cancelled.load(Ordering::Acquire) {
96            return Poll::Ready(());
97        }
98
99        let mut waker = self
100            .state
101            .waker
102            .lock()
103            .expect("task cancellation waker poisoned");
104        if self.state.cancelled.load(Ordering::Acquire) {
105            return Poll::Ready(());
106        }
107        if waker
108            .as_ref()
109            .is_none_or(|registered| !registered.will_wake(cx.waker()))
110        {
111            *waker = Some(cx.waker().clone());
112        }
113        Poll::Pending
114    }
115}
116
117#[cfg(feature = "async")]
118struct CancellableTask {
119    task: UiTask,
120    cancelled: CancellationFuture,
121}
122
123#[cfg(feature = "async")]
124impl Future for CancellableTask {
125    type Output = ();
126
127    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
128        if self.task.as_mut().poll(cx).is_ready() {
129            return Poll::Ready(());
130        }
131        Pin::new(&mut self.cancelled).poll(cx)
132    }
133}
134
135#[cfg(feature = "async")]
136pub(crate) fn cancellable_task(task: UiTask) -> (UiTaskCancellation, UiTask) {
137    let state = Arc::new(CancellationState::default());
138    (
139        UiTaskCancellation {
140            state: Arc::clone(&state),
141        },
142        Box::pin(CancellableTask {
143            task,
144            cancelled: CancellationFuture { state },
145        }),
146    )
147}
148
149#[cfg(all(test, feature = "async"))]
150#[path = "task_test.rs"]
151mod tests;