use std::{
collections::VecDeque,
future::Future,
panic::Location,
pin::Pin,
sync::{
Arc,
atomic::{AtomicBool, Ordering},
},
task::{Context, Poll, Waker},
};
use parking_lot::Mutex;
use crate::flash::{
diag::PrimKind,
flash_ambient,
ids::{Backend, trace_native_from_ambient},
system,
};
#[derive(Default)]
struct RealNotify {
waiters: VecDeque<(Arc<AtomicBool>, Waker)>,
permit: bool,
}
pub struct Notify {
backend: Backend,
real: Mutex<RealNotify>,
}
impl Default for Notify {
#[track_caller]
fn default() -> Self {
Self {
backend: if flash_ambient() {
let cvid = system::next_condvar_id();
system::describe_cvid(cvid, PrimKind::Notify, Location::caller());
Backend::Engine(cvid)
} else {
Backend::Native
},
real: Mutex::new(RealNotify::default()),
}
}
}
impl Notify {
pub fn notified(&self) -> Notified<'_> {
Notified {
state: NotifiedState::Init,
notify: self,
}
}
pub fn notify_one(&self) {
match self.backend {
Backend::Engine(cvid) => system::signal_notify(cvid),
Backend::Native => {
trace_native_from_ambient("notify", "notify_one");
let mut real = self.real.lock();
let waiter = real.waiters.pop_front();
match &waiter {
Some((granted, _)) => granted.store(true, Ordering::Release),
None => real.permit = true,
}
drop(real);
if let Some((_, waker)) = waiter {
waker.wake();
}
}
}
}
}
enum NotifiedState {
Init,
Engine(system::AsyncHandle),
Real(Arc<AtomicBool>),
Done,
}
pub struct Notified<'a> {
notify: &'a Notify,
state: NotifiedState,
}
impl Future for Notified<'_> {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
match &self.state {
NotifiedState::Done => return Poll::Ready(()),
NotifiedState::Engine(handle) => {
if handle.granted() {
self.state = NotifiedState::Done;
return Poll::Ready(());
}
return Poll::Pending;
}
NotifiedState::Real(granted) => {
if granted.load(Ordering::Acquire) {
self.state = NotifiedState::Done;
return Poll::Ready(());
}
return Poll::Pending;
}
NotifiedState::Init => {}
}
match self.notify.backend {
Backend::Engine(cvid) => {
let (handle, adv) = system::register_notify_async(cvid, cx.waker().clone());
match handle {
None => {
self.state = NotifiedState::Done;
Poll::Ready(())
}
Some(handle) => {
self.state = NotifiedState::Engine(handle);
adv.fire();
Poll::Pending
}
}
}
Backend::Native => {
trace_native_from_ambient("notify", "notified");
let mut real = self.notify.real.lock();
if real.permit {
real.permit = false;
drop(real);
self.state = NotifiedState::Done;
return Poll::Ready(());
}
let granted = Arc::new(AtomicBool::new(false));
real.waiters
.push_back((Arc::clone(&granted), cx.waker().clone()));
drop(real);
self.state = NotifiedState::Real(granted);
Poll::Pending
}
}
}
}
impl Drop for Notified<'_> {
fn drop(&mut self) {
match std::mem::replace(&mut self.state, NotifiedState::Done) {
NotifiedState::Engine(handle) => system::cancel_async_wait(&handle),
NotifiedState::Real(granted) => {
let mut real = self.notify.real.lock();
let before = real.waiters.len();
real.waiters.retain(|(g, _)| !Arc::ptr_eq(g, &granted));
let still_queued = real.waiters.len() != before;
if !still_queued && granted.load(Ordering::Acquire) {
if let Some((g, w)) = real.waiters.pop_front() {
g.store(true, Ordering::Release);
drop(real);
w.wake();
} else {
real.permit = true;
}
}
}
NotifiedState::Init | NotifiedState::Done => {}
}
}
}