use core::ptr;
use luau_common::{ByteSlice, flags};
use crate::VmErrorResult;
use crate::gc::GcObject;
use crate::gc::{GcBarrier, GcRuntime};
use crate::handle::RawHandle;
use crate::metamethod::{MetamethodRuntime, TmEvent};
use crate::state::{EmbedderGc, ThreadState};
use crate::string::LuaString;
use crate::string::StringRuntime;
use crate::table::TableRuntime;
use crate::userdata::UserdataRuntime;
use crate::userdata::{
LIGHT_USERDATA_TAG_LIMIT, LuaDestructor, LuaInlineDestructor, LuaUserdataDirectAccess,
LuaUserdataDirectFieldGet, LuaUserdataDirectNamecall, LuaUserdataMark, USERDATA_TAG_LIMIT,
};
use crate::value::{TValue, nil_object};
use super::Thread;
use super::stack::RawStackAccess;
impl Thread {
pub unsafe fn new_userdata_tagged(&self, size: usize, tag: i32) -> VmErrorResult<*mut ()> {
assert!((tag as u32) < crate::userdata::USERDATA_TAG_LIMIT as u32);
unsafe { self.new_userdata_tagged_internal(size, tag) }
}
pub unsafe fn new_userdata_tagged_with_metatable(
&self,
size: usize,
tag: i32,
) -> VmErrorResult<*mut ()> {
assert!((tag as u32) < crate::userdata::USERDATA_TAG_LIMIT as u32);
unsafe {
self.check_gc()?;
self.thread_barrier();
self.ensure_stack(self, 1)?;
let userdata = self.new_userdata_internal(size, tag)?;
debug_assert!(!GcObject::from(userdata).is_black());
let metatable = self.global().userdata_metatable(tag as usize);
userdata.set_metatable(metatable);
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())
}
}
pub unsafe fn new_userdata_dtor(
&self,
size: usize,
destructor: LuaInlineDestructor,
) -> VmErrorResult<*mut ()> {
let payload_len = size.saturating_add(core::mem::size_of::<LuaInlineDestructor>());
unsafe {
self.check_gc()?;
self.thread_barrier();
self.ensure_stack(self, 1)?;
let userdata = self
.new_userdata_internal(payload_len, crate::userdata::USERDATA_TAG_IDTOR as i32)?;
userdata.set_inline_destructor(size, destructor);
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())
}
}
pub unsafe fn new_userdata(&self, size: usize) -> VmErrorResult<*mut ()> {
unsafe { self.new_userdata_tagged_internal(size, 0) }
}
pub unsafe fn to_light_userdata(&self, index: i32) -> *mut () {
let object = unsafe { self.index_to_addr(index) };
if object == nil_object() || !object.is_light_userdata() {
ptr::null_mut()
} else {
object.pointer_value()
}
}
pub unsafe fn to_light_userdata_tagged(&self, index: i32, tag: i32) -> *mut () {
let object = unsafe { self.index_to_addr(index) };
if object == nil_object()
|| !object.is_light_userdata()
|| object.light_userdata_tag() != tag
{
ptr::null_mut()
} else {
object.pointer_value()
}
}
pub unsafe fn to_userdata(&self, index: i32) -> *mut () {
let object = unsafe { self.index_to_addr(index) };
if object == nil_object() {
return ptr::null_mut();
}
if object.is_userdata() {
let userdata = object.userdata_value();
unsafe { userdata.data_mut_ptr().cast() }
} else if object.is_light_userdata() {
object.pointer_value()
} else {
ptr::null_mut()
}
}
pub unsafe fn to_userdata_tagged(&self, index: i32, tag: i32) -> *mut () {
let object = unsafe { self.index_to_addr(index) };
if object == nil_object() || !object.is_userdata() {
return ptr::null_mut();
}
let userdata = object.userdata_value();
if unsafe { userdata.as_ptr().as_ref().unwrap_unchecked().tag as i32 } == tag {
unsafe { userdata.data_mut_ptr().cast() }
} else {
ptr::null_mut()
}
}
pub unsafe fn userdata_tag(&self, index: i32) -> i32 {
let object = unsafe { self.index_to_addr(index) };
if object == nil_object() || !object.is_userdata() {
-1
} else {
unsafe {
object
.userdata_value()
.as_ptr()
.as_ref()
.unwrap_unchecked()
.tag as i32
}
}
}
pub unsafe fn light_userdata_tag(&self, index: i32) -> i32 {
let object = unsafe { self.index_to_addr(index) };
if object == nil_object() || !object.is_light_userdata() {
-1
} else {
object.light_userdata_tag()
}
}
pub unsafe fn set_userdata_tag(&self, index: i32, tag: i32) {
debug_assert!((tag as u32) < USERDATA_TAG_LIMIT as u32);
let object = unsafe { self.to_object(index).unwrap_unchecked() };
debug_assert!(object.is_userdata());
unsafe {
object
.userdata_value()
.as_ptr()
.as_mut()
.unwrap_unchecked()
.tag = tag as u8
};
}
pub unsafe fn set_userdata_dtor(&self, tag: i32, destructor: LuaDestructor) {
debug_assert!((tag as u32) < USERDATA_TAG_LIMIT as u32);
unsafe {
self.global()
.set_userdata_dtor(tag as usize, Some(destructor))
};
}
pub unsafe fn get_userdata_dtor(&self, tag: i32) -> Option<LuaDestructor> {
debug_assert!((tag as u32) < USERDATA_TAG_LIMIT as u32);
unsafe { self.global().userdata_dtor(tag as usize) }
}
pub unsafe fn set_userdata_mark(&self, tag: i32, mark: Option<LuaUserdataMark>) {
debug_assert!(flags::LuauGcTraceUdata.get());
debug_assert!((tag as u32) < USERDATA_TAG_LIMIT as u32);
unsafe { self.global().set_userdata_mark(tag as usize, mark) };
}
pub unsafe fn set_embedder_gc(&self, callback: Option<EmbedderGc>) {
debug_assert!(flags::LuauGcTraceUdata.get());
unsafe { self.global().set_embedder_gc(callback) };
}
pub unsafe fn set_userdata_metatable(&self, tag: i32) {
debug_assert!((tag as u32) < USERDATA_TAG_LIMIT as u32);
unsafe {
let top_cursor = self.stack_top();
debug_assert!(top_cursor.offset_from(self.stack_base()) > 0);
debug_assert!(self.global().userdata_metatable(tag as usize).is_none());
let top = top_cursor.sub(1).value_unchecked();
debug_assert!(top.is_table());
self.global()
.set_userdata_metatable(tag as usize, Some(top.table_value()));
self.set_stack_top(top_cursor.sub(1));
}
}
pub unsafe fn get_userdata_metatable(&self, tag: i32) -> VmErrorResult {
unsafe {
debug_assert!((tag as u32) < USERDATA_TAG_LIMIT as u32);
self.thread_barrier();
self.ensure_stack(self, 1)?;
let top = self.stack_top();
if let Some(metatable) = self.global().userdata_metatable(tag as usize) {
top.value_unchecked().set_table_value(metatable);
} else {
top.value_unchecked().set_nil();
}
debug_assert!(top < self.current_call_info().top());
self.set_stack_top(top.add(1));
}
Ok(())
}
pub unsafe fn get_userdata_name(&self, tag: i32) -> LuaString {
debug_assert!((tag as u32) < USERDATA_TAG_LIMIT as u32);
unsafe {
let global = self.global();
if let Some(metatable) = global.userdata_metatable(tag as usize) {
let value = metatable.get_str(global.tm_name(TmEvent::Type as usize));
if value.is_string() {
return LuaString::from_interned(value.string_value());
}
}
LuaString::from_interned(global.type_name(crate::types::LUA_TUSERDATA as usize))
}
}
pub unsafe fn get_light_userdata_name(&self, tag: i32) -> Option<LuaString> {
debug_assert!((tag as u32) < LIGHT_USERDATA_TAG_LIMIT as u32);
unsafe {
self.global()
.light_userdata_name(tag as usize)
.map(LuaString::from_interned)
}
}
pub unsafe fn register_userdata_direct_access(
&self,
tag: i32,
get: LuaUserdataDirectAccess,
set: LuaUserdataDirectAccess,
namecall: Option<LuaUserdataDirectNamecall>,
) -> i32 {
debug_assert!((tag as u32) < USERDATA_TAG_LIMIT as u32);
unsafe {
self.thread_barrier();
let global_handle = self.global();
let Some(table) = global_handle.userdata_metatable(tag as usize) else {
return 0;
};
let global = global_handle.as_ptr().as_mut().unwrap_unchecked();
let data = &mut global.userdata_direct[tag as usize];
TValue::from_mut(&mut data.index_tm).set_nil();
TValue::from_mut(&mut data.new_index_tm).set_nil();
TValue::from_mut(&mut data.name_call_tm).set_nil();
data.index = None;
data.new_index = None;
data.name_call = None;
if let Some(index_tm) = self.fast_tm(Some(table), TmEvent::Index) {
TValue::from_mut(&mut data.index_tm).set_obj(index_tm);
data.index = Some(get);
}
if let Some(new_index_tm) = self.fast_tm(Some(table), TmEvent::NewIndex) {
TValue::from_mut(&mut data.new_index_tm).set_obj(new_index_tm);
data.new_index = Some(set);
}
if let Some(name_call_tm) = self.fast_tm(Some(table), TmEvent::NameCall) {
TValue::from_mut(&mut data.name_call_tm).set_obj(name_call_tm);
data.name_call = namecall;
}
1
}
}
pub unsafe fn register_userdata_direct_field_get(
&self,
tag: i32,
field: &[u8],
get: LuaUserdataDirectFieldGet,
) -> VmErrorResult {
unsafe {
if !flags::LuauDirectFieldGet.get() {
return Ok(());
}
debug_assert!((tag as u32) < USERDATA_TAG_LIMIT as u32);
let global = self.global();
if global.userdata_direct_field(tag as usize).is_none() {
let table = self.new_table_internal(0, 1)?;
global.set_userdata_direct_field(tag as usize, Some(table));
}
let string = self.intern_string(field.as_bstr())?;
string.fix();
let table = global
.userdata_direct_field(tag as usize)
.unwrap_unchecked();
let node_cursor = self.set_str(table, string)?;
node_cursor
.node_unchecked()
.value_unchecked()
.set_light_userdata(get as *const () as *mut (), 0);
}
Ok(())
}
pub unsafe fn set_light_userdata_name(&self, tag: i32, name: &str) -> VmErrorResult {
unsafe {
debug_assert!((tag as u32) < LIGHT_USERDATA_TAG_LIMIT as u32);
debug_assert!(self.global().light_userdata_name(tag as usize).is_none());
let global = self.global();
if global.light_userdata_name(tag as usize).is_none() {
let string = self.intern_string(name.as_bytes().as_bstr())?;
string.fix();
global.set_light_userdata_name(tag as usize, Some(string));
}
}
Ok(())
}
}