ark_module/
runtime.rs

1#![allow(unsafe_code)] // future runtime require unsafe code to be implemented
2#![allow(clippy::undocumented_unsafe_blocks)] // TODO: remove and comment blocks instead
3
4use futures::channel::oneshot::channel;
5use futures::channel::oneshot::Receiver;
6use futures::FutureExt;
7use std::future::Future;
8use std::pin::Pin;
9use std::task::Context;
10use std::task::Poll;
11
12/// Simple single-threaded futures runtime for Wasm
13///
14/// This is only available if building with the `with_async` feature flag:
15/// ```toml
16/// [dependencies]
17/// ark-api = { version = "*", features = ["with_async"] }
18/// ```
19///
20/// ## Usage
21///
22/// To use this runtime to run async code call the `spawn` method on a future and
23/// then call `poll` on every frame.
24///
25/// If you are writing an applet you may want to create a new `Runtime` in your
26/// `new` method and call `poll` on every frame in the `update` method.
27///
28/// ```rust
29/// # use ark_module::Runtime;
30/// # use futures::{Future, channel::oneshot::Receiver};
31/// # pub trait Applet {
32/// #     fn new() -> Self;
33/// #     fn update(&mut self);
34/// # }
35/// # async fn some_async_function() -> String { "Hello".to_string() }
36///
37/// struct Module {
38///     runtime: Runtime,
39///     channel: Receiver<String>,
40/// }
41///
42/// impl Applet for Module {
43///     fn new() -> Self {
44///         let mut runtime = Runtime::new();
45///
46///         // Call an async function to get a future
47///         let future = some_async_function();
48///         // Run the future on the runtime, getting back a channel for the final result
49///         let channel = runtime.spawn(future);
50///
51///         Self { channel, runtime }
52///     }
53///
54///     fn update(&mut self) {
55///         // Poll the runtime to run the future
56///         self.runtime.poll();
57///
58///         // Check the channel to see if the future has resolved yet
59///         match self.channel.try_recv() {
60///             Ok(Some(value)) => println!("The future resolved returning {}", value),
61///             _ => println!("Still waiting for the future"),
62///         };
63///     }
64/// }
65/// ```
66///
67/// Here we are adding the future to the runtime with the `spawn` method in the
68/// `new` method, but futures can be added to the runtime at any point in your
69/// Ark module.
70///
71#[derive(Default)]
72pub struct Runtime {
73    tasks: Vec<Pin<Box<dyn Future<Output = ()> + 'static + Send>>>,
74    // Reuse allocations, instead of allocating for every poll. This can be removed when
75    // drain_filter is on stable.
76    allocation: Vec<Pin<Box<dyn Future<Output = ()> + 'static + Send>>>,
77}
78
79// Default implementations for a waker which is required for polling futures
80pub(crate) mod waker {
81    use std::task::RawWaker;
82    use std::task::RawWakerVTable;
83    use std::task::Waker;
84    unsafe fn clone(_: *const ()) -> RawWaker {
85        RAW_WAKER
86    }
87    unsafe fn wake(_: *const ()) {}
88    unsafe fn wake_by_ref(_: *const ()) {}
89    unsafe fn drop(_: *const ()) {}
90    const VTABLE: &RawWakerVTable = &RawWakerVTable::new(clone, wake, wake_by_ref, drop);
91    const RAW_WAKER: RawWaker = RawWaker::new(std::ptr::null(), VTABLE);
92
93    pub fn waker() -> Waker {
94        unsafe { Waker::from_raw(RAW_WAKER) }
95    }
96}
97impl Runtime {
98    /// Creates new runtime
99    pub fn new() -> Self {
100        Default::default()
101    }
102
103    /// Poll the runtime to update futures.
104    /// This should be called on every frame until all futures managed by the
105    /// runtime have resolved.
106    pub fn poll(&mut self) {
107        let waker = waker::waker();
108        let mut context = Context::from_waker(&waker);
109        let unfinished_futures = self.tasks.drain(..).filter_map(|mut future| {
110            match future.as_mut().poll(&mut context) {
111                Poll::Ready(_) => None,
112                Poll::Pending => Some(future),
113            }
114        });
115        self.allocation.extend(unfinished_futures);
116        std::mem::swap(&mut self.tasks, &mut self.allocation);
117    }
118
119    /// Spawn a future, returning a channel that will receive the result of the
120    /// future once it resolves.
121    ///
122    /// Ensure to use `.try_recv` rather than `.recv` when checking if the
123    /// channel contains the result of the future as `.recv` will block until the
124    /// future resolves, resulting in poor player experience and performance.
125    pub fn spawn<T: Send + 'static>(
126        &mut self,
127        future: impl Future<Output = T> + 'static + Send,
128    ) -> Receiver<T> {
129        let (tx, rx) = channel();
130        let task = async move {
131            let r = future.await;
132            // We ignore the error because the receiver has been dropped.
133            let _ = tx.send(r);
134        };
135        self.tasks.push(task.boxed());
136        rx
137    }
138
139    /// Drives a future to completion and returns the result.
140    ///
141    /// Note this function is asynchronous and will block until the future has
142    /// resolved. Using this function in your game's update loop will likely
143    /// result in poor player experience and performance.
144    pub fn block_on<T>(&mut self, mut future: impl Future<Output = T>) -> T {
145        let waker = waker::waker();
146        let mut context = Context::from_waker(&waker);
147
148        // We pin the future to the stack, so that it doesn't have to be 'static or `Send`.
149        let mut pinned_future = unsafe { Pin::new_unchecked(&mut future) };
150
151        // [TODO] Avoid busy loop here.
152        loop {
153            if let Poll::Ready(result) = pinned_future.as_mut().poll(&mut context) {
154                return result;
155            }
156            // We still have to poll other futures, as this future might spawn futures.
157            self.poll();
158        }
159    }
160}