use crate::lockfree::AtomicWakerSlot;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicUsize, Ordering};
pub const MAX_LISTENERS: usize = 16;
#[repr(align(64))]
pub struct ListenerState {
pending: AtomicUsize,
waker: AtomicWakerSlot,
dead: AtomicBool,
}
impl ListenerState {
const fn new() -> Self {
Self {
pending: AtomicUsize::new(0),
waker: AtomicWakerSlot::new(),
dead: AtomicBool::new(false),
}
}
#[inline]
pub fn deliver(&self) {
if self.dead.load(Ordering::Acquire) {
return;
}
self.pending.fetch_add(1, Ordering::AcqRel);
self.waker.take_and_wake();
}
pub fn poll_recv(&self, cx: &std::task::Context<'_>) -> std::task::Poll<()> {
loop {
let pending = self.pending.load(Ordering::Acquire);
if pending > 0
&& self
.pending
.compare_exchange_weak(
pending,
pending - 1,
Ordering::AcqRel,
Ordering::Acquire,
)
.is_ok()
{
return std::task::Poll::Ready(());
}
if pending > 0 {
continue; }
self.waker.register(cx.waker());
if self.pending.load(Ordering::Acquire) > 0 {
continue; }
return std::task::Poll::Pending;
}
}
fn mark_dead(&self) {
self.dead.store(true, Ordering::Release);
}
}
#[repr(align(64))]
pub struct ListenerRegistry {
slots: [AtomicPtr<ListenerState>; MAX_LISTENERS],
len: AtomicUsize,
}
impl ListenerRegistry {
#[must_use]
pub fn new() -> Self {
Self {
slots: std::array::from_fn(|_| AtomicPtr::new(std::ptr::null_mut())),
len: AtomicUsize::new(0),
}
}
pub fn register(&self) -> Arc<ListenerState> {
let state = Arc::new(ListenerState::new());
let idx = self.len.fetch_add(1, Ordering::AcqRel);
assert!(
idx < MAX_LISTENERS,
"dtact-signal: too many concurrent listeners for one signal kind (max {MAX_LISTENERS})"
);
let ptr = Arc::into_raw(Arc::clone(&state)).cast_mut();
self.slots[idx].store(ptr, Ordering::Release);
state
}
pub fn broadcast(&self) {
let len = self.len.load(Ordering::Acquire);
for slot in &self.slots[..len] {
let ptr = slot.load(Ordering::Acquire);
if ptr.is_null() {
continue; }
unsafe { &*ptr }.deliver();
}
}
}
pub struct DeadOnDrop(pub Arc<ListenerState>);
impl Drop for DeadOnDrop {
fn drop(&mut self) {
self.0.mark_dead();
}
}