mod id;
use crate::abi::{CppDestructors, WChar};
use crate::ffi::{NotThreadSafe, borrow_cstr, copy_cstr, vcall};
use crate::players::UserId;
use std::collections::{HashMap, HashSet};
use std::ffi::{CStr, CString, c_char, c_float, c_int, c_void};
use std::fmt::{Display, Formatter};
use std::marker::{PhantomData, PhantomPinned};
use std::mem::{ManuallyDrop, offset_of, size_of};
use std::ops::{ControlFlow, Deref};
use std::pin::Pin;
use std::ptr::NonNull;
use std::slice::from_raw_parts;
pub use id::GameEventId;
pub const GAME_EVENT_MANAGER_INTERFACE_VERSION: &CStr = GameEventManager::<'static>::VERSION;
#[doc(alias = "IGameEvent")]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct GameEvent<'e> {
raw: NonNull<sys::IGameEvent>,
_lifetime: PhantomData<&'e ()>,
_not_thread_safe: NotThreadSafe,
}
impl<'e> GameEvent<'e> {
pub(crate) const unsafe fn from_raw(raw: NonNull<sys::IGameEvent>) -> Self {
Self {
raw,
_lifetime: PhantomData,
_not_thread_safe: PhantomData,
}
}
pub const fn as_ptr(self) -> *mut sys::IGameEvent {
self.raw.as_ptr()
}
pub fn find(self, key: impl AsRef<str>) -> Option<GameEventDataValue> {
let mut finder = VisitKeyFinder::new(key.as_ref());
unsafe { self.visitor_exec((&raw mut finder).cast::<sys::IGameEventVisitor2>()) };
finder.state.break_value()
}
pub fn first_key(self) -> Option<GameEventDataKey> {
struct VisitFirstKey;
impl GameEventVisitor for VisitFirstKey {
type Break = GameEventDataKey;
fn visit(
&mut self,
key: GameEventDataKey,
_value: GameEventDataValue,
) -> ControlFlow<Self::Break> {
ControlFlow::Break(key)
}
}
self.visit(VisitFirstKey).break_value()
}
pub fn first_pair(self) -> Option<GameEventDataPair> {
struct VisitFirstPair;
impl GameEventVisitor for VisitFirstPair {
type Break = GameEventDataPair;
fn visit(
&mut self,
key: GameEventDataKey,
value: GameEventDataValue,
) -> ControlFlow<Self::Break> {
ControlFlow::Break(GameEventDataPair(key, value))
}
}
self.visit(VisitFirstPair).break_value()
}
pub fn first_value(self) -> Option<GameEventDataValue> {
struct VisitFirstValue;
impl GameEventVisitor for VisitFirstValue {
type Break = GameEventDataValue;
fn visit(
&mut self,
_key: GameEventDataKey,
value: GameEventDataValue,
) -> ControlFlow<Self::Break> {
ControlFlow::Break(value)
}
}
self.visit(VisitFirstValue).break_value()
}
#[doc(alias = "GetBool")]
pub fn get_bool(self, key: &CStr) -> Option<bool> {
self.has_key(key)
.then(|| unsafe { vcall!(self.as_ptr() => IGameEvent_GetBool(key.as_ptr(), false)) })
}
#[doc(alias = "GetFloat")]
pub fn get_float(self, key: &CStr) -> Option<f32> {
self.has_key(key)
.then(|| unsafe { vcall!(self.as_ptr() => IGameEvent_GetFloat(key.as_ptr(), 0.0)) })
}
#[doc(alias = "GetInt")]
pub fn get_int(self, key: &CStr) -> Option<c_int> {
self.has_key(key)
.then(|| unsafe { vcall!(self.as_ptr() => IGameEvent_GetInt(key.as_ptr(), 0)) })
}
#[doc(alias = "GetString")]
pub fn get_string(self, key: &CStr) -> Option<CString> {
if !self.has_key(key) {
return None;
}
unsafe {
copy_cstr(vcall!(self.as_ptr() => IGameEvent_GetString(key.as_ptr(), c"".as_ptr())))
}
}
#[doc(alias = "GetUint64")]
pub fn get_uint64(self, key: &CStr) -> Option<u64> {
self.has_key(key)
.then(|| unsafe { vcall!(self.as_ptr() => IGameEvent_GetUint64(key.as_ptr(), 0)) })
}
#[doc(alias = "IsEmpty")]
pub fn has_key(self, key: &CStr) -> bool {
!unsafe { vcall!(self.as_ptr() => IGameEvent_IsEmpty(key.as_ptr())) }
}
pub fn id(self) -> Option<GameEventId> {
GameEventId::from_cstr(self.name())
}
#[doc(alias = "IsLocal")]
pub fn is_local(self) -> bool {
unsafe { vcall!(self.as_ptr() => IGameEvent_IsLocal()) }
}
#[doc(alias = "IsReliable")]
pub fn is_reliable(self) -> bool {
unsafe { vcall!(self.as_ptr() => IGameEvent_IsReliable()) }
}
pub fn keys_to_set(self) -> HashSet<GameEventDataKey> {
#[repr(transparent)]
struct VisitorHashSetKeys(HashSet<GameEventDataKey>);
impl GameEventVisitor for VisitorHashSetKeys {
type Break = !;
fn visit(
&mut self,
key: GameEventDataKey,
_value: GameEventDataValue,
) -> ControlFlow<Self::Break> {
assert!(self.0.insert(key));
ControlFlow::Continue(())
}
}
self.visit_nb(VisitorHashSetKeys(HashSet::new())).0
}
pub fn keys_to_vec(self) -> Vec<GameEventDataKey> {
#[repr(transparent)]
struct VisitorVecKeys(Vec<GameEventDataKey>);
impl GameEventVisitor for VisitorVecKeys {
type Break = !;
fn visit(
&mut self,
key: GameEventDataKey,
_value: GameEventDataValue,
) -> ControlFlow<Self::Break> {
self.0.push(key);
ControlFlow::Continue(())
}
}
self.visit_nb(VisitorVecKeys(Vec::new())).0
}
#[doc(alias = "GetName")]
pub fn name(self) -> &'e CStr {
let name = unsafe { borrow_cstr(vcall!(self.as_ptr() => IGameEvent_GetName())) };
name.expect("IGameEvent::GetName returned null")
}
pub fn pairs_to_vec(self) -> Vec<GameEventDataPair> {
#[repr(transparent)]
struct VisitorVecPairs(Vec<GameEventDataPair>);
impl GameEventVisitor for VisitorVecPairs {
type Break = !;
fn visit(
&mut self,
key: GameEventDataKey,
value: GameEventDataValue,
) -> ControlFlow<Self::Break> {
self.0.push(GameEventDataPair(key, value));
ControlFlow::Continue(())
}
}
self.visit_nb(VisitorVecPairs(Vec::new())).0
}
pub fn pairs_to_set(self) -> HashMap<GameEventDataKey, GameEventDataValue> {
#[repr(transparent)]
struct VisitorHashMapPairs(HashMap<GameEventDataKey, GameEventDataValue>);
impl GameEventVisitor for VisitorHashMapPairs {
type Break = !;
fn visit(
&mut self,
key: GameEventDataKey,
value: GameEventDataValue,
) -> ControlFlow<Self::Break> {
assert!(self.0.insert(key, value).is_none());
ControlFlow::Continue(())
}
}
self.visit_nb(VisitorHashMapPairs(HashMap::new())).0
}
pub fn values_to_vec(self) -> Vec<GameEventDataValue> {
#[repr(transparent)]
struct VisitorVecValues(Vec<GameEventDataValue>);
impl GameEventVisitor for VisitorVecValues {
type Break = !;
fn visit(
&mut self,
_key: GameEventDataKey,
value: GameEventDataValue,
) -> ControlFlow<Self::Break> {
self.0.push(value);
ControlFlow::Continue(())
}
}
self.visit_nb(VisitorVecValues(Vec::new())).0
}
pub fn visit<V: GameEventVisitor>(self, visitor: V) -> ControlFlow<V::Break, V> {
let mut executor = VisitorExecutor::new(visitor);
unsafe { self.visitor_exec((&raw mut executor).cast::<sys::IGameEventVisitor2>()) };
executor.state
}
#[inline(always)]
unsafe fn visitor_exec(self, object: *mut sys::IGameEventVisitor2) -> bool {
unsafe { vcall!(self.as_ptr() => IGameEvent_ForEventData(object)) }
}
#[inline(always)]
fn visit_nb<V: GameEventVisitor<Break = !>>(self, visitor: V) -> V {
let ControlFlow::Continue(visitor) = self.visit(visitor);
visitor
}
}
#[derive(Debug)]
pub struct OwnedGameEvent<'s> {
raw: NonNull<sys::IGameEvent>,
manager: GameEventManager<'s>,
}
impl<'s> OwnedGameEvent<'s> {
pub fn as_event(&self) -> GameEvent<'_> {
unsafe { GameEvent::from_raw(self.raw) }
}
#[doc(alias = "SetBool")]
pub fn set_bool(&mut self, key: &CStr, value: bool) {
unsafe { vcall!(self.raw.as_ptr() => IGameEvent_SetBool(key.as_ptr(), value)) };
}
#[doc(alias = "SetFloat")]
pub fn set_float(&mut self, key: &CStr, value: f32) {
unsafe { vcall!(self.raw.as_ptr() => IGameEvent_SetFloat(key.as_ptr(), value)) };
}
#[doc(alias = "SetInt")]
pub fn set_int(&mut self, key: &CStr, value: c_int) {
unsafe { vcall!(self.raw.as_ptr() => IGameEvent_SetInt(key.as_ptr(), value)) };
}
#[doc(alias = "SetString")]
pub fn set_string(&mut self, key: &CStr, value: &CStr) {
unsafe { vcall!(self.raw.as_ptr() => IGameEvent_SetString(key.as_ptr(), value.as_ptr())) };
}
#[doc(alias = "SetUint64")]
pub fn set_uint64(&mut self, key: &CStr, value: u64) {
unsafe { vcall!(self.raw.as_ptr() => IGameEvent_SetUint64(key.as_ptr(), value)) };
}
#[doc(alias = "FireEvent")]
pub fn fire(self, broadcast: bool) -> Result<(), FireEventError> {
let this = ManuallyDrop::new(self);
let name = this.as_event().name().to_owned();
let fired = unsafe {
vcall!(this.manager.as_ptr() => IGameEventManager2_FireEvent(this.raw.as_ptr(), !broadcast))
};
fired.then_some(()).ok_or(FireEventError { name })
}
}
impl Drop for OwnedGameEvent<'_> {
fn drop(&mut self) {
unsafe { vcall!(self.manager.as_ptr() => IGameEventManager2_FreeEvent(self.raw.as_ptr())) };
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[error("no game event named `{}` is registered", .name.to_string_lossy())]
pub struct CreateEventError {
name: CString,
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[error("the game event manager did not fire `{}`", .name.to_string_lossy())]
pub struct FireEventError {
name: CString,
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[error("could not listen for `{}`; no such game event is registered", .name.to_string_lossy())]
pub struct AddListenerError {
name: CString,
}
interface! {
#[doc(alias = "IGameEventManager2")]
pub struct GameEventManager(sys::IGameEventManager2) = Engine c"GAMEEVENTSMANAGER002";
}
impl<'s> GameEventManager<'s> {
#[doc(alias = "AddListener")]
pub unsafe fn add_listener<H: GameEventHandler>(
self,
listener: Pin<&GameEventListener<H>>,
event: &CStr,
server_side: bool,
) -> Result<(), AddListenerError> {
let added = unsafe {
vcall!(self.as_ptr() => IGameEventManager2_AddListener(listener.as_raw(), event.as_ptr(), server_side))
};
added.then_some(()).ok_or_else(|| AddListenerError {
name: event.to_owned(),
})
}
#[doc(alias = "FindListener")]
pub fn is_listening<H: GameEventHandler>(
self,
listener: Pin<&GameEventListener<H>>,
event: &CStr,
) -> bool {
unsafe {
vcall!(self.as_ptr() => IGameEventManager2_FindListener(listener.as_raw(), event.as_ptr()))
}
}
#[doc(alias = "RemoveListener")]
pub fn remove_listener<H: GameEventHandler>(self, listener: Pin<&GameEventListener<H>>) {
unsafe { vcall!(self.as_ptr() => IGameEventManager2_RemoveListener(listener.as_raw())) };
}
#[doc(alias = "CreateEvent")]
pub fn create_event(self, name: &CStr) -> Result<OwnedGameEvent<'s>, CreateEventError> {
let event = unsafe {
vcall!(self.as_ptr() => IGameEventManager2_CreateEvent(name.as_ptr(), false))
};
NonNull::new(event)
.map(|raw| OwnedGameEvent { raw, manager: self })
.ok_or_else(|| CreateEventError {
name: name.to_owned(),
})
}
#[doc(alias = "DuplicateEvent")]
pub fn duplicate_event(self, event: GameEvent<'_>) -> Option<OwnedGameEvent<'s>> {
let duplicate =
unsafe { vcall!(self.as_ptr() => IGameEventManager2_DuplicateEvent(event.as_ptr())) };
NonNull::new(duplicate).map(|raw| OwnedGameEvent { raw, manager: self })
}
#[doc(alias = "LoadEventsFromFile")]
pub unsafe fn load_events_from_file(self, filename: &CStr) -> c_int {
unsafe { vcall!(self.as_ptr() => IGameEventManager2_LoadEventsFromFile(filename.as_ptr())) }
}
#[doc(alias = "Reset")]
pub unsafe fn reset(self) {
unsafe { vcall!(self.as_ptr() => IGameEventManager2_Reset()) }
}
}
pub trait GameEventHandler {
fn fire_game_event(&self, event: GameEvent<'_>);
}
#[repr(C)]
pub struct GameEventListener<H> {
vtable: &'static GameEventListenerVtable,
handler: H,
_pinned: PhantomPinned,
_not_thread_safe: NotThreadSafe,
}
impl<H: GameEventHandler> GameEventListener<H> {
const VTABLE: GameEventListenerVtable = GameEventListenerVtable {
destructor: CppDestructors::new_noop(),
fire_game_event: Self::fire_game_event,
};
pub const fn new(handler: H) -> Self {
Self {
vtable: &Self::VTABLE,
handler,
_pinned: PhantomPinned,
_not_thread_safe: PhantomData,
}
}
pub const fn handler(&self) -> &H {
&self.handler
}
fn as_raw(self: Pin<&Self>) -> *mut sys::IGameEventListener2 {
ptr_from_pin(self).cast()
}
unsafe extern "C" fn fire_game_event(
this: *mut sys::IGameEventListener2,
event: *mut sys::IGameEvent,
) {
let Some(event) = NonNull::new(event) else {
return;
};
let listener = unsafe { &*this.cast::<Self>() };
listener
.handler
.fire_game_event(unsafe { GameEvent::from_raw(event) });
}
}
impl<H: std::fmt::Debug> std::fmt::Debug for GameEventListener<H> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("GameEventListener")
.field("handler", &self.handler)
.finish_non_exhaustive()
}
}
fn ptr_from_pin<T>(pinned: Pin<&T>) -> *mut T {
(&raw const *pinned.get_ref()).cast_mut()
}
#[repr(C)]
struct GameEventListenerVtable {
destructor: CppDestructors,
fire_game_event:
unsafe extern "C" fn(this: *mut sys::IGameEventListener2, event: *mut sys::IGameEvent),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct GameEventDataKey(pub(super) CString);
impl GameEventDataKey {
pub(super) unsafe fn from_ptr(ptr: *const c_char) -> Self {
Self(unsafe { CStr::from_ptr(ptr) }.to_owned())
}
pub fn as_c_str(&self) -> &CStr {
&self.0
}
pub fn as_str(&self) -> &str {
let Ok(name) = self.0.to_str() else {
unreachable!()
};
name
}
pub fn into_string(self) -> String {
let Ok(string) = self.0.into_string() else {
unreachable!()
};
string
}
}
impl Deref for GameEventDataKey {
type Target = str;
fn deref(&self) -> &Self::Target {
self.as_str()
}
}
impl Display for GameEventDataKey {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, Copy)]
#[repr(transparent)]
pub struct GameEventDataLocal(pub(super) NonNull<c_void>);
impl GameEventDataLocal {
pub const fn as_ptr(self) -> *const c_void {
self.0.as_ptr()
}
}
#[derive(Debug, Clone)]
pub struct GameEventDataPair(pub GameEventDataKey, pub GameEventDataValue);
#[derive(Debug, Clone)]
pub enum GameEventDataValue {
Local(GameEventDataLocal),
String(CString),
Float(f32),
Int(c_int),
UInt64(u64),
WString(Vec<WChar>),
Bool(bool),
Null,
}
impl GameEventDataValue {
pub unsafe fn from_raw_string(ptr: *const c_char) -> Self {
if ptr.is_null() {
return Self::Null;
}
Self::String(unsafe { CStr::from_ptr(ptr) }.to_owned())
}
pub unsafe fn from_raw_wstring(ptr: *const WChar) -> Self {
if ptr.is_null() {
return Self::Null;
}
let mut len = 0;
while unsafe { *ptr.add(len) } != 0 {
len += 1;
}
Self::WString(unsafe { from_raw_parts(ptr, len) }.to_vec())
}
#[track_caller]
pub fn unwrap_bool(self) -> bool {
let Self::Bool(bool) = self else {
panic!("Expected `Bool` found {self:?}");
};
bool
}
#[track_caller]
pub fn unwrap_float(self) -> f32 {
let Self::Float(float) = self else {
panic!("Expected `Float` found {self:?}");
};
float
}
#[track_caller]
pub fn unwrap_int(self) -> c_int {
let Self::Int(int) = self else {
panic!("Expected `Int` found {self:?}");
};
int
}
#[track_caller]
pub fn unwrap_local(self) -> GameEventDataLocal {
let Self::Local(local) = self else {
panic!("Expected `Local` found {self:?}");
};
local
}
#[track_caller]
pub fn unwrap_optional_local(self) -> Option<GameEventDataLocal> {
match self {
Self::Local(local) => Some(local),
Self::Null => None,
other => panic!("Expected `Local` found {other:?}"),
}
}
#[track_caller]
pub fn unwrap_optional_user_id(self) -> Option<UserId> {
let Self::Int(int) = self else {
panic!("Expected `Int` found {self:?}");
};
match UserId::from_raw(int) {
Ok(user_id) => Some(user_id),
Err(error) if error.is_sentinel() => None, Err(error) => {
panic!("Game event value was an integer {int}, but not a valid user ID {error}")
}
}
}
#[track_caller]
pub fn unwrap_string(self) -> CString {
let Self::String(string) = self else {
panic!("Expected `String` found {self:?}");
};
string
}
#[track_caller]
pub fn unwrap_uint(self) -> u64 {
let Self::UInt64(uint) = self else {
panic!("Expected `UInt64` found {self:?}");
};
uint
}
#[track_caller]
pub fn unwrap_user_id(self) -> UserId {
let Self::Int(int) = self else {
panic!("Expected `Int` found {self:?}");
};
match UserId::from_raw(int) {
Ok(user_id) => user_id,
Err(error) => panic!("{error}"),
}
}
#[track_caller]
pub fn unwrap_wstring(self) -> Vec<WChar> {
let Self::WString(wstring) = self else {
panic!("Expected `WString` found {self:?}");
};
wstring
}
}
pub trait GameEventVisitor {
type Break;
fn visit(
&mut self,
key: GameEventDataKey,
value: GameEventDataValue,
) -> ControlFlow<Self::Break>;
}
#[repr(C)]
#[derive(Debug)]
#[non_exhaustive]
struct GameEventVistorVtable {
visit_local:
unsafe extern "C" fn(this: *mut c_void, name: *const c_char, value: *const c_void) -> bool,
visit_string:
unsafe extern "C" fn(this: *mut c_void, name: *const c_char, value: *const c_char) -> bool,
visit_float:
unsafe extern "C" fn(this: *mut c_void, name: *const c_char, value: c_float) -> bool,
visit_int: unsafe extern "C" fn(this: *mut c_void, name: *const c_char, value: c_int) -> bool,
visit_uint64: unsafe extern "C" fn(this: *mut c_void, name: *const c_char, value: u64) -> bool,
visit_wstring:
unsafe extern "C" fn(this: *mut c_void, name: *const c_char, value: *const WChar) -> bool,
visit_bool: unsafe extern "C" fn(this: *mut c_void, name: *const c_char, value: bool) -> bool,
}
impl GameEventVistorVtable {
const _ASSERT_LAYOUT: () = {
assert!(offset_of!(Self, visit_local) == 0);
assert!(size_of::<Self>() == size_of::<*const ()>() * 7);
};
}
macro_rules! visit_methods {
($Method:ident) => {
const VTABLE: GameEventVistorVtable = GameEventVistorVtable {
visit_local: Self::visit_local,
visit_string: Self::visit_string,
visit_float: Self::visit_float,
visit_int: Self::visit_int,
visit_uint64: Self::visit_uint64,
visit_wstring: Self::visit_wstring,
visit_bool: Self::visit_bool,
};
unsafe extern "C" fn visit_local(
this: *mut c_void,
name: *const c_char,
value: *const c_void,
) -> bool {
let value = NonNull::new(value.cast_mut()).map_or(GameEventDataValue::Null, |value| {
GameEventDataValue::Local(GameEventDataLocal(value))
});
unsafe { Self::$Method(this, name, value) }
}
unsafe extern "C" fn visit_string(
this: *mut c_void,
name: *const c_char,
value: *const c_char,
) -> bool {
unsafe { Self::$Method(this, name, GameEventDataValue::from_raw_string(value)) }
}
unsafe extern "C" fn visit_float(
this: *mut c_void,
name: *const c_char,
value: c_float,
) -> bool {
unsafe { Self::$Method(this, name, GameEventDataValue::Float(value)) }
}
unsafe extern "C" fn visit_int(
this: *mut c_void,
name: *const c_char,
value: c_int,
) -> bool {
unsafe { Self::$Method(this, name, GameEventDataValue::Int(value)) }
}
unsafe extern "C" fn visit_uint64(
this: *mut c_void,
name: *const c_char,
value: u64,
) -> bool {
unsafe { Self::$Method(this, name, GameEventDataValue::UInt64(value)) }
}
unsafe extern "C" fn visit_wstring(
this: *mut c_void,
name: *const c_char,
value: *const WChar,
) -> bool {
unsafe { Self::$Method(this, name, GameEventDataValue::from_raw_wstring(value)) }
}
unsafe extern "C" fn visit_bool(
this: *mut c_void,
name: *const c_char,
value: bool,
) -> bool {
unsafe { Self::$Method(this, name, GameEventDataValue::Bool(value)) }
}
};
}
#[repr(C)]
struct VisitorExecutor<V: GameEventVisitor> {
vtable: &'static GameEventVistorVtable,
state: ControlFlow<V::Break, V>,
}
impl<V: GameEventVisitor> VisitorExecutor<V> {
fn new(visitor: V) -> Self {
Self {
vtable: &Self::VTABLE,
state: ControlFlow::Continue(visitor),
}
}
unsafe fn visit(this: *mut c_void, name: *const c_char, value: GameEventDataValue) -> bool {
assert!(!name.is_null());
let exec = unsafe { this.cast::<Self>().as_mut() }.expect("Visitor object is null");
let ControlFlow::Continue(visitor) = &mut exec.state else {
panic!();
};
if let ControlFlow::Break(output) =
visitor.visit(unsafe { GameEventDataKey::from_ptr(name) }, value)
{
exec.state = ControlFlow::Break(output);
false
} else {
true
}
}
visit_methods!(visit);
}
#[repr(C)]
struct VisitKeyFinder<'a> {
vtable: &'static GameEventVistorVtable,
state: ControlFlow<GameEventDataValue, &'a str>,
}
impl<'a> VisitKeyFinder<'a> {
fn new(key: &'a str) -> Self {
Self {
vtable: &Self::VTABLE,
state: ControlFlow::Continue(key),
}
}
unsafe fn visit(this: *mut c_void, name: *const c_char, value: GameEventDataValue) -> bool {
assert!(!name.is_null());
let finder = unsafe { this.cast::<Self>().as_mut() }.expect("Visitor object is null");
let Ok(name) = unsafe { CStr::from_ptr(name) }.to_str() else {
panic!("Invalid UTF-8 yielded IGameEvent KeyValues visitor {name:?}")
};
let ControlFlow::Continue(key) = finder.state else {
unreachable!()
};
if key == name {
finder.state = ControlFlow::Break(value);
false
} else {
true
}
}
visit_methods!(visit);
}
const _: () = {
let slot_size = size_of::<*const ()>();
let destructor_slots = if cfg!(target_os = "linux") { 2 } else { 1 };
assert!(
offset_of!(sys::IGameEvent__bindgen_vtable, IGameEvent_GetName)
== slot_size * destructor_slots
);
assert!(
offset_of!(sys::IGameEvent__bindgen_vtable, IGameEvent_IsReliable)
== slot_size * (destructor_slots + 1)
);
assert!(
offset_of!(sys::IGameEvent__bindgen_vtable, IGameEvent_IsLocal)
== slot_size * (destructor_slots + 2)
);
assert!(
offset_of!(
sys::IGameEventManager2__bindgen_vtable,
IGameEventManager2_LoadEventsFromFile
) == slot_size * destructor_slots
);
assert!(
offset_of!(
sys::IGameEventManager2__bindgen_vtable,
IGameEventManager2_AddListener
) == slot_size * (destructor_slots + 2)
);
assert!(
offset_of!(
sys::IGameEventManager2__bindgen_vtable,
IGameEventManager2_RemoveListener
) == slot_size * (destructor_slots + 4)
);
assert!(
offset_of!(GameEventListenerVtable, fire_game_event)
== offset_of!(
sys::IGameEventListener2__bindgen_vtable,
IGameEventListener2_FireGameEvent
)
);
assert!(
size_of::<GameEventListenerVtable>()
== size_of::<sys::IGameEventListener2__bindgen_vtable>()
);
};
#[cfg(test)]
mod tests {
use super::*;
use crate::ffi::test_support::{mock_vtable, unexpected_call};
use std::cell::{Cell, RefCell};
use std::ptr::null_mut;
thread_local! {
static RECEIVED_MANAGER: Cell<*mut sys::IGameEventManager2> = const { Cell::new(null_mut()) };
static RECEIVED_LISTENER: Cell<*mut sys::IGameEventListener2> = const { Cell::new(null_mut()) };
static RECEIVED_NAME: Cell<*const c_char> = const { Cell::new(std::ptr::null()) };
static RECEIVED_SERVER_SIDE: Cell<bool> = const { Cell::new(false) };
static REMOVE_CALLED: Cell<bool> = const { Cell::new(false) };
static FREED: Cell<usize> = const { Cell::new(0) };
static FIRED: Cell<usize> = const { Cell::new(0) };
static DELIVERED: RefCell<Vec<CString>> = const { RefCell::new(Vec::new()) };
}
unsafe extern "C" fn record_add_listener(
manager: *mut sys::IGameEventManager2,
listener: *mut sys::IGameEventListener2,
name: *const c_char,
server_side: bool,
) -> bool {
RECEIVED_MANAGER.set(manager);
RECEIVED_LISTENER.set(listener);
RECEIVED_NAME.set(name);
RECEIVED_SERVER_SIDE.set(server_side);
let name = unsafe { CStr::from_ptr(name) };
name == c"player_regenerate"
}
unsafe extern "C" fn record_remove_listener(
manager: *mut sys::IGameEventManager2,
listener: *mut sys::IGameEventListener2,
) {
RECEIVED_MANAGER.set(manager);
RECEIVED_LISTENER.set(listener);
REMOVE_CALLED.set(true);
}
unsafe extern "C" fn event_name(_: *const sys::IGameEvent) -> *const c_char {
c"player_regenerate".as_ptr()
}
unsafe extern "C" fn event_is_empty(_: *mut sys::IGameEvent, key: *const c_char) -> bool {
let key = unsafe { CStr::from_ptr(key) };
key != c"userid"
}
unsafe extern "C" fn event_get_int(
_: *const sys::IGameEvent,
_: *const c_char,
_: c_int,
) -> c_int {
7
}
unsafe extern "C" fn create_event(
_: *mut sys::IGameEventManager2,
name: *const c_char,
force: bool,
) -> *mut sys::IGameEvent {
assert!(!force);
if unsafe { CStr::from_ptr(name) } == c"player_regenerate" {
EVENT.with(|event| event.as_ptr())
} else {
null_mut()
}
}
unsafe extern "C" fn free_event(_: *mut sys::IGameEventManager2, _: *mut sys::IGameEvent) {
FREED.set(FREED.get() + 1);
}
unsafe extern "C" fn fire_event(
_: *mut sys::IGameEventManager2,
event: *mut sys::IGameEvent,
dont_broadcast: bool,
) -> bool {
assert!(dont_broadcast);
FIRED.set(FIRED.get() + 1);
unsafe { deliver(event) };
true
}
thread_local! {
static EVENT_VTABLE: Box<sys::IGameEvent__bindgen_vtable> = unsafe {
mock_vtable::<sys::IGameEvent__bindgen_vtable>(unexpected_call as *const (), |vtable| {
(&raw mut (*vtable).IGameEvent_GetName).write(event_name);
(&raw mut (*vtable).IGameEvent_IsEmpty).write(event_is_empty);
(&raw mut (*vtable).IGameEvent_GetInt).write(event_get_int);
})
};
static EVENT: Cell<sys::IGameEvent> = Cell::new(sys::IGameEvent { vtable_: EVENT_VTABLE.with(|vtable| &raw const **vtable) });
static LISTENER: Cell<*mut sys::IGameEventListener2> = const { Cell::new(null_mut()) };
}
unsafe fn deliver(event: *mut sys::IGameEvent) {
let listener = LISTENER.get();
let fire = unsafe { (*(*listener).vtable_).IGameEventListener2_FireGameEvent };
unsafe { fire(listener, event) };
}
#[derive(Debug)]
struct RecordingHandler;
impl GameEventHandler for RecordingHandler {
fn fire_game_event(&self, event: GameEvent<'_>) {
assert_eq!(event.get_int(c"userid"), Some(7));
assert_eq!(event.get_int(c"attacker"), None);
DELIVERED.with_borrow_mut(|delivered| delivered.push(event.name().to_owned()));
}
}
fn mock_manager() -> Box<sys::IGameEventManager2__bindgen_vtable> {
unsafe {
mock_vtable::<sys::IGameEventManager2__bindgen_vtable>(
unexpected_call as *const (),
|vtable| {
(&raw mut (*vtable).IGameEventManager2_AddListener).write(record_add_listener);
(&raw mut (*vtable).IGameEventManager2_RemoveListener)
.write(record_remove_listener);
(&raw mut (*vtable).IGameEventManager2_CreateEvent).write(create_event);
(&raw mut (*vtable).IGameEventManager2_FreeEvent).write(free_event);
(&raw mut (*vtable).IGameEventManager2_FireEvent).write(fire_event);
},
)
}
}
#[test]
fn listeners_register_by_address_and_receive_fired_events() {
let vtable = mock_manager();
let mut interface = sys::IGameEventManager2 {
vtable_: &raw const *vtable,
};
let interface_pointer = &raw mut interface;
let manager =
unsafe { GameEventManager::from_raw(NonNull::new(interface_pointer).unwrap()) };
let listener = std::pin::pin!(GameEventListener::new(RecordingHandler));
let listener = listener.into_ref();
let listener_pointer = listener.as_raw();
LISTENER.set(listener_pointer);
assert!(unsafe { manager.add_listener(listener, c"player_regenerate", true) }.is_ok());
assert_eq!(RECEIVED_MANAGER.get(), interface_pointer);
assert_eq!(RECEIVED_LISTENER.get(), listener_pointer);
assert!(RECEIVED_SERVER_SIDE.get());
assert_eq!(
unsafe { manager.add_listener(listener, c"not_an_event", true) }
.unwrap_err()
.to_string(),
"could not listen for `not_an_event`; no such game event is registered"
);
drop(manager.create_event(c"player_regenerate").unwrap());
assert_eq!(FREED.get(), 1);
assert!(manager.create_event(c"not_an_event").is_err());
let event = manager.create_event(c"player_regenerate").unwrap();
assert_eq!(event.as_event().name(), c"player_regenerate");
event.fire(false).unwrap();
assert_eq!((FIRED.get(), FREED.get()), (1, 1));
assert_eq!(DELIVERED.take(), [c"player_regenerate".to_owned()]);
manager.remove_listener(listener);
assert!(REMOVE_CALLED.get());
assert_eq!(RECEIVED_LISTENER.get(), listener_pointer);
}
#[test]
fn int_values_unwrap_as_user_ids() {
assert_eq!(
GameEventDataValue::Int(7).unwrap_user_id(),
UserId::new(7).unwrap()
);
assert_eq!(
GameEventDataValue::Int(7).unwrap_optional_user_id(),
UserId::new(7)
);
assert_eq!(GameEventDataValue::Int(0).unwrap_optional_user_id(), None);
assert_eq!(GameEventDataValue::Int(-1).unwrap_optional_user_id(), None);
}
#[test]
#[should_panic(expected = "Invalid UserId representation: 0")]
fn unwrapping_the_no_player_zero_as_a_user_id_panics() {
GameEventDataValue::Int(0).unwrap_user_id();
}
}