use std::{
sync::atomic::{
AtomicUsize,
Ordering,
},
alloc::{
Layout,
alloc,
dealloc,
handle_alloc_error,
},
fmt::{self, Formatter, Debug},
};
pub struct CallbackCell(AtomicUsize);
impl CallbackCell {
pub fn new() -> Self {
CallbackCell(AtomicUsize::new(0))
}
pub fn put<F: FnOnce() + Send + 'static>(&self, f: F) {
unsafe {
let (layout, callback_offset) = Layout::new::<unsafe fn(bool, *mut u8)>()
.extend(Layout::new::<F>()).unwrap();
let ptr = alloc(layout);
if ptr.is_null() {
handle_alloc_error(layout);
}
(ptr as *mut unsafe fn(bool, *mut u8)).write(fn_ptr_impl::<F>);
(ptr.add(callback_offset) as *mut F).write(f);
let old_ptr = self.0.swap(ptr as usize, Ordering::Release);
drop_raw(old_ptr as *mut u8);
}
}
pub fn take_call(&self) -> bool {
unsafe {
let ptr = self.0.swap(0, Ordering::Acquire) as *mut u8;
if !ptr.is_null() {
let fn_ptr = (ptr as *mut unsafe fn(bool, *mut u8)).read();
fn_ptr(true, ptr);
true
} else {
false
}
}
}
}
impl Drop for CallbackCell {
fn drop(&mut self) {
unsafe {
drop_raw(*self.0.get_mut() as *mut u8);
}
}
}
unsafe fn fn_ptr_impl<F: FnOnce() + Send + 'static>(run: bool, ptr: *mut u8) {
let (layout, callback_offset) = Layout::new::<unsafe fn(bool, *mut u8)>()
.extend(Layout::new::<F>()).unwrap();
let f = (ptr.add(callback_offset) as *mut F).read();
dealloc(ptr, layout);
if run {
f();
}
}
unsafe fn drop_raw(ptr: *mut u8) {
if !ptr.is_null() {
let fn_ptr = (ptr as *mut unsafe fn(bool, *mut u8)).read();
fn_ptr(false, ptr);
}
}
impl Default for CallbackCell {
fn default() -> Self {
Self::new()
}
}
impl Debug for CallbackCell {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
if (self.0.load(Ordering::Relaxed) as *const ()).is_null() {
f.write_str("CallbackCell(NULL)")
} else {
f.write_str("CallbackCell(NOT NULL)")
}
}
}