#[cfg(test)]
#[path = "tests/send_proxies.rs"]
mod tests;
use crate::NotThreadSafe;
use crate::Server;
use crate::datatables::{PropKind, SendProp, SendTable};
use crate::edicts::{Edict, MAX_EDICTS};
use crate::interfaces::ValveEngine;
use crate::math::Vector;
use glam::Vec2;
use sdk_raw::send_proxies::{self as raw, InstallError, ProxyCall, SlotId};
use std::ffi::{CStr, c_char, c_int};
use std::marker::PhantomData;
use std::ptr::NonNull;
use std::sync::atomic::{AtomicU64, Ordering};
pub use sdk_raw::send_proxies::MAX_SEND_PROXY_OVERRIDES;
pub trait EntityValue: sealed::Sealed + Copy + Send + Sync + 'static {
#[doc(hidden)]
fn from_bits(bits: u32) -> Self;
#[doc(hidden)]
fn to_bits(self) -> u32;
}
impl EntityValue for bool {
fn from_bits(bits: u32) -> Self {
bits != 0
}
fn to_bits(self) -> u32 {
u32::from(self)
}
}
impl EntityValue for f32 {
fn from_bits(bits: u32) -> Self {
Self::from_bits(bits)
}
fn to_bits(self) -> u32 {
self.to_bits()
}
}
impl EntityValue for i32 {
fn from_bits(bits: u32) -> Self {
bits.cast_signed()
}
fn to_bits(self) -> u32 {
self.cast_unsigned()
}
}
impl EntityValue for u32 {
fn from_bits(bits: u32) -> Self {
bits
}
fn to_bits(self) -> u32 {
self
}
}
#[derive(Debug)]
pub struct EntityValues<T: EntityValue> {
slots: [AtomicU64; MAX_EDICTS as usize],
_value: PhantomData<T>,
}
impl<T: EntityValue> EntityValues<T> {
const SET: u64 = 1 << 32;
pub const fn new() -> Self {
Self {
slots: [const { AtomicU64::new(0) }; MAX_EDICTS as usize],
_value: PhantomData,
}
}
pub fn clear(&self) {
for slot in &self.slots {
slot.store(0, Ordering::Relaxed);
}
}
pub fn get(&self, index: usize) -> Option<T> {
let slot = self.slots.get(index)?.load(Ordering::Relaxed);
(slot & Self::SET != 0).then(|| T::from_bits(slot as u32))
}
pub fn set(&self, index: usize, value: Option<T>) -> bool {
let Some(slot) = self.slots.get(index) else {
return false;
};
let bits = value.map_or(0, |value| Self::SET | u64::from(value.to_bits()));
slot.swap(bits, Ordering::Relaxed) != bits
}
pub fn set_and_resend(&self, engine: ValveEngine<'_>, edict: Edict<'_>, value: Option<T>) {
let changed = usize::try_from(edict.index()).is_ok_and(|index| self.set(index, value));
if changed {
edict.full_state_changed(engine);
}
}
}
impl<T: EntityValue> Default for EntityValues<T> {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum SendProxyError {
#[error("`{name}` is {kind}, which has no single value to send")]
NotAValue {
name: String,
kind: PropKind,
},
#[error("`{name}` has no send proxy")]
NoProxy {
name: String,
},
#[error("`{name}`'s send proxy is already overridden")]
AlreadyOverridden {
name: String,
},
#[error("{MAX_SEND_PROXY_OVERRIDES} send proxies are already overridden")]
Full,
}
#[must_use = "dropping the override puts the game's send proxy back at once"]
#[derive(Debug)]
pub struct SendProxyOverride {
id: SlotId,
prop: NonNull<sys::SendProp>,
_not_thread_safe: NotThreadSafe,
}
impl SendProxyOverride {
#[doc(alias("m_ProxyFn", "SendProxy"))]
pub fn install<F>(prop: SendProp<'_>, callback: F) -> Result<Self, SendProxyError>
where
F: for<'a> Fn(Sent, SentValue<'a>) -> Option<SentValue<'a>> + Send + Sync + 'static,
{
let name = || prop.name().to_string_lossy().into_owned();
let kind = prop.kind();
if !SentValue::sendable(kind) {
return Err(SendProxyError::NotAValue { name: name(), kind });
}
let handler = move |call: ProxyCall| {
let (Ok(entity_index), Ok(element)) = (
usize::try_from(call.object_id),
usize::try_from(call.element),
) else {
return;
};
unsafe {
let sent = SentValue::read(kind, call.out);
if let Some(value) = callback(
Sent {
entity_index,
element,
},
sent,
) {
value.write(kind, call.out);
}
}
};
let raw = NonNull::new(prop.as_ptr()).expect("a send property is never null");
let id = unsafe { raw::install(raw, Box::new(handler)) }.map_err(|error| match error {
InstallError::NoProxy => SendProxyError::NoProxy { name: name() },
InstallError::AlreadyOverridden => SendProxyError::AlreadyOverridden { name: name() },
InstallError::Full => SendProxyError::Full,
})?;
Ok(Self {
id,
prop: raw,
_not_thread_safe: PhantomData,
})
}
pub fn is_installed(&self) -> bool {
raw::is_installed(self.id)
}
pub const fn prop_ptr(&self) -> *mut sys::SendProp {
self.prop.as_ptr()
}
pub fn remove(self, server: Server<'_>) {
let prop = self.prop;
drop(self);
resend_all(server, prop);
}
pub fn resend_all(&self, server: Server<'_>) {
resend_all(server, self.prop);
}
}
impl Drop for SendProxyOverride {
fn drop(&mut self) {
unsafe { raw::restore(self.id) };
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Sent {
entity_index: usize,
element: usize,
}
impl Sent {
pub const fn element(self) -> usize {
self.element
}
pub const fn entity_index(self) -> usize {
self.entity_index
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum SentValue<'a> {
Int(c_int),
Float(f32),
Vector(Vector),
VectorXY(Vec2),
String(&'a CStr),
}
impl SentValue<'_> {
unsafe fn read<'a>(kind: PropKind, out: *const sys::DVariant) -> SentValue<'a> {
unsafe {
let value = &raw const (*out).__bindgen_anon_1;
match kind {
PropKind::Int => SentValue::Int((&raw const (*value).m_Int).read()),
PropKind::Float => SentValue::Float((&raw const (*value).m_Float).read()),
PropKind::Vector => {
let [x, y, z] = (&raw const (*value).m_Vector).read();
SentValue::Vector(Vector::new(x, y, z))
}
PropKind::VectorXY => {
let [x, y, _] = (&raw const (*value).m_Vector).read();
SentValue::VectorXY(Vec2::new(x, y))
}
_ => {
let string: *const c_char = (&raw const (*value).m_pString).read();
SentValue::String(if string.is_null() {
c""
} else {
CStr::from_ptr(string)
})
}
}
}
}
const fn sendable(kind: PropKind) -> bool {
matches!(
kind,
PropKind::Int
| PropKind::Float
| PropKind::Vector
| PropKind::VectorXY
| PropKind::String
)
}
pub const fn kind(self) -> PropKind {
match self {
Self::Int(_) => PropKind::Int,
Self::Float(_) => PropKind::Float,
Self::Vector(_) => PropKind::Vector,
Self::VectorXY(_) => PropKind::VectorXY,
Self::String(_) => PropKind::String,
}
}
unsafe fn write(self, kind: PropKind, out: *mut sys::DVariant) {
if self.kind() != kind {
return;
}
unsafe {
let value = &raw mut (*out).__bindgen_anon_1;
match self {
Self::Int(int) => (&raw mut (*value).m_Int).write(int),
Self::Float(float) => (&raw mut (*value).m_Float).write(float),
Self::Vector(vector) => {
(&raw mut (*value).m_Vector).write([vector.x, vector.y, vector.z])
}
Self::VectorXY(vector) => {
let z = (&raw const (*value).m_Vector).read()[2];
(&raw mut (*value).m_Vector).write([vector.x, vector.y, z]);
}
Self::String(string) => (&raw mut (*value).m_pString).write(string.as_ptr()),
}
}
}
}
fn holds_prop(table: SendTable<'_>, prop: NonNull<sys::SendProp>, depth: usize) -> bool {
const MAX_DEPTH: usize = 32;
depth <= MAX_DEPTH
&& table.props().any(|candidate| {
candidate.as_ptr() == prop.as_ptr()
|| candidate
.data_table()
.is_some_and(|nested| holds_prop(nested, prop, depth + 1))
})
}
fn resend_all(server: Server<'_>, prop: NonNull<sys::SendProp>) {
let Ok(engine) = server.valve_engine() else {
return;
};
let mut checked: Vec<(*mut sys::SendTable, bool)> = Vec::new();
for index in 0..MAX_EDICTS {
let Some(edict) = engine.edict_of_index(index) else {
continue;
};
let Some(table) = edict
.entity()
.and_then(|entity| entity.server_class())
.and_then(|class| class.table())
else {
continue;
};
let holds = match checked
.iter()
.find(|(checked, _)| *checked == table.as_ptr())
{
Some(&(_, holds)) => holds,
None => {
let holds = holds_prop(table, prop, 0);
checked.push((table.as_ptr(), holds));
holds
}
};
if holds {
edict.full_state_changed(engine);
}
}
}
pub fn restore_all(_server: Server<'_>) {
unsafe { raw::restore_all() };
}
mod sealed {
pub trait Sealed {}
impl Sealed for bool {}
impl Sealed for f32 {}
impl Sealed for i32 {}
impl Sealed for u32 {}
}