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}