use std::any::TypeId;
use std::cell::RefCell;
use std::marker::PhantomData;
use crate::callback::{create_callback_function, BoxedCallback};
use crate::error::{Error, Result};
use crate::state::{Lua, LuaRef};
use crate::sync::{MaybeSend, MaybeSync, NotSync, XRc, NOT_SYNC};
use crate::sys::*;
use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti};
use crate::value::Value;
pub trait UserData: Sized {
fn add_fields<F: UserDataFields<Self>>(_fields: &mut F) {}
fn add_methods<M: UserDataMethods<Self>>(_methods: &mut M) {}
}
pub trait UserDataMethods<T> {
fn add_method<M, A, R>(&mut self, name: impl Into<String>, method: M)
where
M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti;
fn add_method_mut<M, A, R>(&mut self, name: impl Into<String>, method: M)
where
M: Fn(&Lua, &mut T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti;
fn add_function<F, A, R>(&mut self, name: impl Into<String>, function: F)
where
F: Fn(&Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti;
fn add_meta_method<M, A, R>(&mut self, name: impl Into<String>, method: M)
where
M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti;
fn add_meta_method_mut<M, A, R>(&mut self, name: impl Into<String>, method: M)
where
M: Fn(&Lua, &mut T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti;
}
pub trait UserDataFields<T> {
fn add_field<V>(&mut self, name: impl Into<String>, value: V)
where
V: IntoLua + Clone + MaybeSend + 'static;
fn add_field_method_get<M, R>(&mut self, name: impl Into<String>, method: M)
where
M: Fn(&Lua, &T) -> Result<R> + MaybeSend + 'static,
R: IntoLua;
fn add_field_method_set<M, A>(&mut self, name: impl Into<String>, method: M)
where
M: Fn(&Lua, &mut T, A) -> Result<()> + MaybeSend + 'static,
A: FromLua;
fn add_field_function_get<F, R>(&mut self, name: impl Into<String>, function: F)
where
F: Fn(&Lua, AnyUserData) -> Result<R> + MaybeSend + 'static,
R: IntoLua;
fn add_field_function_set<F, A>(&mut self, name: impl Into<String>, function: F)
where
F: Fn(&Lua, AnyUserData, A) -> Result<()> + MaybeSend + 'static,
A: FromLua;
}
#[derive(Clone)]
pub struct AnyUserData {
pub(crate) reference: XRc<LuaRef>,
pub(crate) _not_sync: NotSync,
}
impl AnyUserData {
pub(crate) fn from_ref(reference: LuaRef) -> AnyUserData {
AnyUserData {
reference: XRc::new(reference),
_not_sync: NOT_SYNC,
}
}
pub(crate) unsafe fn push_to_stack(&self) {
self.reference.push();
}
pub fn lua(&self) -> Lua {
self.reference.lua()
}
pub fn to_pointer(&self) -> *const c_void {
let state = self.reference.state();
unsafe {
self.reference.push();
let p = lua_topointer(state, -1);
lua_pop(state, 1);
p
}
}
pub fn equals(&self, other: &AnyUserData) -> Result<bool> {
let lua = self.lua();
let state = lua.state();
unsafe {
self.reference.push();
other.reference.push();
let eq = lua_equal(state, -2, -1);
lua_pop(state, 2);
Ok(eq != 0)
}
}
fn cell<T: 'static>(&self) -> Result<&UserDataCell<T>> {
let state = self.reference.state();
unsafe {
self.reference.push();
let ptr = lua_touserdata(state, -1);
lua_pop(state, 1);
if ptr.is_null() {
return Err(Error::UserDataTypeMismatch);
}
let header = &*(ptr as *const UserDataHeader);
if header.type_id != TypeId::of::<T>() {
return Err(Error::UserDataTypeMismatch);
}
Ok(&*(ptr as *const UserDataCell<T>))
}
}
pub fn is<T: 'static>(&self) -> bool {
match self.cell::<T>() {
Ok(cell) => cell.cell.borrow().is_some(),
Err(_) => false,
}
}
pub fn type_id(&self) -> Option<TypeId> {
let state = self.reference.state();
unsafe {
self.reference.push();
let ptr = lua_touserdata(state, -1);
lua_pop(state, 1);
if ptr.is_null() {
return None;
}
let header = &*(ptr as *const UserDataHeader);
Some(header.type_id)
}
}
pub fn borrow<T: 'static>(&self) -> Result<UserDataRef<'_, T>> {
let cell = self.cell::<T>()?;
let guard = cell
.cell
.try_borrow()
.map_err(|_| Error::UserDataBorrowError)?;
if guard.is_none() {
return Err(Error::UserDataDestructed);
}
Ok(UserDataRef {
guard,
_marker: PhantomData,
})
}
pub fn borrow_mut<T: 'static>(&self) -> Result<UserDataRefMut<'_, T>> {
let cell = self.cell::<T>()?;
let guard = cell
.cell
.try_borrow_mut()
.map_err(|_| Error::UserDataBorrowMutError)?;
if guard.is_none() {
return Err(Error::UserDataDestructed);
}
Ok(UserDataRefMut {
guard,
_marker: PhantomData,
})
}
pub fn take<T: 'static>(&self) -> Result<T> {
let cell = self.cell::<T>()?;
let mut guard = cell
.cell
.try_borrow_mut()
.map_err(|_| Error::UserDataBorrowMutError)?;
guard.take().ok_or(Error::UserDataDestructed)
}
}
pub struct UserDataRef<'a, T> {
guard: std::cell::Ref<'a, Option<T>>,
_marker: PhantomData<T>,
}
impl<T> std::ops::Deref for UserDataRef<'_, T> {
type Target = T;
fn deref(&self) -> &T {
self.guard.as_ref().expect("userdata destructed")
}
}
impl<T: std::fmt::Debug> std::fmt::Debug for UserDataRef<'_, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Debug::fmt(&**self, f)
}
}
impl<T: std::fmt::Display> std::fmt::Display for UserDataRef<'_, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(&**self, f)
}
}
pub struct UserDataRefMut<'a, T> {
guard: std::cell::RefMut<'a, Option<T>>,
_marker: PhantomData<T>,
}
impl<T> std::ops::Deref for UserDataRefMut<'_, T> {
type Target = T;
fn deref(&self) -> &T {
self.guard.as_ref().expect("userdata destructed")
}
}
impl<T> std::ops::DerefMut for UserDataRefMut<'_, T> {
fn deref_mut(&mut self) -> &mut T {
self.guard.as_mut().expect("userdata destructed")
}
}
impl<T: std::fmt::Debug> std::fmt::Debug for UserDataRefMut<'_, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Debug::fmt(&**self, f)
}
}
impl<T: std::fmt::Display> std::fmt::Display for UserDataRefMut<'_, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Display::fmt(&**self, f)
}
}
impl std::fmt::Debug for AnyUserData {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "UserData")
}
}
impl PartialEq for AnyUserData {
fn eq(&self, other: &Self) -> bool {
self.to_pointer() == other.to_pointer()
}
}
impl<T: UserData + crate::sync::MaybeSend + crate::sync::MaybeSync + 'static> IntoLua for T {
fn into_lua(self, lua: &Lua) -> Result<Value> {
Ok(Value::UserData(lua.create_userdata(self)?))
}
}
impl IntoLua for AnyUserData {
fn into_lua(self, _lua: &Lua) -> Result<Value> {
Ok(Value::UserData(self))
}
}
impl IntoLua for &AnyUserData {
fn into_lua(self, _lua: &Lua) -> Result<Value> {
Ok(Value::UserData(self.clone()))
}
}
impl FromLua for AnyUserData {
fn from_lua(value: Value, _lua: &Lua) -> Result<Self> {
match value {
Value::UserData(ud) => Ok(ud),
other => Err(Error::FromLuaConversionError {
from: other.type_name(),
to: "AnyUserData".to_string(),
message: None,
}),
}
}
}
#[repr(C)]
struct UserDataHeader {
type_id: TypeId,
}
#[repr(C)]
struct UserDataCell<T> {
type_id: TypeId,
cell: RefCell<Option<T>>,
}
fn recover_cell<'a, T: 'static>(lua: &Lua, value: &Value) -> Result<&'a UserDataCell<T>> {
match value {
Value::UserData(ud) => {
let state = lua.state();
unsafe {
ud.reference.push();
let ptr = lua_touserdata(state, -1);
lua_pop(state, 1);
if ptr.is_null() {
return Err(Error::UserDataTypeMismatch);
}
let header = &*(ptr as *const UserDataHeader);
if header.type_id != TypeId::of::<T>() {
return Err(Error::UserDataTypeMismatch);
}
Ok(&*(ptr as *const UserDataCell<T>))
}
}
_ => Err(Error::UserDataTypeMismatch),
}
}
struct Registered {
name: String,
is_meta: bool,
callback: BoxedCallback,
}
struct FieldEntry {
name: String,
is_get: bool,
callback: BoxedCallback,
}
struct Collector<T> {
methods: Vec<Registered>,
fields: Vec<FieldEntry>,
_phantom: PhantomData<T>,
}
impl<T> Collector<T> {
fn new() -> Self {
Collector {
methods: Vec::new(),
fields: Vec::new(),
_phantom: PhantomData,
}
}
}
impl<T: 'static> UserDataMethods<T> for Collector<T> {
fn add_method<M, A, R>(&mut self, name: impl Into<String>, method: M)
where
M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
{
let callback: BoxedCallback = Box::new(move |lua, mut args| {
let this = args.pop_front().unwrap_or(Value::Nil);
let cell = recover_cell::<T>(lua, &this)?;
let a = A::from_lua_multi(args, lua)?;
let borrowed = cell
.cell
.try_borrow()
.map_err(|_| Error::UserDataBorrowError)?;
let data = borrowed.as_ref().ok_or(Error::UserDataDestructed)?;
let r = method(lua, data, a)?;
r.into_lua_multi(lua)
});
self.methods.push(Registered {
name: name.into(),
is_meta: false,
callback,
});
}
fn add_method_mut<M, A, R>(&mut self, name: impl Into<String>, method: M)
where
M: Fn(&Lua, &mut T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
{
let callback: BoxedCallback = Box::new(move |lua, mut args| {
let this = args.pop_front().unwrap_or(Value::Nil);
let cell = recover_cell::<T>(lua, &this)?;
let a = A::from_lua_multi(args, lua)?;
let mut borrowed = cell
.cell
.try_borrow_mut()
.map_err(|_| Error::UserDataBorrowMutError)?;
let data = borrowed.as_mut().ok_or(Error::UserDataDestructed)?;
let r = method(lua, data, a)?;
r.into_lua_multi(lua)
});
self.methods.push(Registered {
name: name.into(),
is_meta: false,
callback,
});
}
fn add_function<F, A, R>(&mut self, name: impl Into<String>, function: F)
where
F: Fn(&Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
{
let callback: BoxedCallback = Box::new(move |lua, args| {
let a = A::from_lua_multi(args, lua)?;
let r = function(lua, a)?;
r.into_lua_multi(lua)
});
self.methods.push(Registered {
name: name.into(),
is_meta: false,
callback,
});
}
fn add_meta_method<M, A, R>(&mut self, name: impl Into<String>, method: M)
where
M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
{
let callback: BoxedCallback = Box::new(move |lua, mut args| {
let this = args.pop_front().unwrap_or(Value::Nil);
let cell = recover_cell::<T>(lua, &this)?;
let a = A::from_lua_multi(args, lua)?;
let borrowed = cell
.cell
.try_borrow()
.map_err(|_| Error::UserDataBorrowError)?;
let data = borrowed.as_ref().ok_or(Error::UserDataDestructed)?;
let r = method(lua, data, a)?;
r.into_lua_multi(lua)
});
self.methods.push(Registered {
name: name.into(),
is_meta: true,
callback,
});
}
fn add_meta_method_mut<M, A, R>(&mut self, name: impl Into<String>, method: M)
where
M: Fn(&Lua, &mut T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
{
let callback: BoxedCallback = Box::new(move |lua, mut args| {
let this = args.pop_front().unwrap_or(Value::Nil);
let cell = recover_cell::<T>(lua, &this)?;
let a = A::from_lua_multi(args, lua)?;
let mut borrowed = cell
.cell
.try_borrow_mut()
.map_err(|_| Error::UserDataBorrowMutError)?;
let data = borrowed.as_mut().ok_or(Error::UserDataDestructed)?;
let r = method(lua, data, a)?;
r.into_lua_multi(lua)
});
self.methods.push(Registered {
name: name.into(),
is_meta: true,
callback,
});
}
}
impl<T: 'static> UserDataFields<T> for Collector<T> {
fn add_field<V>(&mut self, name: impl Into<String>, value: V)
where
V: IntoLua + Clone + MaybeSend + 'static,
{
let callback: BoxedCallback = Box::new(move |lua, _args| {
let v = value.clone().into_lua(lua)?;
v.into_lua_multi(lua)
});
self.fields.push(FieldEntry {
name: name.into(),
is_get: true,
callback,
});
}
fn add_field_method_get<M, R>(&mut self, name: impl Into<String>, method: M)
where
M: Fn(&Lua, &T) -> Result<R> + MaybeSend + 'static,
R: IntoLua,
{
let callback: BoxedCallback = Box::new(move |lua, mut args| {
let this = args.pop_front().unwrap_or(Value::Nil);
let cell = recover_cell::<T>(lua, &this)?;
let borrowed = cell
.cell
.try_borrow()
.map_err(|_| Error::UserDataBorrowError)?;
let data = borrowed.as_ref().ok_or(Error::UserDataDestructed)?;
let r = method(lua, data)?;
r.into_lua_multi(lua)
});
self.fields.push(FieldEntry {
name: name.into(),
is_get: true,
callback,
});
}
fn add_field_method_set<M, A>(&mut self, name: impl Into<String>, method: M)
where
M: Fn(&Lua, &mut T, A) -> Result<()> + MaybeSend + 'static,
A: FromLua,
{
let callback: BoxedCallback = Box::new(move |lua, mut args| {
let this = args.pop_front().unwrap_or(Value::Nil);
let cell = recover_cell::<T>(lua, &this)?;
let val = A::from_lua(args.pop_front().unwrap_or(Value::Nil), lua)?;
let mut borrowed = cell
.cell
.try_borrow_mut()
.map_err(|_| Error::UserDataBorrowMutError)?;
let data = borrowed.as_mut().ok_or(Error::UserDataDestructed)?;
method(lua, data, val)?;
().into_lua_multi(lua)
});
self.fields.push(FieldEntry {
name: name.into(),
is_get: false,
callback,
});
}
fn add_field_function_get<F, R>(&mut self, name: impl Into<String>, function: F)
where
F: Fn(&Lua, AnyUserData) -> Result<R> + MaybeSend + 'static,
R: IntoLua,
{
let callback: BoxedCallback = Box::new(move |lua, mut args| {
let this = args.pop_front().unwrap_or(Value::Nil);
let ud = AnyUserData::from_lua(this, lua)?;
let r = function(lua, ud)?;
r.into_lua_multi(lua)
});
self.fields.push(FieldEntry {
name: name.into(),
is_get: true,
callback,
});
}
fn add_field_function_set<F, A>(&mut self, name: impl Into<String>, function: F)
where
F: Fn(&Lua, AnyUserData, A) -> Result<()> + MaybeSend + 'static,
A: FromLua,
{
let callback: BoxedCallback = Box::new(move |lua, mut args| {
let this = args.pop_front().unwrap_or(Value::Nil);
let ud = AnyUserData::from_lua(this, lua)?;
let val = A::from_lua(args.pop_front().unwrap_or(Value::Nil), lua)?;
function(lua, ud, val)?;
().into_lua_multi(lua)
});
self.fields.push(FieldEntry {
name: name.into(),
is_get: false,
callback,
});
}
}
unsafe extern "C" fn userdata_dtor<T>(ptr: *mut c_void) {
if !ptr.is_null() {
unsafe { core::ptr::drop_in_place(ptr as *mut UserDataCell<T>) };
}
}
use std::sync::atomic::{AtomicU64, Ordering};
static SCOPED_MARKER: AtomicU64 = AtomicU64::new(1);
fn next_scoped_marker() -> u64 {
SCOPED_MARKER.fetch_add(1, Ordering::Relaxed)
}
#[repr(C)]
struct ScopedHeader {
marker: u64,
}
#[repr(C)]
struct ScopedCell<T> {
marker: u64,
cell: RefCell<Option<T>>,
}
unsafe extern "C" fn scoped_userdata_dtor<T>(ptr: *mut c_void) {
if !ptr.is_null() {
unsafe { core::ptr::drop_in_place(ptr as *mut ScopedCell<T>) };
}
}
unsafe fn recover_scoped_cell<'a, T>(
lua: &Lua,
value: &Value,
marker: u64,
) -> Result<&'a ScopedCell<T>> {
match value {
Value::UserData(ud) => {
let state = lua.state();
unsafe {
ud.reference.push();
let ptr = lua_touserdata(state, -1);
lua_pop(state, 1);
if ptr.is_null() {
return Err(Error::UserDataTypeMismatch);
}
let header = &*(ptr as *const ScopedHeader);
if header.marker != marker {
return Err(Error::UserDataTypeMismatch);
}
Ok(&*(ptr as *const ScopedCell<T>))
}
}
_ => Err(Error::UserDataTypeMismatch),
}
}
struct ScopedCollector<T> {
marker: u64,
methods: Vec<Registered>,
fields: Vec<FieldEntry>,
_phantom: PhantomData<T>,
}
impl<T> ScopedCollector<T> {
fn new(marker: u64) -> Self {
ScopedCollector {
marker,
methods: Vec::new(),
fields: Vec::new(),
_phantom: PhantomData,
}
}
}
impl<T> UserDataMethods<T> for ScopedCollector<T> {
fn add_method<M, A, R>(&mut self, name: impl Into<String>, method: M)
where
M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
{
let marker = self.marker;
let callback: BoxedCallback = Box::new(move |lua, mut args| {
let this = args.pop_front().unwrap_or(Value::Nil);
let cell = unsafe { recover_scoped_cell::<T>(lua, &this, marker)? };
let a = A::from_lua_multi(args, lua)?;
let borrowed = cell
.cell
.try_borrow()
.map_err(|_| Error::UserDataBorrowError)?;
let data = borrowed.as_ref().ok_or(Error::UserDataDestructed)?;
let r = method(lua, data, a)?;
r.into_lua_multi(lua)
});
self.methods.push(Registered {
name: name.into(),
is_meta: false,
callback,
});
}
fn add_method_mut<M, A, R>(&mut self, name: impl Into<String>, method: M)
where
M: Fn(&Lua, &mut T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
{
let marker = self.marker;
let callback: BoxedCallback = Box::new(move |lua, mut args| {
let this = args.pop_front().unwrap_or(Value::Nil);
let cell = unsafe { recover_scoped_cell::<T>(lua, &this, marker)? };
let a = A::from_lua_multi(args, lua)?;
let mut borrowed = cell
.cell
.try_borrow_mut()
.map_err(|_| Error::UserDataBorrowMutError)?;
let data = borrowed.as_mut().ok_or(Error::UserDataDestructed)?;
let r = method(lua, data, a)?;
r.into_lua_multi(lua)
});
self.methods.push(Registered {
name: name.into(),
is_meta: false,
callback,
});
}
fn add_function<F, A, R>(&mut self, name: impl Into<String>, function: F)
where
F: Fn(&Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
{
let callback: BoxedCallback = Box::new(move |lua, args| {
let a = A::from_lua_multi(args, lua)?;
let r = function(lua, a)?;
r.into_lua_multi(lua)
});
self.methods.push(Registered {
name: name.into(),
is_meta: false,
callback,
});
}
fn add_meta_method<M, A, R>(&mut self, name: impl Into<String>, method: M)
where
M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
{
let marker = self.marker;
let callback: BoxedCallback = Box::new(move |lua, mut args| {
let this = args.pop_front().unwrap_or(Value::Nil);
let cell = unsafe { recover_scoped_cell::<T>(lua, &this, marker)? };
let a = A::from_lua_multi(args, lua)?;
let borrowed = cell
.cell
.try_borrow()
.map_err(|_| Error::UserDataBorrowError)?;
let data = borrowed.as_ref().ok_or(Error::UserDataDestructed)?;
let r = method(lua, data, a)?;
r.into_lua_multi(lua)
});
self.methods.push(Registered {
name: name.into(),
is_meta: true,
callback,
});
}
fn add_meta_method_mut<M, A, R>(&mut self, name: impl Into<String>, method: M)
where
M: Fn(&Lua, &mut T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
{
let marker = self.marker;
let callback: BoxedCallback = Box::new(move |lua, mut args| {
let this = args.pop_front().unwrap_or(Value::Nil);
let cell = unsafe { recover_scoped_cell::<T>(lua, &this, marker)? };
let a = A::from_lua_multi(args, lua)?;
let mut borrowed = cell
.cell
.try_borrow_mut()
.map_err(|_| Error::UserDataBorrowMutError)?;
let data = borrowed.as_mut().ok_or(Error::UserDataDestructed)?;
let r = method(lua, data, a)?;
r.into_lua_multi(lua)
});
self.methods.push(Registered {
name: name.into(),
is_meta: true,
callback,
});
}
}
impl<T> UserDataFields<T> for ScopedCollector<T> {
fn add_field<V>(&mut self, name: impl Into<String>, value: V)
where
V: IntoLua + Clone + MaybeSend + 'static,
{
let callback: BoxedCallback = Box::new(move |lua, _args| {
let v = value.clone().into_lua(lua)?;
v.into_lua_multi(lua)
});
self.fields.push(FieldEntry {
name: name.into(),
is_get: true,
callback,
});
}
fn add_field_method_get<M, R>(&mut self, name: impl Into<String>, method: M)
where
M: Fn(&Lua, &T) -> Result<R> + MaybeSend + 'static,
R: IntoLua,
{
let marker = self.marker;
let callback: BoxedCallback = Box::new(move |lua, mut args| {
let this = args.pop_front().unwrap_or(Value::Nil);
let cell = unsafe { recover_scoped_cell::<T>(lua, &this, marker)? };
let borrowed = cell
.cell
.try_borrow()
.map_err(|_| Error::UserDataBorrowError)?;
let data = borrowed.as_ref().ok_or(Error::UserDataDestructed)?;
let r = method(lua, data)?;
r.into_lua_multi(lua)
});
self.fields.push(FieldEntry {
name: name.into(),
is_get: true,
callback,
});
}
fn add_field_method_set<M, A>(&mut self, name: impl Into<String>, method: M)
where
M: Fn(&Lua, &mut T, A) -> Result<()> + MaybeSend + 'static,
A: FromLua,
{
let marker = self.marker;
let callback: BoxedCallback = Box::new(move |lua, mut args| {
let this = args.pop_front().unwrap_or(Value::Nil);
let cell = unsafe { recover_scoped_cell::<T>(lua, &this, marker)? };
let val = A::from_lua(args.pop_front().unwrap_or(Value::Nil), lua)?;
let mut borrowed = cell
.cell
.try_borrow_mut()
.map_err(|_| Error::UserDataBorrowMutError)?;
let data = borrowed.as_mut().ok_or(Error::UserDataDestructed)?;
method(lua, data, val)?;
().into_lua_multi(lua)
});
self.fields.push(FieldEntry {
name: name.into(),
is_get: false,
callback,
});
}
fn add_field_function_get<F, R>(&mut self, name: impl Into<String>, function: F)
where
F: Fn(&Lua, AnyUserData) -> Result<R> + MaybeSend + 'static,
R: IntoLua,
{
let callback: BoxedCallback = Box::new(move |lua, mut args| {
let this = args.pop_front().unwrap_or(Value::Nil);
let ud = AnyUserData::from_lua(this, lua)?;
let r = function(lua, ud)?;
r.into_lua_multi(lua)
});
self.fields.push(FieldEntry {
name: name.into(),
is_get: true,
callback,
});
}
fn add_field_function_set<F, A>(&mut self, name: impl Into<String>, function: F)
where
F: Fn(&Lua, AnyUserData, A) -> Result<()> + MaybeSend + 'static,
A: FromLua,
{
let callback: BoxedCallback = Box::new(move |lua, mut args| {
let this = args.pop_front().unwrap_or(Value::Nil);
let ud = AnyUserData::from_lua(this, lua)?;
let val = A::from_lua(args.pop_front().unwrap_or(Value::Nil), lua)?;
function(lua, ud, val)?;
().into_lua_multi(lua)
});
self.fields.push(FieldEntry {
name: name.into(),
is_get: false,
callback,
});
}
}
pub(crate) fn create_scoped_userdata<T: UserData>(
lua: &Lua,
data: T,
) -> Result<(AnyUserData, Box<dyn FnOnce()>)> {
let state = lua.state();
let marker = next_scoped_marker();
let mut collector = ScopedCollector::<T>::new(marker);
T::add_fields(&mut collector);
T::add_methods(&mut collector);
let method_table = lua.create_table();
let metatable = lua.create_table();
for item in collector.methods {
let func = create_callback_function(lua, item.callback)?;
if item.is_meta {
metatable.set(item.name, func)?;
} else {
method_table.set(item.name, func)?;
}
}
let has_fields = !collector.fields.is_empty();
let getters = lua.create_table();
let setters = lua.create_table();
for field in collector.fields {
let func = create_callback_function(lua, field.callback)?;
if field.is_get {
getters.set(field.name, func)?;
} else {
setters.set(field.name, func)?;
}
}
if has_fields {
let getters_c = getters.clone();
let methods_c = method_table.clone();
let index_fn = lua.create_function(move |_, (ud, key): (Value, Value)| {
let getter: Value = getters_c.get(key.clone())?;
if let Value::Function(f) = getter {
return f.call::<Value>(ud);
}
let m: Value = methods_c.get(key)?;
Ok(m)
})?;
metatable.set("__index", index_fn)?;
let setters_c = setters.clone();
let newindex_fn =
lua.create_function(move |_, (ud, key, val): (Value, Value, Value)| {
let setter: Value = setters_c.get(key.clone())?;
if let Value::Function(f) = setter {
f.call::<()>((ud, val))?;
return Ok(());
}
let name = key.to_string().unwrap_or_default();
Err(Error::RuntimeError(format!(
"attempt to set unknown field '{name}' on userdata"
)))
})?;
metatable.set("__newindex", newindex_fn)?;
} else {
metatable.set("__index", method_table)?;
}
let ud = unsafe {
let storage = lua_newuserdatadtor(
state,
core::mem::size_of::<ScopedCell<T>>(),
Some(scoped_userdata_dtor::<T>),
);
if storage.is_null() {
return Err(Error::runtime(
"luaur-rt: failed to allocate scoped userdata",
));
}
core::ptr::write(
storage as *mut ScopedCell<T>,
ScopedCell {
marker,
cell: RefCell::new(Some(data)),
},
);
metatable.push_to_stack();
lua_setmetatable(state, -2);
AnyUserData::from_ref(lua.pop_ref())
};
let ud_for_dtor = ud.clone();
let neutralise: Box<dyn FnOnce()> = Box::new(move || {
let state = ud_for_dtor.reference.state();
unsafe {
ud_for_dtor.reference.push();
let ptr = lua_touserdata(state, -1);
lua_pop(state, 1);
if ptr.is_null() {
return;
}
let cell = &*(ptr as *const ScopedCell<T>);
if let Ok(mut guard) = cell.cell.try_borrow_mut() {
let _ = guard.take();
}
}
});
Ok((ud, neutralise))
}
pub(crate) fn create_userdata<T: UserData + MaybeSend + MaybeSync + 'static>(
lua: &Lua,
data: T,
) -> Result<AnyUserData> {
let state = lua.state();
let mut collector = Collector::<T>::new();
T::add_fields(&mut collector);
T::add_methods(&mut collector);
let method_table = lua.create_table();
let metatable = lua.create_table();
for item in collector.methods {
let func = create_callback_function(lua, item.callback)?;
if item.is_meta {
metatable.set(item.name, func)?;
} else {
method_table.set(item.name, func)?;
}
}
let has_fields = !collector.fields.is_empty();
let getters = lua.create_table();
let setters = lua.create_table();
for field in collector.fields {
let func = create_callback_function(lua, field.callback)?;
if field.is_get {
getters.set(field.name, func)?;
} else {
setters.set(field.name, func)?;
}
}
if has_fields {
let getters_c = getters.clone();
let methods_c = method_table.clone();
let index_fn = lua.create_function(move |_, (ud, key): (Value, Value)| {
let getter: Value = getters_c.get(key.clone())?;
if let Value::Function(f) = getter {
return f.call::<Value>(ud);
}
let m: Value = methods_c.get(key)?;
Ok(m)
})?;
metatable.set("__index", index_fn)?;
let setters_c = setters.clone();
let newindex_fn =
lua.create_function(move |_, (ud, key, val): (Value, Value, Value)| {
let setter: Value = setters_c.get(key.clone())?;
if let Value::Function(f) = setter {
f.call::<()>((ud, val))?;
return Ok(());
}
let name = key.to_string().unwrap_or_default();
Err(Error::RuntimeError(format!(
"attempt to set unknown field '{name}' on userdata"
)))
})?;
metatable.set("__newindex", newindex_fn)?;
} else {
metatable.set("__index", method_table)?;
}
unsafe {
let storage = lua_newuserdatadtor(
state,
core::mem::size_of::<UserDataCell<T>>(),
Some(userdata_dtor::<T>),
);
if storage.is_null() {
return Err(Error::runtime("luaur-rt: failed to allocate userdata"));
}
core::ptr::write(
storage as *mut UserDataCell<T>,
UserDataCell {
type_id: TypeId::of::<T>(),
cell: RefCell::new(Some(data)),
},
);
metatable.push_to_stack();
lua_setmetatable(state, -2);
Ok(AnyUserData::from_ref(lua.pop_ref()))
}
}