use std::marker::PhantomData;
use super::vtable::call_vtable;
#[repr(C)]
pub struct EventDispatcher<H> {
_opaque: [u8; 0],
_handler: PhantomData<*mut H>,
}
pub mod priority {
pub const HIGHEST: i8 = -128;
pub const FAIRLY_HIGH: i8 = -64;
pub const DEFAULT: i8 = 0;
pub const FAIRLY_LOW: i8 = 63;
pub const LOWEST: i8 = 127;
}
const SLOT_ADD: usize = 0;
const SLOT_REMOVE: usize = 1;
const SLOT_COUNT: usize = 3;
pub unsafe fn add_event_handler<H>(
dispatcher: *mut EventDispatcher<H>,
handler: *mut H,
priority: i8,
) -> bool {
call_vtable!(
dispatcher.cast::<u8>(),
0,
SLOT_ADD,
(*mut u8, i8) -> bool,
(handler.cast::<u8>(), priority),
false
)
}
pub unsafe fn remove_event_handler<H>(
dispatcher: *mut EventDispatcher<H>,
handler: *mut H,
) -> bool {
call_vtable!(
dispatcher.cast::<u8>(),
0,
SLOT_REMOVE,
(*mut u8) -> bool,
(handler.cast::<u8>()),
false
)
}
#[must_use]
pub unsafe fn event_handler_count<H>(dispatcher: *mut EventDispatcher<H>) -> usize {
call_vtable!(dispatcher.cast::<u8>(), 0, SLOT_COUNT, () -> usize, (), 0)
}
macro_rules! event_handler {
(
$(#[$meta:meta])*
$name:ident for $handler:ident {
$(
$(#[$fmeta:meta])*
$field:ident: fn($($arg:ty),* $(,)?) $(-> $ret:ty)? = $default:expr
),* $(,)?
}
) => {
$crate::omp::dispatch::event_handler!(@types
$(#[$meta])* $name for $handler {
$($(#[$fmeta])* $field: fn($($arg),*) $(-> $ret)?),*
}
);
impl $name {
pub const DEFAULT: Self = Self {
$($field: {
#[cfg(not(target_env = "msvc"))]
unsafe extern "C" fn f(_: *mut $handler, $(_: $arg),*) $(-> $ret)? { $default }
#[cfg(target_env = "msvc")]
unsafe extern "thiscall" fn f(_: *mut $handler, $(_: $arg),*) $(-> $ret)? { $default }
f
}),*
};
}
};
(
$(#[$meta:meta])*
$name:ident for $handler:ident {
$(
$(#[$fmeta:meta])*
$field:ident: fn($($arg:ty),* $(,)?) $(-> $ret:ty)?
),* $(,)?
}
) => {
$crate::omp::dispatch::event_handler!(@types
$(#[$meta])* $name for $handler {
$($(#[$fmeta])* $field: fn($($arg),*) $(-> $ret)?),*
}
);
};
(@types
$(#[$meta:meta])*
$name:ident for $handler:ident {
$(
$(#[$fmeta:meta])*
$field:ident: fn($($arg:ty),*) $(-> $ret:ty)?
),*
}
) => {
$(#[$meta])*
#[cfg(not(target_env = "msvc"))]
#[repr(C)]
#[derive(Clone, Copy)]
pub struct $name {
$($(#[$fmeta])* pub $field: unsafe extern "C" fn(*mut $handler, $($arg),*) $(-> $ret)?),*
}
$(#[$meta])*
#[cfg(target_env = "msvc")]
#[repr(C)]
#[derive(Clone, Copy)]
pub struct $name {
$($(#[$fmeta])* pub $field: unsafe extern "thiscall" fn(*mut $handler, $($arg),*) $(-> $ret)?),*
}
#[doc = stringify!($name)]
#[repr(C)]
pub struct $handler {
vtable: *const $name,
}
unsafe impl Send for $handler {}
unsafe impl Sync for $handler {}
impl $handler {
#[must_use]
pub const fn new(vtable: *const $name) -> Self {
Self { vtable }
}
}
};
}
pub(crate) use event_handler;
#[cfg(test)]
mod tests {
use super::*;
use crate::omp::vtable::MockTable;
struct Probe;
#[cfg(not(target_env = "msvc"))]
unsafe extern "C" fn add(_: *mut u8, handler: *mut Probe, priority: i8) -> u32 {
u32::from(!handler.is_null() && priority == priority::FAIRLY_LOW)
}
#[cfg(target_env = "msvc")]
unsafe extern "thiscall" fn add(_: *mut u8, handler: *mut Probe, priority: i8) -> u32 {
u32::from(!handler.is_null() && priority == priority::FAIRLY_LOW)
}
#[cfg(not(target_env = "msvc"))]
unsafe extern "C" fn count(_: *mut u8) -> usize {
7
}
#[cfg(target_env = "msvc")]
unsafe extern "thiscall" fn count(_: *mut u8) -> usize {
7
}
static TABLE: MockTable<4> = MockTable([
add as *const (),
std::ptr::null(),
std::ptr::null(),
count as *const (),
]);
#[test]
fn calls_reach_their_slots() {
let mut object = [&raw const TABLE.0 as *const ()];
let dispatcher = (&raw mut object).cast::<EventDispatcher<Probe>>();
let mut probe = Probe;
unsafe {
assert!(add_event_handler(
dispatcher,
&raw mut probe,
priority::FAIRLY_LOW
));
assert_eq!(event_handler_count(dispatcher), 7);
assert!(!remove_event_handler(dispatcher, &raw mut probe));
}
}
#[test]
fn a_null_dispatcher_answers_the_defaults() {
let dispatcher = std::ptr::null_mut::<EventDispatcher<Probe>>();
unsafe {
assert!(!add_event_handler(dispatcher, std::ptr::null_mut(), 0));
assert_eq!(event_handler_count(dispatcher), 0);
}
}
}