use std::hint::spin_loop;
use std::sync::atomic::{AtomicU64, Ordering, fence};
use std::thread::{self};
use wait_on_address::AtomicWait;
#[derive(Debug, Default)]
pub struct Event {
atomic: AtomicU64,
}
impl Event {
const WAITER_FLAG: u64 = 1 << 63;
pub const fn new() -> Self {
Self {
atomic: AtomicU64::new(0),
}
}
pub fn notify(&self) {
let atomic = self.atomic.fetch_add(1, Ordering::Release);
if (atomic & Self::WAITER_FLAG) != 0 {
self.atomic
.fetch_and(!Event::WAITER_FLAG, Ordering::Relaxed);
self.atomic.notify_all();
}
}
pub fn listen(&self) -> EventListener<'_> {
EventListener {
event: self,
version: self.atomic.load(Ordering::Acquire) & !Self::WAITER_FLAG,
}
}
}
#[derive(Copy, Clone, Debug)]
pub struct EventListener<'a> {
event: &'a Event,
version: u64,
}
impl<'a> EventListener<'a> {
pub fn spin_wait(&mut self, cycles: usize) -> bool {
let mut i = 0;
while i < cycles {
if self.read_new_version(self.event.atomic.load(Ordering::Relaxed)) {
return false;
}
spin_loop();
i += 1;
}
self.wait();
true
}
pub fn wait(&mut self) {
let (Ok(atomic) | Err(atomic)) = self.event.atomic.compare_exchange(
self.version,
self.version | Event::WAITER_FLAG,
Ordering::Relaxed,
Ordering::Relaxed,
);
if !self.read_new_version(atomic) {
loop {
self.event.atomic.wait(self.version | Event::WAITER_FLAG);
if self.read_new_version(self.event.atomic.load(Ordering::Relaxed)) {
break;
}
}
}
}
fn read_new_version(&mut self, atomic: u64) -> bool {
let new_version = atomic & !Event::WAITER_FLAG;
if self.version != new_version {
fence(Ordering::Acquire);
self.version = new_version;
true
} else {
false
}
}
}
pub fn abort_on_panic<R, F: FnOnce() -> R>(f: F) -> R {
struct PanicGuard;
impl Drop for PanicGuard {
fn drop(&mut self) {
if thread::panicking() {
std::process::abort();
}
}
}
let _guard = PanicGuard;
f()
}