Skip to main content

i_slint_core/
future.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4#![cfg(target_has_atomic = "ptr")] // Arc is not available. TODO: implement using RawWaker
5#![warn(missing_docs)]
6
7//! This module contains the code that runs futures
8
9use crate::SlintContext;
10use crate::api::EventLoopError;
11use alloc::boxed::Box;
12use alloc::task::Wake;
13use alloc::vec::Vec;
14use core::future::Future;
15use core::ops::DerefMut;
16use core::pin::Pin;
17use core::task::Poll;
18use portable_atomic as atomic;
19
20enum FutureState<T> {
21    Running(Pin<Box<dyn Future<Output = T>>>),
22    Finished(Option<T>),
23}
24
25struct FutureRunnerInner<T> {
26    fut: FutureState<T>,
27    wakers: Vec<core::task::Waker>,
28}
29
30struct FutureRunner<T> {
31    #[cfg(not(feature = "std"))]
32    inner: core::cell::RefCell<FutureRunnerInner<T>>,
33    #[cfg(feature = "std")]
34    inner: std::sync::Mutex<FutureRunnerInner<T>>,
35    aborted: atomic::AtomicBool,
36    proxy: Box<dyn crate::platform::EventLoopProxy>,
37    #[cfg(feature = "std")]
38    thread: std::thread::ThreadId,
39}
40
41impl<T> FutureRunner<T> {
42    fn inner(&self) -> impl DerefMut<Target = FutureRunnerInner<T>> + '_ {
43        #[cfg(feature = "std")]
44        return self.inner.lock().unwrap();
45        #[cfg(not(feature = "std"))]
46        return self.inner.borrow_mut();
47    }
48}
49
50// # Safety:
51// The Future might not be Send, but we only poll the future from the main thread.
52// (We even assert that)
53// We may access the finished value from another thread only if T is Send
54// (because JoinHandle only implement Send if T:Send)
55#[allow(unsafe_code)]
56unsafe impl<T> Send for FutureRunner<T> {}
57#[allow(unsafe_code)]
58unsafe impl<T> Sync for FutureRunner<T> {}
59
60impl<T: 'static> Wake for FutureRunner<T> {
61    fn wake(self: alloc::sync::Arc<Self>) {
62        self.clone().proxy.invoke_from_event_loop(Box::new(move || {
63            #[cfg(feature = "std")]
64            assert_eq!(self.thread, std::thread::current().id(), "the future was moved to a thread despite we checked it was created in the event loop thread");
65            let waker = self.clone().into();
66            let mut inner = self.inner();
67            let mut cx = core::task::Context::from_waker(&waker);
68            if let FutureState::Running(fut) = &mut inner.fut {
69                if self.aborted.load(atomic::Ordering::Relaxed) {
70                    inner.fut = FutureState::Finished(None);
71                } else {
72                    match fut.as_mut().poll(&mut cx) {
73                        Poll::Ready(val) => {
74                            inner.fut = FutureState::Finished(Some(val));
75                            for w in core::mem::take(&mut inner.wakers) {
76                                w.wake();
77                            }
78                        }
79                        Poll::Pending => {}
80                    }
81                }
82            }
83        }))
84        // The event loop may be gone during teardown; the future can't progress, so drop the wake.
85        .ok();
86    }
87}
88
89/// The return value of the `spawn_local()` function
90///
91/// Can be used to abort the future, or to get the value from a different thread with `.await`
92///
93/// This trait implements future. Polling it after it finished or aborted may result in a panic.
94pub struct JoinHandle<T>(alloc::sync::Arc<FutureRunner<T>>);
95
96impl<T> Future for JoinHandle<T> {
97    type Output = T;
98
99    fn poll(self: Pin<&mut Self>, cx: &mut core::task::Context<'_>) -> Poll<Self::Output> {
100        let mut inner = self.0.inner();
101        match &mut inner.fut {
102            FutureState::Running(_) => {
103                let waker = cx.waker();
104                if !inner.wakers.iter().any(|w| w.will_wake(waker)) {
105                    inner.wakers.push(waker.clone());
106                }
107                Poll::Pending
108            }
109            FutureState::Finished(x) => {
110                Poll::Ready(x.take().expect("Polling completed or aborted JoinHandle"))
111            }
112        }
113    }
114}
115
116impl<T> JoinHandle<T> {
117    /// If the future hasn't completed yet, this will make the event loop stop polling the corresponding future and it will be dropped
118    ///
119    /// Once this handle has been aborted, it can no longer be polled
120    pub fn abort(self) {
121        self.0.aborted.store(true, atomic::Ordering::Relaxed);
122    }
123    /// Checks if the task associated with this `JoinHandle` has finished.
124    pub fn is_finished(&self) -> bool {
125        matches!(self.0.inner().fut, FutureState::Finished(_))
126    }
127}
128
129#[cfg(feature = "std")]
130#[allow(unsafe_code)]
131// Safety: JoinHandle doesn't access the future, only the
132unsafe impl<T: Send> Send for JoinHandle<T> {}
133
134/// Implementation for [`SlintContext::spawn_local`]
135pub(crate) fn spawn_local_with_ctx<F: Future + 'static>(
136    ctx: &SlintContext,
137    fut: F,
138) -> Result<JoinHandle<F::Output>, EventLoopError> {
139    let arc = alloc::sync::Arc::new(FutureRunner {
140        #[cfg(feature = "std")]
141        thread: std::thread::current().id(),
142        inner: FutureRunnerInner { fut: FutureState::Running(Box::pin(fut)), wakers: Vec::new() }
143            .into(),
144        aborted: Default::default(),
145        proxy: ctx.event_loop_proxy().ok_or(EventLoopError::NoEventLoopProvider)?,
146    });
147    arc.wake_by_ref();
148    Ok(JoinHandle(arc))
149}