use std::{any::TypeId, os::raw::c_void};
use crate::{lua, lua::raw::lua_State};
pub type UserDataMethods = &'static [(lua::CStr<'static>, lua::RustFunction)];
pub const INDEX_KEY: i32 = 1;
#[repr(C)]
struct EmptyUserData;
#[repr(C)]
pub struct TaggedUserData<T: 'static> {
pub data: T,
pub type_id: TypeId,
}
impl<T: 'static> TaggedUserData<T> {
pub fn new(data: T) -> Self {
Self {
data,
type_id: TypeId::of::<T>(),
}
}
pub fn is(&self) -> bool {
self.type_id == TypeId::of::<T>()
}
pub fn from_ptr<'a>(ptr: *mut c_void) -> Option<&'a mut Self> {
if ptr.is_null() {
return None;
}
let tagged_ptr = ptr as *mut Self;
let tagged = unsafe { &mut *tagged_ptr };
if !tagged.is() {
return None;
}
Some(tagged)
}
fn consume(ptr: *mut c_void) -> Option<TaggedUserData<T>> {
if ptr.is_null() {
return None;
}
let tagged_ptr = ptr as *mut Self;
let tagged_ref = unsafe { &mut *tagged_ptr };
if !tagged_ref.is() {
return None;
}
tagged_ref.type_id = TypeId::of::<EmptyUserData>();
let tagged = unsafe { std::ptr::read(tagged_ptr) };
Some(tagged)
}
}
extern "C" fn userdata_gc<T: UserData>(l: *mut lua_State) -> i32 {
let l = lua::State(l);
let tagged = TaggedUserData::<T>::consume(l.direct_to_userdata(-1));
match tagged {
Some(t) => drop(t.data), None => {
#[cfg(debug_assertions)]
eprintln!("[gmodx] Warning: __gc called on invalid userdata")
}
}
0
}
pub trait UserData: Sized + 'static {
const METATABLE_NAME: lua::CStr<'_>;
const METHODS: UserDataMethods = &[];
}
impl lua::State {
pub fn push_userdata<T: UserData>(self, ud: T) {
self.create_table(1, 0);
{
let tagged = TaggedUserData::new(ud);
let ud_ptr = self.direct_new_userdata(std::mem::size_of::<TaggedUserData<T>>());
let tagged_ptr = ud_ptr as *mut TaggedUserData<T>;
unsafe {
tagged_ptr.write(tagged);
}
}
self.create_table(0, 1);
{
self.push_cclosure(Some(userdata_gc::<T>), 0);
let _ = self.raw_set_field(-2, c"__gc");
}
self.set_metatable(-2);
let _ = self.raw_seti(-2, INDEX_KEY);
if self.new_metatable(T::METATABLE_NAME) {
for (name, func) in T::METHODS {
self.push_function(*func);
let _ = self.raw_set_field(-2, name);
}
self.push_value(-1); let _ = self.raw_set_field(-2, c"__index");
}
self.set_metatable(-2);
}
pub fn to_userdata<'a, T: UserData>(self, index: i32) -> Option<&'a mut T> {
self.check_table(index).ok()?;
self.raw_geti(index, INDEX_KEY).ok()?;
let tagged = TaggedUserData::<T>::from_ptr(self.direct_to_userdata(-1));
tagged.map(|t| &mut t.data)
}
pub fn check_userdata<'a, T: UserData>(self, index: i32) -> Result<&'a mut T, lua::Error> {
self.to_userdata(index).ok_or_else(|| {
lua::Error::Message(self.type_error(index, &T::METATABLE_NAME.to_string_lossy()))
})
}
}