use std::{
future::Future,
pin::{pin, Pin},
sync::{
atomic::{AtomicBool, AtomicI32, Ordering},
Arc,
},
task::{Context, Poll, RawWaker, RawWakerVTable, Waker},
};
use parking_lot::RwLock;
use crate::event::main_loop_manual;
use super::end_frame;
pub struct Task {
future: Pin<Box<dyn Future<Output = ()> + 'static>>,
awake: Arc<AtomicBool>,
}
unsafe impl Send for Task {}
unsafe impl Sync for Task {}
impl Task {
fn new(fut: impl Future<Output = ()> + 'static) -> Self {
Self {
future: Box::pin(fut),
awake: Arc::new(AtomicBool::new(true)),
}
}
}
pub struct Executor {
tasks: RwLock<Vec<Task>>,
to_spawn: RwLock<Vec<Task>>,
wake_next_frame: RwLock<Vec<Arc<AtomicBool>>>,
}
impl Executor {
fn run_until_sleep(&self) {
let mut tasks = self.tasks.write();
let mut wake_next_frame = self.wake_next_frame.write();
let mut any_awake = true;
for waker in wake_next_frame.drain(..) {
waker.store(true, Ordering::Relaxed);
}
for new_task in self.to_spawn.write().drain(..) {
tasks.push(new_task);
}
while any_awake {
any_awake = false;
let mut i = 0;
while i < tasks.len() {
let mut remove = false;
if tasks[i].awake.load(Ordering::Relaxed) {
any_awake = true;
AWAIT_REASON.store(AwaitReason::Waker as i32, Ordering::Relaxed);
let awake_bool = tasks[i].awake.clone();
let result = pin!(&mut tasks[i].future)
.poll(&mut Context::from_waker(&create_waker(awake_bool)));
if let Poll::Ready(_) = result {
tasks.swap_remove(i);
remove = true;
} else {
match AwaitReason::from(AWAIT_REASON.load(Ordering::Relaxed)) {
AwaitReason::Waker => {
tasks[i].awake.store(false, Ordering::Relaxed);
}
AwaitReason::NextFrame => {
tasks[i].awake.store(false, Ordering::Relaxed);
wake_next_frame.push(tasks[i].awake.clone());
}
AwaitReason::Yield => {
}
}
}
}
for new_task in self.to_spawn.write().drain(..) {
tasks.push(new_task);
}
if !remove {
i += 1;
}
}
}
}
fn spawn(&self, task: Task) {
self.to_spawn.write().push(task);
}
}
static ASYNC_EXECUTOR: Executor = Executor {
tasks: RwLock::new(Vec::new()),
to_spawn: RwLock::new(Vec::new()),
wake_next_frame: RwLock::new(Vec::new()),
};
enum AwaitReason {
Waker = 0,
Yield = 1,
NextFrame = 2,
}
impl From<i32> for AwaitReason {
fn from(value: i32) -> Self {
match value {
1 => AwaitReason::Yield,
2 => AwaitReason::NextFrame,
_ => AwaitReason::Waker,
}
}
}
static AWAIT_REASON: AtomicI32 = AtomicI32::new(0);
const WAKER_VTABLE: RawWakerVTable =
RawWakerVTable::new(waker_clone, waker_wake, waker_wake_by_ref, waker_drop);
fn waker_clone(waker: *const ()) -> RawWaker {
let waker = unsafe { &*(waker as *const Arc<AtomicBool>) };
let waker = waker.clone();
let waker_ptr = Box::into_raw(Box::new(waker));
RawWaker::new(waker_ptr as *const (), &WAKER_VTABLE)
}
fn waker_wake(waker: *const ()) {
let waker = unsafe { Box::from_raw(waker as *mut Arc<AtomicBool>) };
waker.store(true, Ordering::Relaxed);
}
fn waker_wake_by_ref(waker: *const ()) {
let waker = unsafe { &*(waker as *const Arc<AtomicBool>) };
waker.store(true, Ordering::Relaxed);
}
fn waker_drop(waker: *const ()) {
let _waker = unsafe { Box::from_raw(waker as *mut Arc<AtomicBool>) };
}
fn create_waker(waker: Arc<AtomicBool>) -> Waker {
let waker_ptr = Box::into_raw(Box::new(waker));
let raw_waker = RawWaker::new(waker_ptr as *const (), &WAKER_VTABLE);
unsafe { Waker::from_raw(raw_waker) }
}
pub fn async_executor(fut: impl Future<Output = ()> + 'static, call_end_frame: bool) {
ASYNC_EXECUTOR.spawn(Task::new(async move {
fut.await;
}));
main_loop_manual(super::init, move |()| {
ASYNC_EXECUTOR.run_until_sleep();
if call_end_frame {
end_frame();
}
});
}
pub async fn next_frame() {
let mut ready = false;
std::future::poll_fn(move |_| {
if ready {
Poll::Ready(())
} else {
AWAIT_REASON.store(AwaitReason::NextFrame as i32, Ordering::Relaxed);
ready = true;
Poll::Pending
}
})
.await;
}
pub async fn async_yield() {
let mut ready = false;
std::future::poll_fn(move |_| {
if ready {
Poll::Ready(())
} else {
AWAIT_REASON.store(AwaitReason::Yield as i32, Ordering::Relaxed);
ready = true;
Poll::Pending
}
})
.await;
}
pub fn spawn(task: impl Future<Output = ()> + 'static) {
ASYNC_EXECUTOR.spawn(Task {
future: Box::pin(task),
awake: Arc::new(AtomicBool::new(true)),
})
}