| 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 RawWarker |
| 5 | #![warn (missing_docs)] |
| 6 | |
| 7 | //! This module contains the code that runs futures |
| 8 | |
| 9 | use crate::api::EventLoopError; |
| 10 | use crate::SlintContext; |
| 11 | use alloc::boxed::Box; |
| 12 | use alloc::task::Wake; |
| 13 | use alloc::vec::Vec; |
| 14 | use core::future::Future; |
| 15 | use core::ops::DerefMut; |
| 16 | use core::pin::Pin; |
| 17 | use core::task::Poll; |
| 18 | use portable_atomic as atomic; |
| 19 | |
| 20 | enum FutureState<T> { |
| 21 | Running(Pin<Box<dyn Future<Output = T>>>), |
| 22 | Finished(Option<T>), |
| 23 | } |
| 24 | |
| 25 | struct FutureRunnerInner<T> { |
| 26 | fut: FutureState<T>, |
| 27 | wakers: Vec<core::task::Waker>, |
| 28 | } |
| 29 | |
| 30 | struct 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 | |
| 41 | impl<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)] |
| 56 | unsafe impl<T> Send for FutureRunner<T> {} |
| 57 | #[allow (unsafe_code)] |
| 58 | unsafe impl<T> Sync for FutureRunner<T> {} |
| 59 | |
| 60 | impl<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 | .expect("No event loop despite we checked" ); |
| 85 | } |
| 86 | } |
| 87 | |
| 88 | /// The return value of the `spawn_local()` function |
| 89 | /// |
| 90 | /// Can be used to abort the future, or to get the value from a different thread with `.await` |
| 91 | /// |
| 92 | /// This trait implements future. Polling it after it finished or aborted may result in a panic. |
| 93 | pub struct JoinHandle<T>(alloc::sync::Arc<FutureRunner<T>>); |
| 94 | |
| 95 | impl<T> Future for JoinHandle<T> { |
| 96 | type Output = T; |
| 97 | |
| 98 | fn poll(self: Pin<&mut Self>, cx: &mut core::task::Context<'_>) -> Poll<Self::Output> { |
| 99 | let mut inner: impl DerefMut> = self.0.inner(); |
| 100 | match &mut inner.fut { |
| 101 | FutureState::Running(_) => { |
| 102 | let waker: &Waker = cx.waker(); |
| 103 | if !inner.wakers.iter().any(|w: &Waker| w.will_wake(waker)) { |
| 104 | inner.wakers.push(waker.clone()); |
| 105 | } |
| 106 | Poll::Pending |
| 107 | } |
| 108 | FutureState::Finished(x: &mut Option) => { |
| 109 | Poll::Ready(x.take().expect(msg:"Polling completed or aborted JoinHandle" )) |
| 110 | } |
| 111 | } |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | impl<T> JoinHandle<T> { |
| 116 | /// If the future hasn't completed yet, this will make the event loop stop polling the corresponding future and it will be dropped |
| 117 | /// |
| 118 | /// Once this handle has been aborted, it can no longer be polled |
| 119 | pub fn abort(self) { |
| 120 | self.0.aborted.store(val:true, order:atomic::Ordering::Relaxed); |
| 121 | } |
| 122 | /// Checks if the task associated with this `JoinHandle` has finished. |
| 123 | pub fn is_finished(&self) -> bool { |
| 124 | matches!(self.0.inner().fut, FutureState::Finished(_)) |
| 125 | } |
| 126 | } |
| 127 | |
| 128 | #[cfg (feature = "std" )] |
| 129 | #[allow (unsafe_code)] |
| 130 | // Safety: JoinHandle doesn't access the future, only the |
| 131 | unsafe impl<T: Send> Send for JoinHandle<T> {} |
| 132 | |
| 133 | /// Implementation for [`SlintContext::spawn_local`] |
| 134 | pub(crate) fn spawn_local_with_ctx<F: Future + 'static>( |
| 135 | ctx: &SlintContext, |
| 136 | fut: F, |
| 137 | ) -> Result<JoinHandle<F::Output>, EventLoopError> { |
| 138 | let arc: Arc::Output>> = alloc::sync::Arc::new(data:FutureRunner { |
| 139 | #[cfg (feature = "std" )] |
| 140 | thread: std::thread::current().id(), |
| 141 | inner: FutureRunnerInner { fut: FutureState::Running(Box::pin(fut)), wakers: Vec::new() } |
| 142 | .into(), |
| 143 | aborted: Default::default(), |
| 144 | proxy: ctx.event_loop_proxy().ok_or(err:EventLoopError::NoEventLoopProvider)?, |
| 145 | }); |
| 146 | arc.wake_by_ref(); |
| 147 | Ok(JoinHandle(arc)) |
| 148 | } |
| 149 | |