Skip to main content

samp_sdk/omp/
events.rs

1//! Pawn script events via `IEventDispatcher<PawnEventHandler>`.
2//!
3//! The Open Multiplayer server invokes `onAmxLoad`/`onAmxUnload` via vtable when scripts
4//! are loaded/unloaded. In native component mode, these events replace SA-MP's
5//! `AmxLoad`/`AmxUnload` — the SDK registers an internal handler on the
6//! `IEventDispatcher` in `omp_on_init` and routes to the matching methods of
7//! the `SampPlugin` trait.
8//!
9//! ## `PawnEventHandler` vtable
10//!
11//! No virtual destructor in the header, so only 2 slots — identical on Itanium
12//! and MSVC (only the calling convention differs):
13//!
14//! ```text
15//! [0] onAmxLoad(IPawnScript&)
16//! [1] onAmxUnload(IPawnScript&)
17//! ```
18
19use super::server::IPawnScript;
20
21// ---------------------------------------------------------------------------
22// PawnEventHandler vtable and object (WE implement — the server calls)
23// ---------------------------------------------------------------------------
24
25/// `PawnEventHandler` vtable for the Itanium ABI (Linux).
26///
27/// `PawnEventHandler` does not declare a virtual destructor — no index shift
28/// between ABIs. The only difference is the calling convention.
29#[cfg(not(target_env = "msvc"))]
30#[repr(C)]
31pub struct PawnEventHandlerVTable {
32    pub on_amx_load: unsafe extern "C" fn(*mut PawnEventHandler, *mut IPawnScript),
33    pub on_amx_unload: unsafe extern "C" fn(*mut PawnEventHandler, *mut IPawnScript),
34}
35
36/// `PawnEventHandler` vtable for the MSVC ABI (Windows).
37#[cfg(target_env = "msvc")]
38#[repr(C)]
39pub struct PawnEventHandlerVTable {
40    pub on_amx_load: unsafe extern "thiscall" fn(*mut PawnEventHandler, *mut IPawnScript),
41    pub on_amx_unload: unsafe extern "thiscall" fn(*mut PawnEventHandler, *mut IPawnScript),
42}
43
44/// Rust object compatible with Open Multiplayer's `PawnEventHandler*`.
45///
46/// Created by `samp` in `omp_on_init` and registered on the `IPawnComponent`
47/// dispatcher. The server calls the vtable methods when Pawn scripts are
48/// loaded or unloaded.
49#[repr(C)]
50pub struct PawnEventHandler {
51    vtable: *const PawnEventHandlerVTable,
52}
53
54// SAFETY: handler is only accessed on the server's main thread.
55unsafe impl Send for PawnEventHandler {}
56unsafe impl Sync for PawnEventHandler {}
57
58impl PawnEventHandler {
59    /// Creates a new handler with the supplied vtable.
60    #[must_use]
61    pub fn new(vtable: *const PawnEventHandlerVTable) -> Self {
62        Self { vtable }
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    #[cfg(not(target_env = "msvc"))]
71    unsafe extern "C" fn noop_amx_load(_: *mut PawnEventHandler, _: *mut IPawnScript) {}
72    #[cfg(not(target_env = "msvc"))]
73    unsafe extern "C" fn noop_amx_unload(_: *mut PawnEventHandler, _: *mut IPawnScript) {}
74
75    #[cfg(target_env = "msvc")]
76    unsafe extern "thiscall" fn noop_amx_load(_: *mut PawnEventHandler, _: *mut IPawnScript) {}
77    #[cfg(target_env = "msvc")]
78    unsafe extern "thiscall" fn noop_amx_unload(_: *mut PawnEventHandler, _: *mut IPawnScript) {}
79
80    static TEST_VTABLE: PawnEventHandlerVTable = PawnEventHandlerVTable {
81        on_amx_load: noop_amx_load,
82        on_amx_unload: noop_amx_unload,
83    };
84
85    #[test]
86    fn pawn_event_handler_new_stores_vtable() {
87        let handler = PawnEventHandler::new(&raw const TEST_VTABLE);
88        assert_eq!(handler.vtable, &raw const TEST_VTABLE);
89    }
90
91    #[test]
92    fn pawn_event_handler_is_send_and_sync() {
93        fn assert_send_sync<T: Send + Sync>() {}
94        assert_send_sync::<PawnEventHandler>();
95    }
96}