use crate::NotThreadSafe;
use crate::bitbuf::BitWriter;
use crate::players::UserId;
use sdk_raw::abi::WChar;
use sdk_raw::bitbuf::BfWrite;
use sdk_raw::interfaces::game_event::{
EventValue, GameEventListenerObject, MAX_EVENT_BYTES, OnFireGameEvent, for_event_data,
};
use sdk_raw::util::cstr::{borrow_cstr, copy_cstr};
use sdk_raw::vcall;
use std::collections::{HashMap, HashSet};
use std::ffi::{CStr, CString, c_int, c_void};
use std::fmt::{Display, Formatter};
use std::marker::{PhantomData, PhantomPinned};
use std::mem::{ManuallyDrop, size_of};
use std::ops::{ControlFlow, Deref};
use std::pin::Pin;
use std::ptr::NonNull;
#[doc(alias("INTERFACEVERSION_GAMEEVENTSMANAGER2"))]
pub const GAME_EVENT_MANAGER_INTERFACE_VERSION: &CStr = GameEventManager::<'static>::VERSION;
#[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,
}
#[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,
}
#[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 state = ControlFlow::Continue(key.as_ref());
unsafe {
for_event_data(self.raw, &mut |name, value| {
let ControlFlow::Continue(key) = state else {
unreachable!("the engine visited a pair after the finder stopped")
};
if key.as_bytes() == name.to_bytes() {
state = ControlFlow::Break(GameEventDataValue::from_raw(value));
false
} else {
true
}
})
};
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())) }
}
#[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> {
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> {
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_set(self) -> HashMap<GameEventDataKey, GameEventDataValue> {
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 pairs_to_vec(self) -> Vec<GameEventDataPair> {
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 values_to_vec(self) -> Vec<GameEventDataValue> {
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
}
#[doc(alias("ForEventData"))]
pub fn visit<V: GameEventVisitor>(self, visitor: V) -> ControlFlow<V::Break, V> {
let mut state = ControlFlow::Continue(visitor);
unsafe {
for_event_data(self.raw, &mut |name, value| {
let ControlFlow::Continue(visitor) = &mut state else {
unreachable!("the engine visited a pair after the visitor broke")
};
let key = GameEventDataKey(name.to_owned());
let value = GameEventDataValue::from_raw(value);
if let ControlFlow::Break(output) = visitor.visit(key, value) {
state = ControlFlow::Break(output);
false
} else {
true
}
})
};
state
}
#[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("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 })
}
#[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)) };
}
}
impl Drop for OwnedGameEvent<'_> {
fn drop(&mut self) {
unsafe { vcall!(self.manager.as_ptr() => IGameEventManager2_FreeEvent(self.raw.as_ptr())) };
}
}
interface! {
#[doc(alias("IGameEventManager2"))]
pub struct GameEventManager(sys::IGameEventManager2) = Engine sdk_raw::interfaces::game_event::VERSION;
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct GameEventDataKey(pub(super) CString);
impl GameEventDataKey {
pub fn as_c_str(&self) -> &CStr {
&self.0
}
pub fn as_str(&self) -> &str {
self.0.to_str().expect("game event key should be UTF-8")
}
pub fn into_string(self) -> String {
self.0
.into_string()
.expect("game event key should be UTF-8")
}
}
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)]
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 {
fn from_raw(value: EventValue<'_>) -> Self {
match value {
EventValue::Local(local) => {
local.map_or(Self::Null, |local| Self::Local(GameEventDataLocal(local)))
}
EventValue::String(string) => {
string.map_or(Self::Null, |string| Self::String(string.to_owned()))
}
EventValue::Float(float) => Self::Float(float),
EventValue::Int(int) => Self::Int(int),
EventValue::UInt64(uint) => Self::UInt64(uint),
EventValue::WString(wide) => {
wide.map_or(Self::Null, |wide| Self::WString(wide.to_vec()))
}
EventValue::Bool(bool) => Self::Bool(bool),
}
}
#[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 GameEventHandler {
#[doc(alias("FireGameEvent"))]
fn fire_game_event(&self, event: GameEvent<'_>);
}
#[doc(alias("IGameEventListener2"))]
pub struct GameEventListener<H> {
raw: GameEventListenerObject<HandlerAdapter<H>>,
_pinned: PhantomPinned,
_not_thread_safe: NotThreadSafe,
}
impl<H: GameEventHandler> GameEventListener<H> {
pub const fn new(handler: H) -> Self {
Self {
raw: GameEventListenerObject::new(HandlerAdapter(handler)),
_pinned: PhantomPinned,
_not_thread_safe: PhantomData,
}
}
fn as_raw(self: Pin<&Self>) -> *mut sys::IGameEventListener2 {
self.get_ref().raw.as_raw()
}
pub const fn handler(&self) -> &H {
&self.raw.inner().0
}
}
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.raw.inner().0)
.finish_non_exhaustive()
}
}
#[doc(alias("IGameEventVisitor2"))]
pub trait GameEventVisitor {
type Break;
fn visit(
&mut self,
key: GameEventDataKey,
value: GameEventDataValue,
) -> ControlFlow<Self::Break>;
}
struct HandlerAdapter<H>(H);
impl<H: GameEventHandler> OnFireGameEvent for HandlerAdapter<H> {
unsafe fn fire_game_event(&self, event: NonNull<sys::IGameEvent>) {
self.0
.fire_game_event(unsafe { GameEvent::from_raw(event) });
}
}
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("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("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("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("RemoveListener"))]
pub fn remove_listener<H: GameEventHandler>(self, listener: Pin<&GameEventListener<H>>) {
unsafe { vcall!(self.as_ptr() => IGameEventManager2_RemoveListener(listener.as_raw())) };
}
#[doc(alias("Reset"))]
pub unsafe fn reset(self) {
unsafe { vcall!(self.as_ptr() => IGameEventManager2_Reset()) }
}
#[doc(alias("SerializeEvent"))]
pub fn serialize_event(self, event: GameEvent<'_>) -> Option<BitWriter> {
let mut storage = [0u32; MAX_EVENT_BYTES / size_of::<u32>()];
let mut buffer = BfWrite::empty(&mut storage);
let serialized = unsafe {
vcall!(self.as_ptr() => IGameEventManager2_SerializeEvent(
event.as_ptr(),
buffer.as_raw(),
))
};
serialized
.then(|| unsafe { BfWrite::read_back(NonNull::from(&mut buffer)) })
.flatten()
.map(BitWriter::from)
}
}