use std::{any::Any, fmt};
use crate::lua_vm::lua_error::LuaError;
use crate::{LuaValue, RefAliveToken, UserDataTrait, gc::TablePtr};
pub enum UserdataStorage {
Owned(Box<dyn UserDataTrait>),
Borrowed(*mut dyn UserDataTrait),
}
pub struct LuaUserdata {
data: UserdataStorage,
metatable: TablePtr,
alive_token: RefAliveToken,
}
impl LuaUserdata {
pub fn new<T: UserDataTrait>(data: T) -> Self {
LuaUserdata {
data: UserdataStorage::Owned(Box::new(data)),
metatable: TablePtr::null(),
alive_token: RefAliveToken::default(),
}
}
pub fn from_boxed(data: Box<dyn UserDataTrait>) -> Self {
LuaUserdata {
data: UserdataStorage::Owned(data),
metatable: TablePtr::null(),
alive_token: RefAliveToken::default(),
}
}
pub fn from_ref<T: UserDataTrait>(reference: &mut T, token: RefAliveToken) -> Self {
LuaUserdata {
data: UserdataStorage::Borrowed(reference as *mut T as *mut dyn UserDataTrait),
metatable: TablePtr::null(),
alive_token: token,
}
}
pub fn from_ptr<T: UserDataTrait>(ptr: *const T, token: RefAliveToken) -> Self {
LuaUserdata {
data: UserdataStorage::Borrowed(ptr as *mut T as *mut dyn UserDataTrait),
metatable: TablePtr::null(),
alive_token: token,
}
}
pub fn from_trait_ptr(ptr: *const (dyn UserDataTrait + 'static), token: RefAliveToken) -> Self {
LuaUserdata {
data: UserdataStorage::Borrowed(ptr as *mut (dyn UserDataTrait + 'static)),
metatable: TablePtr::null(),
alive_token: token,
}
}
pub fn with_metatable<T: UserDataTrait>(data: T, metatable: TablePtr) -> Self {
LuaUserdata {
data: UserdataStorage::Owned(Box::new(data)),
metatable,
alive_token: RefAliveToken::default(),
}
}
#[inline]
pub fn is_alive(&self) -> bool {
match &self.data {
UserdataStorage::Owned(_) => true,
UserdataStorage::Borrowed(_) => self.alive_token.is_alive(),
}
}
#[inline]
pub fn sub_guard_token(&self) -> RefAliveToken {
self.alive_token.clone()
}
#[inline]
pub fn get_trait(&self) -> Result<&dyn UserDataTrait, LuaError> {
if !self.is_alive() {
return Err(LuaError::ExpiredReference);
}
Ok(match &self.data {
UserdataStorage::Owned(boxed) => boxed.as_ref(),
UserdataStorage::Borrowed(ptr) => unsafe { &**ptr },
})
}
#[inline]
pub fn get_trait_mut(&mut self) -> Result<&mut dyn UserDataTrait, LuaError> {
if !self.is_alive() {
return Err(LuaError::ExpiredReference);
}
Ok(match &mut self.data {
UserdataStorage::Owned(boxed) => boxed.as_mut(),
UserdataStorage::Borrowed(ptr) => unsafe { &mut **ptr },
})
}
#[inline]
pub fn type_name(&self) -> &'static str {
match self.get_trait() {
Ok(t) => t.type_name(),
Err(_) => "expired_userdata",
}
}
#[inline]
pub fn downcast_ref<T: 'static>(&self) -> Option<&T> {
self.get_trait().ok()?.as_any().downcast_ref::<T>()
}
#[inline]
pub fn downcast_mut<T: 'static>(&mut self) -> Option<&mut T> {
self.get_trait_mut().ok()?.as_any_mut().downcast_mut::<T>()
}
pub fn get_data(&self) -> Result<&dyn Any, LuaError> {
Ok(self.get_trait()?.as_any())
}
pub fn get_data_mut(&mut self) -> Result<&mut dyn Any, LuaError> {
Ok(self.get_trait_mut()?.as_any_mut())
}
pub fn get_metatable(&self) -> Option<LuaValue> {
if self.metatable.is_null() {
None
} else {
Some(LuaValue::table(self.metatable))
}
}
pub(crate) fn set_metatable(&mut self, metatable: LuaValue) {
if let Some(table_ptr) = metatable.as_table_ptr() {
self.metatable = table_ptr;
} else if metatable.is_nil() {
self.metatable = TablePtr::null();
}
}
}
impl Drop for LuaUserdata {
fn drop(&mut self) {
if let UserdataStorage::Owned(_) = &self.data {
self.alive_token.set(false);
}
}
}
impl fmt::Debug for LuaUserdata {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.get_trait() {
Ok(trait_obj) => write!(
f,
"Userdata({}@{:p})",
trait_obj.type_name(),
trait_obj.as_any() as *const dyn Any
),
Err(_) => write!(f, "Userdata(expired)"),
}
}
}