use core::mem::MaybeUninit;
use core::ptr::{self, NonNull};
use crate::Table;
use crate::VmErrorResult;
use crate::gc::{GcBarrier, GcObject, GcRuntime};
use crate::handle::RawHandle;
use crate::handle::sealed::Sealed;
use crate::layout::Align8Byte;
use crate::memory::{LuaPage, MemoryRuntime};
use crate::native::NativeCallResult;
use crate::state::{GlobalState, ThreadState};
use crate::string::TString;
use crate::table::RawLuaTable;
use crate::thread::Thread;
use crate::types::LUA_TUSERDATA;
use crate::value::{RawTValue, TValue};
mod typed;
pub use typed::UserdataTypeRegistration;
pub(crate) use typed::UserdataTypeRegistry;
pub use typed::{
TypedUserdata, TypedUserdataAccess, TypedUserdataError, UserdataTypeRegistryAccess,
};
#[repr(C)]
pub struct RawUserdata {
pub tt: u8,
pub marked: u8,
pub memcat: u8,
pub tag: u8,
pub len: i32,
pub metatable: *mut RawLuaTable,
pub data: [Align8Byte; 1],
}
#[derive(Clone, Copy, PartialEq, Eq)]
#[repr(transparent)]
pub struct Userdata {
raw: NonNull<RawUserdata>,
}
impl Userdata {
pub const unsafe fn from_raw(raw: NonNull<RawUserdata>) -> Self {
Self { raw }
}
const fn size_userdata(payload_len: usize) -> usize {
core::mem::offset_of!(RawUserdata, data)
+ if payload_len > 16 {
(payload_len + 15) & !15
} else {
payload_len
}
}
pub fn allocation_size_for_payload_len(payload_len: usize) -> Option<usize> {
let max_len = i32::MAX as usize - core::mem::size_of::<RawUserdata>();
(payload_len <= max_len).then(|| Self::size_userdata(payload_len))
}
pub unsafe fn metatable(&self) -> Option<Table> {
unsafe {
NonNull::new(self.as_ptr().as_ref().unwrap_unchecked().metatable)
.map(|raw| Table::from_raw(raw))
}
}
pub unsafe fn set_metatable(&self, metatable: Option<Table>) {
unsafe {
self.as_ptr().as_mut().unwrap_unchecked().metatable =
metatable.map_or(core::ptr::null_mut(), |table| table.as_ptr())
};
}
pub fn total_payload_len(&self) -> usize {
unsafe { self.as_ptr().as_ref().unwrap_unchecked().len as usize }
}
pub const fn data_ptr(&self) -> *const u8 {
unsafe { (&raw const self.raw.as_ref().data).cast::<u8>() }
}
pub const unsafe fn data_mut_ptr(&self) -> *mut u8 {
unsafe { (&raw mut (*self.raw.as_ptr()).data).cast::<u8>() }
}
unsafe fn inline_destructor_slot(&self, requested_payload_len: usize) -> *mut u8 {
unsafe { self.data_mut_ptr().add(requested_payload_len).cast::<u8>() }
}
pub unsafe fn set_inline_destructor(
&self,
requested_payload_len: usize,
destructor: LuaInlineDestructor,
) {
debug_assert_eq!(
unsafe { self.as_ptr().as_ref().unwrap_unchecked().tag as usize },
USERDATA_TAG_IDTOR
);
let inline_destructor_size = core::mem::size_of::<LuaInlineDestructor>();
debug_assert!(self.total_payload_len() >= inline_destructor_size);
debug_assert_eq!(
self.total_payload_len() - inline_destructor_size,
requested_payload_len
);
unsafe {
ptr::copy_nonoverlapping(
(&raw const destructor).cast::<u8>(),
self.inline_destructor_slot(requested_payload_len),
inline_destructor_size,
);
}
}
pub unsafe fn inline_destructor(&self) -> LuaInlineDestructor {
debug_assert_eq!(
unsafe { self.as_ptr().as_ref().unwrap_unchecked().tag as usize },
USERDATA_TAG_IDTOR
);
let mut destructor = MaybeUninit::<LuaInlineDestructor>::uninit();
unsafe {
let inline_destructor_size = core::mem::size_of::<LuaInlineDestructor>();
debug_assert!(self.total_payload_len() >= inline_destructor_size);
ptr::copy_nonoverlapping(
self.inline_destructor_slot(self.total_payload_len() - inline_destructor_size),
destructor.as_mut_ptr().cast::<u8>(),
inline_destructor_size,
);
destructor.assume_init()
}
}
pub unsafe fn allocation_size(&self) -> usize {
Self::size_userdata(self.total_payload_len())
}
}
impl crate::handle::sealed::Sealed for Userdata {}
impl RawHandle for Userdata {
type Raw = RawUserdata;
fn as_ptr(&self) -> *mut Self::Raw {
self.raw.as_ptr()
}
}
impl AsRef<Userdata> for Userdata {
fn as_ref(&self) -> &Userdata {
self
}
}
#[repr(C)]
pub struct LuaUserdataDirectAccessData {
pub index_tm: RawTValue,
pub new_index_tm: RawTValue,
pub name_call_tm: RawTValue,
pub index: Option<LuaUserdataDirectAccess>,
pub new_index: Option<LuaUserdataDirectAccess>,
pub name_call: Option<LuaUserdataDirectNamecall>,
}
impl GlobalState {
pub fn userdata_direct_field(&self, tag: usize) -> Option<Table> {
unsafe {
Some(Table::from_raw(NonNull::new(
self.as_ptr()
.as_ref()
.unwrap_unchecked()
.userdata_direct_fields[tag],
)?))
}
}
pub fn set_userdata_direct_field(&self, tag: usize, table: Option<Table>) {
unsafe {
self.as_ptr()
.as_mut()
.unwrap_unchecked()
.userdata_direct_fields[tag] =
table.map_or(core::ptr::null_mut(), |table| table.as_ptr());
}
}
}
#[allow(
clippy::missing_safety_doc,
reason = "all methods share the capability-level safety contract"
)]
pub trait UserdataRuntime: Sealed {
unsafe fn new_userdata_tagged_internal(&self, size: usize, tag: i32) -> VmErrorResult<*mut ()>;
unsafe fn new_userdata_internal(&self, size: usize, tag: i32) -> VmErrorResult<Userdata>;
unsafe fn free_userdata(&self, userdata: Userdata, page: LuaPage);
}
pub const LIGHT_USERDATA_TAG_LIMIT: usize = 128;
pub const USERDATA_TAG_LIMIT: usize = 128;
pub(crate) const USERDATA_TAG_IDTOR: usize = USERDATA_TAG_LIMIT;
pub(crate) const USERDATA_TAG_PROXY: usize = USERDATA_TAG_IDTOR + 1;
pub(crate) const USERDATA_INTERNAL_LIMIT: usize = USERDATA_TAG_PROXY + 1;
pub type LuaDestructor = fn(&Thread, *mut ());
pub type LuaUserdataMark = fn(&Thread, *mut ());
pub type LuaInlineDestructor = fn(*mut ());
pub type LuaUserdataDirectAccess = fn(&Thread, *mut (), i32, *mut u16, i32) -> VmErrorResult;
pub type LuaUserdataDirectNamecall = fn(&Thread, *mut (), i32, *mut u16, i32) -> NativeCallResult;
pub type LuaUserdataDirectFieldGet = fn(*mut (), &mut UserdataDirectFieldResult);
impl GlobalState {
pub fn userdata_metatable(&self, tag: usize) -> Option<Table> {
unsafe {
Some(Table::from_raw(NonNull::new(
self.as_ptr().as_ref().unwrap_unchecked().userdata_mt[tag],
)?))
}
}
pub fn set_userdata_metatable(&self, tag: usize, table: Option<Table>) {
unsafe {
(*self.as_ptr()).userdata_mt[tag] =
table.map_or(core::ptr::null_mut(), |table| table.as_ptr());
}
}
pub fn userdata_dtor(&self, tag: usize) -> Option<LuaDestructor> {
unsafe { (*self.as_ptr()).userdata_gc[tag] }
}
pub fn set_userdata_dtor(&self, tag: usize, destructor: Option<LuaDestructor>) {
unsafe {
(*self.as_ptr()).userdata_gc[tag] = destructor;
}
}
pub fn userdata_mark(&self, tag: usize) -> Option<LuaUserdataMark> {
unsafe { (*self.as_ptr()).userdata_mark[tag] }
}
pub fn set_userdata_mark(&self, tag: usize, mark: Option<LuaUserdataMark>) {
unsafe {
(*self.as_ptr()).userdata_mark[tag] = mark;
}
}
pub fn light_userdata_name(&self, tag: usize) -> Option<TString> {
unsafe {
NonNull::new((*self.as_ptr()).light_userdata_name[tag])
.map(|raw| TString::from_raw(raw))
}
}
pub fn set_light_userdata_name(&self, tag: usize, name: Option<TString>) {
unsafe {
(*self.as_ptr()).light_userdata_name[tag] =
name.map_or(core::ptr::null_mut(), |name| name.as_ptr());
}
}
}
#[repr(transparent)]
pub struct UserdataDirectFieldResult(pub(crate) TValue);
impl UserdataRuntime for Thread {
unsafe fn new_userdata_tagged_internal(&self, size: usize, tag: i32) -> VmErrorResult<*mut ()> {
assert!((tag as u32) < USERDATA_INTERNAL_LIMIT as u32);
unsafe {
self.check_gc()?;
self.thread_barrier();
self.ensure_stack(self, 1)?;
let userdata = self.new_userdata_internal(size, tag)?;
let data = userdata.data_mut_ptr();
let top = self.stack_top();
top.value_unchecked().set_userdata_value(userdata);
debug_assert!(top < self.current_call_info().top());
self.set_stack_top(top.add(1));
Ok(data.cast())
}
}
unsafe fn new_userdata_internal(
&self,
payload_len: usize,
tag: i32,
) -> VmErrorResult<Userdata> {
let Some(allocation_size) = Userdata::allocation_size_for_payload_len(payload_len) else {
return unsafe { self.too_big() };
};
unsafe {
let userdata = self.new_gco::<Userdata>(
allocation_size,
self.as_ptr().as_ref().unwrap_unchecked().active_memcat,
)?;
GcObject::from(userdata).init_header(self, LUA_TUSERDATA as u8);
userdata.as_ptr().as_mut().unwrap_unchecked().len = payload_len as i32;
userdata.set_metatable(None);
assert!((tag as u32) < USERDATA_INTERNAL_LIMIT as u32);
userdata.as_ptr().as_mut().unwrap_unchecked().tag = tag as u8;
Ok(userdata)
}
}
unsafe fn free_userdata(&self, userdata: Userdata, page: LuaPage) {
let userdata_ref = unsafe { userdata.as_ptr().as_ref().unwrap_unchecked() };
let tag = userdata_ref.tag;
let memcat = userdata_ref.memcat;
let data = unsafe { userdata.data_mut_ptr() };
unsafe {
if tag < USERDATA_TAG_LIMIT as u8 {
if let Some(dtor) = self.global().userdata_dtor(tag as usize) {
dtor(self, data.cast());
}
} else if tag == USERDATA_TAG_IDTOR as u8 {
let dtor = userdata.inline_destructor();
dtor(data.cast());
}
self.free_gco(userdata.into(), userdata.allocation_size(), memcat, page);
}
}
}
impl UserdataDirectFieldResult {
pub fn set_number(&mut self, value: f64) {
self.0.set_number(value);
}
pub fn set_vector(&mut self, value: [f32; crate::types::LUA_VECTOR_SIZE]) {
self.0.set_vector(value);
}
pub fn set_boolean(&mut self, value: i32) {
self.0.set_boolean(value);
}
pub fn set_integer64(&mut self, value: i64) {
self.0.set_integer(value);
}
pub fn set_nil(&mut self) {
self.0.set_nil();
}
}