use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use std::sync::{Arc, Mutex};
use std::task::Waker;
use std::thread::JoinHandle;
use crate::lock_unpoisoned;
#[derive(Clone, Copy)]
pub(crate) struct Interest {
pub(crate) direction: usize,
pub(crate) writer: bool,
}
impl Interest {
fn index(self) -> usize {
self.direction * 2 + usize::from(self.writer)
}
}
#[derive(Default)]
struct SlotWakers {
wakers: [Option<Waker>; 4],
}
pub(crate) struct Registry {
slots: Box<[Mutex<SlotWakers>]>,
offer: Mutex<Option<Waker>>,
}
impl Registry {
pub(crate) fn new(total_slots: usize) -> Self {
Self {
slots: (0..total_slots)
.map(|_| Mutex::new(SlotWakers::default()))
.collect(),
offer: Mutex::new(None),
}
}
pub(crate) fn register(&self, slot: usize, interest: Interest, waker: &Waker) {
let mut entry = lock_unpoisoned(&self.slots[slot]);
let place = &mut entry.wakers[interest.index()];
match place {
Some(existing) if existing.will_wake(waker) => {}
_ => *place = Some(waker.clone()),
}
}
pub(crate) fn register_offer(&self, waker: &Waker) {
let mut place = lock_unpoisoned(&self.offer);
match &*place {
Some(existing) if existing.will_wake(waker) => {}
_ => *place = Some(waker.clone()),
}
}
pub(crate) fn wake(&self, slot: usize) {
let taken = {
let mut entry = lock_unpoisoned(&self.slots[slot]);
std::mem::take(&mut entry.wakers)
};
for waker in taken.into_iter().flatten() {
waker.wake();
}
}
pub(crate) fn wake_offer(&self) {
if let Some(waker) = lock_unpoisoned(&self.offer).take() {
waker.wake();
}
}
pub(crate) fn clear(&self) {
for slot in &self.slots {
lock_unpoisoned(slot).wakers = Default::default();
}
lock_unpoisoned(&self.offer).take();
}
}
pub(crate) trait Doorstep: Send + Sync + 'static {
fn generation(&self) -> &AtomicU32;
fn listening(&self, delta: i32);
fn drain(&self);
}
pub(crate) struct Driver {
stop: Arc<AtomicBool>,
thread: Option<JoinHandle<()>>,
pid: u32,
}
impl Driver {
pub(crate) fn start<T: Doorstep>(target: *const T, name: String) -> std::io::Result<Self> {
let stop = Arc::new(AtomicBool::new(false));
let thread_stop = Arc::clone(&stop);
let target = SendPtr(target);
let thread = std::thread::Builder::new().name(name).spawn(move || {
let target = target;
let table = unsafe { &*target.0 };
run(table, &thread_stop);
})?;
Ok(Self {
stop,
thread: Some(thread),
pid: std::process::id(),
})
}
pub(crate) fn stop(&mut self, generation: &AtomicU32) {
self.stop.store(true, Ordering::Release);
generation.fetch_add(1, Ordering::SeqCst);
crate::wake_on(generation);
if let Some(thread) = self.thread.take()
&& self.pid == std::process::id()
{
let _ = thread.join();
}
}
}
struct SendPtr<T>(*const T);
unsafe impl<T: Sync> Send for SendPtr<T> {}
fn run<T: Doorstep>(table: &T, stop: &AtomicBool) {
let generation = table.generation();
table.listening(1);
let mut seen = generation.load(Ordering::SeqCst);
while !stop.load(Ordering::Acquire) {
table.drain();
let now = generation.load(Ordering::SeqCst);
if now != seen {
seen = now;
continue;
}
if crate::wait_on(generation, seen).is_err() {
break;
}
}
table.listening(-1);
}