use crate::commands::{CommandBaseKind, CommandFlags};
use sdk_raw::commands::ICONVAR_OFFSET;
use sdk_raw::test_support::{mock_vtable, unexpected_call};
use std::cell::RefCell;
use std::ffi::{CStr, c_char};
use std::ptr::null_mut;
#[repr(C)]
struct MockCvar {
interface: sys::ICvar,
head: *mut sys::ConCommandBase,
vars: Vec<*mut sys::ConVar>,
callbacks: RefCell<Vec<sys::FnChangeCallback_t>>,
}
unsafe extern "C" fn call_global_change_callbacks(
this: *mut sys::ICvar,
var: *mut sys::ConVar,
old_value: *const c_char,
old_float: f32,
) {
let cvar = unsafe { &*this.cast::<MockCvar>() };
let interface = match var.is_null() {
true => null_mut(),
false => unsafe { &raw mut (*var)._base_1 },
};
let count = cvar.callbacks.borrow().len();
for index in 0..count {
let Some(callback) = cvar.callbacks.borrow().get(index).copied() else {
break;
};
if let Some(callback) = callback {
unsafe { callback(interface, old_value, old_float) };
}
}
}
unsafe extern "C" fn find_var(this: *mut sys::ICvar, name: *const c_char) -> *mut sys::ConVar {
let (name, cvar) = unsafe { (CStr::from_ptr(name), &*this.cast::<MockCvar>()) };
cvar.vars
.iter()
.copied()
.find(|&var| {
let listed = unsafe { CStr::from_ptr((*var)._base.m_pszName) };
listed.to_bytes().eq_ignore_ascii_case(name.to_bytes())
})
.unwrap_or(null_mut())
}
unsafe extern "C" fn get_commands(this: *mut sys::ICvar) -> *mut sys::ConCommandBase {
unsafe { (*this.cast::<MockCvar>()).head }
}
unsafe extern "C" fn get_name(this: *const sys::ConCommandBase) -> *const c_char {
unsafe { (&raw const (*this).m_pszName).read() }
}
unsafe extern "C" fn install_global_change_callback(
this: *mut sys::ICvar,
callback: sys::FnChangeCallback_t,
) {
let cvar = unsafe { &*this.cast::<MockCvar>() };
cvar.callbacks.borrow_mut().push(callback);
}
unsafe extern "C" fn interface_get_name(this: *const sys::IConVar) -> *const c_char {
unsafe {
let var = this.byte_sub(ICONVAR_OFFSET).cast::<sys::ConVar>();
let parent = (&raw const (*var).m_pParent).read();
(&raw const (*parent)._base.m_pszName).read()
}
}
pub unsafe extern "C" fn is_command(_: *const sys::ConCommandBase) -> bool {
true
}
unsafe extern "C" fn is_registered(this: *const sys::ConCommandBase) -> bool {
unsafe { (&raw const (*this).m_bRegistered).read() }
}
pub unsafe extern "C" fn is_variable(_: *const sys::ConCommandBase) -> bool {
false
}
pub fn mock_base(
name: &'static CStr,
kind: CommandBaseKind,
flags: CommandFlags,
next: *mut sys::ConCommandBase,
) -> sys::ConCommandBase {
let vtable = unsafe {
mock_vtable::<sys::ConCommandBase__bindgen_vtable>(unexpected_call as *const (), |vtable| {
(&raw mut (*vtable).ConCommandBase_IsCommand).write(match kind {
CommandBaseKind::Command => is_command,
CommandBaseKind::Variable => is_variable,
});
(&raw mut (*vtable).ConCommandBase_GetName).write(get_name);
(&raw mut (*vtable).ConCommandBase_IsRegistered).write(is_registered);
})
};
sys::ConCommandBase {
vtable_: Box::leak(vtable),
m_pNext: next,
m_bRegistered: true,
m_pszName: name.as_ptr(),
m_pszHelpString: c"".as_ptr(),
m_nFlags: flags.bits(),
}
}
pub fn mock_command(
name: &'static CStr,
next: *mut sys::ConCommandBase,
) -> *mut sys::ConCommandBase {
let base = mock_base(name, CommandBaseKind::Command, CommandFlags::NONE, next);
Box::into_raw(Box::new(base))
}
pub fn mock_cvar(head: *mut sys::ConCommandBase, vars: Vec<*mut sys::ConVar>) -> *mut sys::ICvar {
let vtable = unsafe {
mock_vtable::<sys::ICvar__bindgen_vtable>(unexpected_call as *const (), |vtable| {
(&raw mut (*vtable).ICvar_FindVar).write(find_var);
(&raw mut (*vtable).ICvar_GetCommands).write(get_commands);
(&raw mut (*vtable).ICvar_InstallGlobalChangeCallback)
.write(install_global_change_callback);
(&raw mut (*vtable).ICvar_RemoveGlobalChangeCallback)
.write(remove_global_change_callback);
(&raw mut (*vtable).ICvar_CallGlobalChangeCallbacks)
.write(call_global_change_callbacks);
})
};
Box::into_raw(Box::new(MockCvar {
interface: sys::ICvar {
vtable_: Box::leak(vtable),
},
head,
vars,
callbacks: RefCell::new(Vec::new()),
}))
.cast()
}
pub fn mock_var(
name: &'static CStr,
default: &'static CStr,
value: &'static CStr,
flags: CommandFlags,
next: *mut sys::ConCommandBase,
) -> *mut sys::ConVar {
let var = Box::into_raw(Box::new(unsafe { std::mem::zeroed::<sys::ConVar>() }));
let interface = unsafe {
mock_vtable::<sys::IConVar__bindgen_vtable>(unexpected_call as *const (), |vtable| {
(&raw mut (*vtable).IConVar_GetName).write(interface_get_name);
})
};
unsafe {
(*var)._base = mock_base(name, CommandBaseKind::Variable, flags, next);
(*var)._base_1.vtable_ = Box::leak(interface);
(*var).m_pParent = var;
(*var).m_pszDefaultValue = default.as_ptr();
(*var).m_pszString = value.as_ptr().cast_mut();
}
var
}
unsafe extern "C" fn remove_global_change_callback(
this: *mut sys::ICvar,
callback: sys::FnChangeCallback_t,
) {
let cvar = unsafe { &*this.cast::<MockCvar>() };
let mut callbacks = cvar.callbacks.borrow_mut();
let address = |callback: sys::FnChangeCallback_t| callback.map(|callback| callback as usize);
if let Some(index) = callbacks
.iter()
.position(|&listed| address(listed) == address(callback))
{
callbacks.remove(index);
}
}