Skip to main content

samp_sdk/omp/
dispatch.rs

1//! `IEventDispatcher<H>`, for any handler type `H`, and the macro that writes
2//! a handler.
3//!
4//! Every component hands out its events through the same template: a
5//! dispatcher whose first slots add, remove and look up a handler. It declares
6//! no destructor and no overload, so the slots are the declaration order on
7//! both ABIs, and one generic wrapper serves every event group. The accessors
8//! that return a dispatcher are generated (`objects_event_dispatcher`,
9//! `npcs_event_dispatcher`, ...), and so are the handlers of the groups the SDK
10//! does not write by hand.
11
12use std::marker::PhantomData;
13
14use super::vtable::call_vtable;
15
16/// `IEventDispatcher<H>*` — held by pointer only.
17#[repr(C)]
18pub struct EventDispatcher<H> {
19    _opaque: [u8; 0],
20    _handler: PhantomData<*mut H>,
21}
22
23/// Where a handler runs relative to the others: `EventPriority` in `events.hpp`.
24pub mod priority {
25    /// Before every default-priority handler.
26    pub const HIGHEST: i8 = -128;
27    /// Before the default.
28    pub const FAIRLY_HIGH: i8 = -64;
29    /// What the header's default argument gives.
30    pub const DEFAULT: i8 = 0;
31    /// After the default.
32    pub const FAIRLY_LOW: i8 = 63;
33    /// After every default-priority handler.
34    pub const LOWEST: i8 = 127;
35}
36
37const SLOT_ADD: usize = 0;
38const SLOT_REMOVE: usize = 1;
39const SLOT_COUNT: usize = 3;
40
41/// `addEventHandler(handler, priority)`. `false` means the handler was
42/// already registered, or the dispatcher is null.
43///
44/// # Safety
45/// `dispatcher` must come from an accessor for the same `H`, and `handler`
46/// must stay alive until it is removed — the server keeps the pointer.
47pub unsafe fn add_event_handler<H>(
48    dispatcher: *mut EventDispatcher<H>,
49    handler: *mut H,
50    priority: i8,
51) -> bool {
52    call_vtable!(
53        dispatcher.cast::<u8>(),
54        0,
55        SLOT_ADD,
56        // Any `H*` is one pointer; the type lives in the signature above.
57        (*mut u8, i8) -> bool,
58        (handler.cast::<u8>(), priority),
59        false
60    )
61}
62
63/// `removeEventHandler(handler)`. `false` means it was not registered.
64///
65/// # Safety
66/// As for [`add_event_handler`].
67pub unsafe fn remove_event_handler<H>(
68    dispatcher: *mut EventDispatcher<H>,
69    handler: *mut H,
70) -> bool {
71    call_vtable!(
72        dispatcher.cast::<u8>(),
73        0,
74        SLOT_REMOVE,
75        (*mut u8) -> bool,
76        (handler.cast::<u8>()),
77        false
78    )
79}
80
81/// `count()` — how many handlers are registered.
82///
83/// # Safety
84/// `dispatcher` must come from an accessor, or be null.
85#[must_use]
86pub unsafe fn event_handler_count<H>(dispatcher: *mut EventDispatcher<H>) -> usize {
87    call_vtable!(dispatcher.cast::<u8>(), 0, SLOT_COUNT, () -> usize, (), 0)
88}
89
90/// Writes a handler: its vtable for both ABIs, the object the server calls
91/// through, and `DEFAULT`, a vtable whose every entry does what the C++
92/// handler's own body does — nothing, or return its literal. Override the
93/// entries you need with struct update syntax:
94///
95/// ```ignore
96/// static VTABLE: ObjectHandlerVTable = ObjectHandlerVTable {
97///     on_moved: my_on_moved,
98///     ..ObjectHandlerVTable::DEFAULT
99/// };
100/// ```
101macro_rules! event_handler {
102    // Every entry with the C++ body's default: a `DEFAULT` vtable too.
103    (
104        $(#[$meta:meta])*
105        $name:ident for $handler:ident {
106            $(
107                $(#[$fmeta:meta])*
108                $field:ident: fn($($arg:ty),* $(,)?) $(-> $ret:ty)? = $default:expr
109            ),* $(,)?
110        }
111    ) => {
112        $crate::omp::dispatch::event_handler!(@types
113            $(#[$meta])* $name for $handler {
114                $($(#[$fmeta])* $field: fn($($arg),*) $(-> $ret)?),*
115            }
116        );
117
118        impl $name {
119            /// Every entry doing what the C++ handler's own body does.
120            pub const DEFAULT: Self = Self {
121                $($field: {
122                    #[cfg(not(target_env = "msvc"))]
123                    unsafe extern "C" fn f(_: *mut $handler, $(_: $arg),*) $(-> $ret)? { $default }
124                    #[cfg(target_env = "msvc")]
125                    unsafe extern "thiscall" fn f(_: *mut $handler, $(_: $arg),*) $(-> $ret)? { $default }
126                    f
127                }),*
128            };
129        }
130    };
131    // An interface of pure methods — a callback the plugin implements whole.
132    (
133        $(#[$meta:meta])*
134        $name:ident for $handler:ident {
135            $(
136                $(#[$fmeta:meta])*
137                $field:ident: fn($($arg:ty),* $(,)?) $(-> $ret:ty)?
138            ),* $(,)?
139        }
140    ) => {
141        $crate::omp::dispatch::event_handler!(@types
142            $(#[$meta])* $name for $handler {
143                $($(#[$fmeta])* $field: fn($($arg),*) $(-> $ret)?),*
144            }
145        );
146    };
147    (@types
148        $(#[$meta:meta])*
149        $name:ident for $handler:ident {
150            $(
151                $(#[$fmeta:meta])*
152                $field:ident: fn($($arg:ty),*) $(-> $ret:ty)?
153            ),*
154        }
155    ) => {
156        $(#[$meta])*
157        #[cfg(not(target_env = "msvc"))]
158        #[repr(C)]
159        #[derive(Clone, Copy)]
160        pub struct $name {
161            $($(#[$fmeta])* pub $field: unsafe extern "C" fn(*mut $handler, $($arg),*) $(-> $ret)?),*
162        }
163
164        $(#[$meta])*
165        #[cfg(target_env = "msvc")]
166        #[repr(C)]
167        #[derive(Clone, Copy)]
168        pub struct $name {
169            $($(#[$fmeta])* pub $field: unsafe extern "thiscall" fn(*mut $handler, $($arg),*) $(-> $ret)?),*
170        }
171
172        /// Object the server calls through [`
173        #[doc = stringify!($name)]
174        /// `]: the vtable pointer at offset 0, like any C++ object with virtuals.
175        /// The server keeps the pointer, so it must outlive the registration.
176        #[repr(C)]
177        pub struct $handler {
178            vtable: *const $name,
179        }
180
181        // SAFETY: handlers are only ever touched on the server's main thread.
182        unsafe impl Send for $handler {}
183        unsafe impl Sync for $handler {}
184
185        impl $handler {
186            /// Builds a handler backed by `vtable`.
187            #[must_use]
188            pub const fn new(vtable: *const $name) -> Self {
189                Self { vtable }
190            }
191        }
192    };
193}
194
195pub(crate) use event_handler;
196
197#[cfg(test)]
198mod tests {
199    use super::*;
200    use crate::omp::vtable::MockTable;
201
202    struct Probe;
203
204    #[cfg(not(target_env = "msvc"))]
205    unsafe extern "C" fn add(_: *mut u8, handler: *mut Probe, priority: i8) -> u32 {
206        // A C++ `bool` comes back in a whole register.
207        u32::from(!handler.is_null() && priority == priority::FAIRLY_LOW)
208    }
209    #[cfg(target_env = "msvc")]
210    unsafe extern "thiscall" fn add(_: *mut u8, handler: *mut Probe, priority: i8) -> u32 {
211        // A C++ `bool` comes back in a whole register.
212        u32::from(!handler.is_null() && priority == priority::FAIRLY_LOW)
213    }
214    #[cfg(not(target_env = "msvc"))]
215    unsafe extern "C" fn count(_: *mut u8) -> usize {
216        7
217    }
218    #[cfg(target_env = "msvc")]
219    unsafe extern "thiscall" fn count(_: *mut u8) -> usize {
220        7
221    }
222
223    static TABLE: MockTable<4> = MockTable([
224        add as *const (),
225        std::ptr::null(),
226        std::ptr::null(),
227        count as *const (),
228    ]);
229
230    #[test]
231    fn calls_reach_their_slots() {
232        let mut object = [&raw const TABLE.0 as *const ()];
233        let dispatcher = (&raw mut object).cast::<EventDispatcher<Probe>>();
234        let mut probe = Probe;
235        unsafe {
236            assert!(add_event_handler(
237                dispatcher,
238                &raw mut probe,
239                priority::FAIRLY_LOW
240            ));
241            assert_eq!(event_handler_count(dispatcher), 7);
242            // A null slot is no call at all.
243            assert!(!remove_event_handler(dispatcher, &raw mut probe));
244        }
245    }
246
247    #[test]
248    fn a_null_dispatcher_answers_the_defaults() {
249        let dispatcher = std::ptr::null_mut::<EventDispatcher<Probe>>();
250        unsafe {
251            assert!(!add_event_handler(dispatcher, std::ptr::null_mut(), 0));
252            assert_eq!(event_handler_count(dispatcher), 0);
253        }
254    }
255}