use std::{
hint::spin_loop,
sync::{Condvar, Mutex, MutexGuard},
time::Duration,
};
const TABLE_SIZE: usize = 256;
static TABLE: [TableEntry; TABLE_SIZE] = [TableEntry::DEFAULT; TABLE_SIZE];
pub fn wait(ptr: *const (), condition: impl FnOnce() -> bool, timeout: Option<Duration>) {
let entry = &TABLE[entry_for_ptr(ptr) as usize];
let mut guard = spin_lock(&entry.mutex);
if condition() {
if guard.waiting_count == 0 {
guard.address = ptr;
} else if guard.address != ptr {
guard.address = std::ptr::null();
}
guard.waiting_count += 1;
guard = if let Some(time) = timeout {
entry
.condvar
.wait_timeout(guard, time)
.expect("Failed to lock mutex")
.0
} else {
entry.condvar.wait(guard).expect("Failed to lock mutex")
};
guard.waiting_count -= 1;
}
}
pub fn notify_all(ptr: *const ()) {
if !ptr.is_null() {
let entry = &TABLE[entry_for_ptr(ptr) as usize];
let metadata = *spin_lock(&entry.mutex);
if 0 < metadata.waiting_count {
entry.condvar.notify_all();
}
}
}
pub fn notify_one(ptr: *const ()) {
if !ptr.is_null() {
let entry = &TABLE[entry_for_ptr(ptr) as usize];
let metadata = *spin_lock(&entry.mutex);
if 0 < metadata.waiting_count {
if metadata.address.is_null() {
entry.condvar.notify_all();
} else if metadata.address == ptr {
entry.condvar.notify_one();
}
}
}
}
fn spin_lock<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
loop {
if let Ok(x) = mutex.try_lock() {
return x;
}
spin_loop();
}
}
fn entry_for_ptr(ptr: *const ()) -> u8 {
let x_64 = ptr as u64;
let x_32 = (x_64 >> 32) as u32 ^ x_64 as u32;
let x_16 = (x_32 >> 16) as u16 ^ x_32 as u16;
(x_16 >> 8) as u8 ^ x_16 as u8
}
#[derive(Copy, Clone)]
struct WaitMetadata {
pub address: *const (),
pub waiting_count: usize,
}
impl WaitMetadata {
pub const DEFAULT: Self = Self {
address: std::ptr::null(),
waiting_count: 0,
};
}
unsafe impl Send for WaitMetadata {}
unsafe impl Sync for WaitMetadata {}
struct TableEntry {
pub condvar: Condvar,
pub mutex: Mutex<WaitMetadata>,
}
impl TableEntry {
#[allow(clippy::declare_interior_mutable_const)]
pub const DEFAULT: Self = Self {
condvar: Condvar::new(),
mutex: Mutex::new(WaitMetadata::DEFAULT),
};
}