use std::any::TypeId;
use std::marker::PhantomData;
use crate::runtime::heap::{Gc, GcHeader};
use crate::runtime::table::Table;
use crate::runtime::value::{NativeFn, Value};
use crate::vm::error::LuaError;
use crate::vm::exec::Vm;
use crate::vm::typed_native::{FromLuaArgs, IntoLuaReturn};
pub struct UserdataMarker<'a> {
inner: &'a mut crate::runtime::heap::Marker,
}
impl<'a> UserdataMarker<'a> {
#[doc(hidden)]
pub(crate) fn __new_internal(inner: &'a mut crate::runtime::heap::Marker) -> Self {
UserdataMarker { inner }
}
pub fn mark<T>(&mut self, g: Gc<T>) -> bool {
self.inner.header(g.as_ptr() as *mut GcHeader)
}
pub fn mark_value(&mut self, v: Value) -> bool {
self.inner.value(v)
}
}
#[non_exhaustive]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum MetaMethod {
Add,
Sub,
Mul,
Div,
Mod,
Pow,
IDiv,
BAnd,
BOr,
BXor,
Shl,
Shr,
BNot,
Unm,
Concat,
Len,
Eq,
Lt,
Le,
Index,
NewIndex,
Call,
ToString,
Pairs,
Close,
Gc,
}
impl MetaMethod {
pub const fn name(self) -> &'static str {
match self {
MetaMethod::Add => "__add",
MetaMethod::Sub => "__sub",
MetaMethod::Mul => "__mul",
MetaMethod::Div => "__div",
MetaMethod::Mod => "__mod",
MetaMethod::Pow => "__pow",
MetaMethod::IDiv => "__idiv",
MetaMethod::BAnd => "__band",
MetaMethod::BOr => "__bor",
MetaMethod::BXor => "__bxor",
MetaMethod::Shl => "__shl",
MetaMethod::Shr => "__shr",
MetaMethod::BNot => "__bnot",
MetaMethod::Unm => "__unm",
MetaMethod::Concat => "__concat",
MetaMethod::Len => "__len",
MetaMethod::Eq => "__eq",
MetaMethod::Lt => "__lt",
MetaMethod::Le => "__le",
MetaMethod::Index => "__index",
MetaMethod::NewIndex => "__newindex",
MetaMethod::Call => "__call",
MetaMethod::ToString => "__tostring",
MetaMethod::Pairs => "__pairs",
MetaMethod::Close => "__close",
MetaMethod::Gc => "__gc",
}
}
}
pub trait LuaUserdata: 'static + Sized {
fn type_name() -> &'static str {
std::any::type_name::<Self>()
}
fn add_methods<M: UserdataMethods<Self>>(_m: &mut M) {}
fn trace(&self, _m: &mut UserdataMarker) {}
}
pub trait UserdataMethods<T> {
fn add_method<F, A, R>(&mut self, name: &str, f: F)
where
F: Fn(&mut Vm, &T, A) -> Result<R, LuaError> + Copy + 'static,
A: FromLuaArgs + 'static,
R: IntoLuaReturn + 'static;
fn add_method_mut<F, A, R>(&mut self, name: &str, f: F)
where
F: Fn(&mut Vm, &mut T, A) -> Result<R, LuaError> + Copy + 'static,
A: FromLuaArgs + 'static,
R: IntoLuaReturn + 'static;
fn add_function<F, A, R>(&mut self, name: &str, f: F)
where
F: Fn(&mut Vm, A) -> Result<R, LuaError> + Copy + 'static,
A: FromLuaArgs + 'static,
R: IntoLuaReturn + 'static;
fn add_meta_method<F, A, R>(&mut self, meta: MetaMethod, f: F)
where
F: Fn(&mut Vm, &T, A) -> Result<R, LuaError> + Copy + 'static,
A: FromLuaArgs + 'static,
R: IntoLuaReturn + 'static;
fn add_meta_method_mut<F, A, R>(&mut self, meta: MetaMethod, f: F)
where
F: Fn(&mut Vm, &mut T, A) -> Result<R, LuaError> + Copy + 'static,
A: FromLuaArgs + 'static,
R: IntoLuaReturn + 'static;
fn add_field_method_get<F, R>(&mut self, name: &str, f: F)
where
F: Fn(&mut Vm, &T) -> Result<R, LuaError> + Copy + 'static,
R: IntoLuaReturn + 'static;
fn add_field_method_set<F, A>(&mut self, name: &str, f: F)
where
F: Fn(&mut Vm, &mut T, A) -> Result<(), LuaError> + Copy + 'static,
A: FromLuaArgs + 'static;
}
pub struct MetatableBuilder<'vm, T> {
vm: &'vm mut Vm,
methods: Vec<(Gc<crate::runtime::LuaStr>, Value)>,
fields_get: Vec<(Gc<crate::runtime::LuaStr>, Value)>,
fields_set: Vec<(Gc<crate::runtime::LuaStr>, Value)>,
meta_entries: Vec<(Gc<crate::runtime::LuaStr>, Value)>,
_phantom: PhantomData<fn() -> T>,
}
impl<'vm, T: LuaUserdata> MetatableBuilder<'vm, T> {
fn new(vm: &'vm mut Vm) -> Self {
Self {
vm,
methods: Vec::new(),
fields_get: Vec::new(),
fields_set: Vec::new(),
meta_entries: Vec::new(),
_phantom: PhantomData,
}
}
fn intern(&mut self, s: &str) -> Gc<crate::runtime::LuaStr> {
self.vm.heap.intern(s.as_bytes())
}
fn make_native(&mut self, f: NativeFn, upvals: Box<[Value]>) -> Value {
self.vm.native_with(f, upvals)
}
fn finalize(self) -> Result<Gc<Table>, LuaError> {
let MetatableBuilder {
vm,
methods,
fields_get,
fields_set,
meta_entries,
..
} = self;
let mt = vm.heap.new_table();
let name_key = vm.heap.intern(b"__name");
let type_name_str = vm.heap.intern(T::type_name().as_bytes());
let name_val = Value::Str(type_name_str);
unsafe { mt.as_mut() }.set(&mut vm.heap, Value::Str(name_key), name_val)?;
let mk_bucket = |vm: &mut Vm,
entries: Vec<(Gc<crate::runtime::LuaStr>, Value)>|
-> Result<Option<Gc<Table>>, LuaError> {
if entries.is_empty() {
return Ok(None);
}
let t = vm.heap.new_table();
for (k, v) in entries {
unsafe { t.as_mut() }.set(&mut vm.heap, Value::Str(k), v)?;
}
Ok(Some(t))
};
if fields_get.is_empty() {
if let Some(idx) = mk_bucket(vm, methods)? {
let key = vm.heap.intern(b"__index");
unsafe { mt.as_mut() }.set(&mut vm.heap, Value::Str(key), Value::Table(idx))?;
}
} else {
let methods_val = match mk_bucket(vm, methods)? {
Some(t) => Value::Table(t),
None => Value::Nil,
};
let fields_val =
Value::Table(mk_bucket(vm, fields_get)?.expect("fields_get non-empty checked"));
let upvals: Box<[Value]> = Box::new([methods_val, fields_val]);
let trampoline = vm.native_with(index_trampoline, upvals);
let key = vm.heap.intern(b"__index");
unsafe { mt.as_mut() }.set(&mut vm.heap, Value::Str(key), trampoline)?;
}
if !fields_set.is_empty() {
let setters_tbl = mk_bucket(vm, fields_set)?.expect("fields_set non-empty checked");
let upvals: Box<[Value]> =
Box::new([Value::Table(setters_tbl), Value::Str(type_name_str)]);
let trampoline = vm.native_with(newindex_trampoline, upvals);
let key = vm.heap.intern(b"__newindex");
unsafe { mt.as_mut() }.set(&mut vm.heap, Value::Str(key), trampoline)?;
}
for (k, v) in meta_entries {
unsafe { mt.as_mut() }.set(&mut vm.heap, Value::Str(k), v)?;
}
vm.heap
.barrier_back(mt.as_ptr() as *mut crate::runtime::heap::GcHeader);
Ok(mt)
}
}
impl<'vm, T: LuaUserdata> UserdataMethods<T> for MetatableBuilder<'vm, T> {
fn add_method<F, A, R>(&mut self, name: &str, f: F)
where
F: Fn(&mut Vm, &T, A) -> Result<R, LuaError> + Copy + 'static,
A: FromLuaArgs + 'static,
R: IntoLuaReturn + 'static,
{
let (raw_fn, upvals) = pack_method::<T, F, A, R>(f);
let v = self.make_native(raw_fn, upvals);
let k = self.intern(name);
self.methods.push((k, v));
}
fn add_method_mut<F, A, R>(&mut self, name: &str, f: F)
where
F: Fn(&mut Vm, &mut T, A) -> Result<R, LuaError> + Copy + 'static,
A: FromLuaArgs + 'static,
R: IntoLuaReturn + 'static,
{
let (raw_fn, upvals) = pack_method_mut::<T, F, A, R>(f);
let v = self.make_native(raw_fn, upvals);
let k = self.intern(name);
self.methods.push((k, v));
}
fn add_function<F, A, R>(&mut self, name: &str, f: F)
where
F: Fn(&mut Vm, A) -> Result<R, LuaError> + Copy + 'static,
A: FromLuaArgs + 'static,
R: IntoLuaReturn + 'static,
{
let (raw_fn, upvals) = pack_function::<F, A, R>(f);
let v = self.make_native(raw_fn, upvals);
let k = self.intern(name);
self.meta_entries.push((k, v));
}
fn add_meta_method<F, A, R>(&mut self, meta: MetaMethod, f: F)
where
F: Fn(&mut Vm, &T, A) -> Result<R, LuaError> + Copy + 'static,
A: FromLuaArgs + 'static,
R: IntoLuaReturn + 'static,
{
let (raw_fn, upvals) = pack_method::<T, F, A, R>(f);
let v = self.make_native(raw_fn, upvals);
let k = self.intern(meta.name());
self.meta_entries.push((k, v));
}
fn add_meta_method_mut<F, A, R>(&mut self, meta: MetaMethod, f: F)
where
F: Fn(&mut Vm, &mut T, A) -> Result<R, LuaError> + Copy + 'static,
A: FromLuaArgs + 'static,
R: IntoLuaReturn + 'static,
{
let (raw_fn, upvals) = pack_method_mut::<T, F, A, R>(f);
let v = self.make_native(raw_fn, upvals);
let k = self.intern(meta.name());
self.meta_entries.push((k, v));
}
fn add_field_method_get<F, R>(&mut self, name: &str, f: F)
where
F: Fn(&mut Vm, &T) -> Result<R, LuaError> + Copy + 'static,
R: IntoLuaReturn + 'static,
{
let adapter = move |vm: &mut Vm, this: &T, _args: ()| f(vm, this);
let (raw_fn, upvals) = pack_method::<T, _, (), R>(adapter);
let v = self.make_native(raw_fn, upvals);
let k = self.intern(name);
self.fields_get.push((k, v));
}
fn add_field_method_set<F, A>(&mut self, name: &str, f: F)
where
F: Fn(&mut Vm, &mut T, A) -> Result<(), LuaError> + Copy + 'static,
A: FromLuaArgs + 'static,
{
let (raw_fn, upvals) = pack_method_mut::<T, F, A, ()>(f);
let v = self.make_native(raw_fn, upvals);
let k = self.intern(name);
self.fields_set.push((k, v));
}
}
fn method_trampoline<T, F, A, R>(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError>
where
T: LuaUserdata,
F: Fn(&mut Vm, &T, A) -> Result<R, LuaError> + Copy + 'static,
A: FromLuaArgs + 'static,
R: IntoLuaReturn + 'static,
{
let f: F = reconstruct_zst_or_fnptr(vm, fs);
let self_val = vm.nat_arg(fs, nargs, 0);
let ud_gc = match self_val {
Value::Userdata(g) => g,
_ => {
return Err(vm.rt_err(&format!(
"method called on non-userdata value (expected {})",
T::type_name()
)));
}
};
let ud_ptr = ud_gc.as_ptr();
let type_matches = unsafe { (*ud_ptr).downcast::<T>().is_some() };
if !type_matches {
return Err(vm.rt_err(&format!(
"method called on wrong userdata type (expected {})",
T::type_name()
)));
}
let args = A::from_lua_args_skip_self(vm, fs, nargs)?;
let this: &T = unsafe { (*ud_ptr).downcast::<T>().unwrap_unchecked() };
f(vm, this, args).into_lua_return(vm, fs)
}
fn method_mut_trampoline<T, F, A, R>(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError>
where
T: LuaUserdata,
F: Fn(&mut Vm, &mut T, A) -> Result<R, LuaError> + Copy + 'static,
A: FromLuaArgs + 'static,
R: IntoLuaReturn + 'static,
{
let f: F = reconstruct_zst_or_fnptr(vm, fs);
let self_val = vm.nat_arg(fs, nargs, 0);
let ud_gc = match self_val {
Value::Userdata(g) => g,
_ => {
return Err(vm.rt_err(&format!(
"method called on non-userdata value (expected {})",
T::type_name()
)));
}
};
let ud_ptr = ud_gc.as_ptr();
let type_matches = unsafe { (*ud_ptr).downcast::<T>().is_some() };
if !type_matches {
return Err(vm.rt_err(&format!(
"method called on wrong userdata type (expected {})",
T::type_name()
)));
}
let args = A::from_lua_args_skip_self(vm, fs, nargs)?;
let this: &mut T = unsafe { (*ud_ptr).downcast_mut::<T>().unwrap_unchecked() };
f(vm, this, args).into_lua_return(vm, fs)
}
fn function_trampoline<F, A, R>(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError>
where
F: Fn(&mut Vm, A) -> Result<R, LuaError> + Copy + 'static,
A: FromLuaArgs + 'static,
R: IntoLuaReturn + 'static,
{
let f: F = reconstruct_zst_or_fnptr(vm, fs);
let args = A::from_lua_args(vm, fs, nargs)?;
f(vm, args).into_lua_return(vm, fs)
}
fn index_trampoline(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
let methods_upval = vm.nat_upval(fs, 0);
let fields_upval = vm.nat_upval(fs, 1);
let self_val = vm.nat_arg(fs, nargs, 0);
let key = vm.nat_arg(fs, nargs, 1);
if let Value::Table(m) = methods_upval {
let v = m.get(key);
if !v.is_nil() {
return Ok(vm.nat_return(fs, &[v]));
}
}
if let Value::Table(g) = fields_upval {
let getter = g.get(key);
if !getter.is_nil() {
let mut results = vm.call_value(getter, &[self_val])?;
let r = if results.is_empty() {
Value::Nil
} else {
results.swap_remove(0)
};
return Ok(vm.nat_return(fs, &[r]));
}
}
Ok(vm.nat_return(fs, &[Value::Nil]))
}
fn newindex_trampoline(vm: &mut Vm, fs: u32, nargs: u32) -> Result<u32, LuaError> {
let setters_upval = vm.nat_upval(fs, 0);
let type_name_upval = vm.nat_upval(fs, 1);
let self_val = vm.nat_arg(fs, nargs, 0);
let key = vm.nat_arg(fs, nargs, 1);
let value = vm.nat_arg(fs, nargs, 2);
if let Value::Table(s) = setters_upval {
let setter = s.get(key);
if !setter.is_nil() {
vm.call_value(setter, &[self_val, value])?;
return Ok(vm.nat_return(fs, &[]));
}
}
let key_str = match key {
Value::Str(s) => std::str::from_utf8(s.as_bytes())
.unwrap_or("<non-utf8>")
.to_string(),
other => format!("{:?}", other),
};
let type_str = match type_name_upval {
Value::Str(s) => std::str::from_utf8(s.as_bytes())
.unwrap_or("<non-utf8>")
.to_string(),
_ => "userdata".to_string(),
};
Err(vm.rt_err(&format!(
"attempt to write unknown field '{}' on {} (no setter registered)",
key_str, type_str
)))
}
fn pack_method<T, F, A, R>(f: F) -> (NativeFn, Box<[Value]>)
where
T: LuaUserdata,
F: Fn(&mut Vm, &T, A) -> Result<R, LuaError> + Copy + 'static,
A: FromLuaArgs + 'static,
R: IntoLuaReturn + 'static,
{
(method_trampoline::<T, F, A, R>, pack_zst_or_fnptr::<F>(f))
}
fn pack_method_mut<T, F, A, R>(f: F) -> (NativeFn, Box<[Value]>)
where
T: LuaUserdata,
F: Fn(&mut Vm, &mut T, A) -> Result<R, LuaError> + Copy + 'static,
A: FromLuaArgs + 'static,
R: IntoLuaReturn + 'static,
{
(
method_mut_trampoline::<T, F, A, R>,
pack_zst_or_fnptr::<F>(f),
)
}
fn pack_function<F, A, R>(f: F) -> (NativeFn, Box<[Value]>)
where
F: Fn(&mut Vm, A) -> Result<R, LuaError> + Copy + 'static,
A: FromLuaArgs + 'static,
R: IntoLuaReturn + 'static,
{
(function_trampoline::<F, A, R>, pack_zst_or_fnptr::<F>(f))
}
#[inline]
fn pack_zst_or_fnptr<F: Copy + 'static>(f: F) -> Box<[Value]> {
if std::mem::size_of::<F>() == 0 {
Box::new([])
} else {
assert!(
std::mem::size_of::<F>() == std::mem::size_of::<*const ()>(),
"LuaUserdata method closure must be ZST (non-capturing) or fn-pointer-sized; \
capturing closures unsupported in v1.2"
);
let raw_ptr: *const () = unsafe { std::mem::transmute_copy(&f) };
Box::new([Value::LightUserdata(raw_ptr)])
}
}
#[inline]
fn reconstruct_zst_or_fnptr<F: Copy + 'static>(vm: &Vm, fs: u32) -> F {
if std::mem::size_of::<F>() == 0 {
#[allow(clippy::uninit_assumed_init)]
unsafe {
std::mem::MaybeUninit::<F>::uninit().assume_init()
}
} else {
let upval = vm.nat_upval(fs, 0);
match upval {
Value::LightUserdata(ptr) => {
debug_assert_eq!(
std::mem::size_of::<F>(),
std::mem::size_of::<*const ()>(),
"non-ZST F must be fn-pointer-sized"
);
unsafe { std::mem::transmute_copy::<*const (), F>(&ptr) }
}
_ => unreachable!("LuaUserdata method upval shape corrupted"),
}
}
}
impl Vm {
pub fn register_userdata<T: LuaUserdata>(&mut self) -> Result<Gc<Table>, LuaError> {
let tid = TypeId::of::<T>();
if let Some(&mt) = self.userdata_metatables.get(&tid) {
return Ok(mt);
}
let mut builder = MetatableBuilder::<T>::new(self);
T::add_methods(&mut builder);
let mt = builder.finalize()?;
self.userdata_metatables.insert(tid, mt);
self.pin_host(Value::Table(mt));
Ok(mt)
}
}