Skip to content

Commit a7d0691

Browse files
committed
Add AnyUserData::destroy method
1 parent 05778fb commit a7d0691

File tree

6 files changed

+99
-23
lines changed

6 files changed

+99
-23
lines changed

src/state/raw.rs

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use crate::util::{
2727
assert_stack, check_stack, get_destructed_userdata_metatable, get_internal_userdata, get_main_state,
2828
get_metatable_ptr, get_userdata, init_error_registry, init_internal_metatable, init_userdata_metatable,
2929
pop_error, push_internal_userdata, push_string, push_table, rawset_field, safe_pcall, safe_xpcall,
30-
short_type_name, StackGuard, WrappedFailure,
30+
short_type_name, take_userdata, StackGuard, WrappedFailure,
3131
};
3232
use crate::value::{Nil, Value};
3333

@@ -960,21 +960,26 @@ impl RawLua {
960960
}
961961
}
962962

963-
#[cfg(feature = "luau")]
964-
let extra_init = None;
965-
#[cfg(not(feature = "luau"))]
966-
let extra_init: Option<fn(*mut ffi::lua_State, c_int) -> Result<()>> = Some(|state, mt_idx| {
967-
ffi::lua_pushcfunction(state, crate::util::userdata_destructor::<UserDataStorage<T>>);
968-
rawset_field(state, mt_idx, "__gc")
969-
});
963+
unsafe extern "C-unwind" fn userdata_destructor<T>(state: *mut ffi::lua_State) -> c_int {
964+
let ud = get_userdata::<UserDataStorage<T>>(state, -1);
965+
if !(*ud).is_borrowed() {
966+
take_userdata::<UserDataStorage<T>>(state);
967+
ffi::lua_pushboolean(state, 1);
968+
} else {
969+
ffi::lua_pushboolean(state, 0);
970+
}
971+
1
972+
}
973+
974+
ffi::lua_pushcfunction(state, userdata_destructor::<T>);
975+
rawset_field(state, metatable_index, "__gc")?;
970976

971977
init_userdata_metatable(
972978
state,
973979
metatable_index,
974980
field_getters_index,
975981
field_setters_index,
976982
methods_index,
977-
extra_init,
978983
)?;
979984

980985
// Update stack guard to keep metatable after return

src/userdata.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -696,6 +696,31 @@ impl AnyUserData {
696696
}
697697
}
698698

699+
/// Destroys this userdata.
700+
///
701+
/// This is similar to [`AnyUserData::take`], but it doesn't require a type.
702+
///
703+
/// This method works for non-scoped userdata only.
704+
pub fn destroy(&self) -> Result<()> {
705+
let lua = self.0.lua.lock();
706+
let state = lua.state();
707+
unsafe {
708+
let _sg = StackGuard::new(state);
709+
check_stack(state, 3)?;
710+
711+
lua.push_userdata_ref(&self.0)?;
712+
protect_lua!(state, 1, 1, fn(state) {
713+
if ffi::luaL_callmeta(state, -1, cstr!("__gc")) == 0 {
714+
ffi::lua_pushboolean(state, 0);
715+
}
716+
})?;
717+
if ffi::lua_isboolean(state, -1) != 0 && ffi::lua_toboolean(state, -1) != 0 {
718+
return Ok(());
719+
}
720+
Err(Error::UserDataBorrowMutError)
721+
}
722+
}
723+
699724
/// Sets an associated value to this [`AnyUserData`].
700725
///
701726
/// The value may be any Lua value whatsoever, and can be retrieved with [`user_value`].

src/userdata/cell.rs

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use std::any::{type_name, TypeId};
2-
use std::cell::{RefCell, UnsafeCell};
2+
use std::cell::{Cell, RefCell, UnsafeCell};
33
use std::fmt;
44
use std::ops::{Deref, DerefMut};
55
use std::os::raw::c_int;
@@ -99,6 +99,15 @@ impl<T> UserDataVariant<T> {
9999
}
100100
}
101101

102+
#[inline(always)]
103+
fn borrow_count(&self) -> &Cell<usize> {
104+
match self {
105+
Self::Default(inner) => &inner.borrow_count,
106+
#[cfg(feature = "serialize")]
107+
Self::Serializable(inner) => &inner.borrow_count,
108+
}
109+
}
110+
102111
#[inline(always)]
103112
fn as_ptr(&self) -> *mut T {
104113
match self {
@@ -130,6 +139,7 @@ impl Serialize for UserDataStorage<()> {
130139
/// A type that provides interior mutability for a userdata value (thread-safe).
131140
pub(crate) struct UserDataCell<T> {
132141
raw_lock: RawLock,
142+
borrow_count: Cell<usize>,
133143
value: UnsafeCell<T>,
134144
}
135145

@@ -143,6 +153,7 @@ impl<T> UserDataCell<T> {
143153
fn new(value: T) -> Self {
144154
UserDataCell {
145155
raw_lock: RawLock::INIT,
156+
borrow_count: Cell::new(0),
146157
value: UnsafeCell::new(value),
147158
}
148159
}
@@ -291,7 +302,10 @@ pub(crate) struct UserDataBorrowRef<'a, T>(&'a UserDataVariant<T>);
291302
impl<T> Drop for UserDataBorrowRef<'_, T> {
292303
#[inline]
293304
fn drop(&mut self) {
294-
unsafe { self.0.raw_lock().unlock_shared() };
305+
unsafe {
306+
self.0.borrow_count().set(self.0.borrow_count().get() - 1);
307+
self.0.raw_lock().unlock_shared();
308+
}
295309
}
296310
}
297311

@@ -317,6 +331,7 @@ impl<'a, T> TryFrom<&'a UserDataVariant<T>> for UserDataBorrowRef<'a, T> {
317331
if !variant.raw_lock().try_lock_shared() {
318332
return Err(Error::UserDataBorrowError);
319333
}
334+
variant.borrow_count().set(variant.borrow_count().get() + 1);
320335
Ok(UserDataBorrowRef(variant))
321336
}
322337
}
@@ -326,7 +341,10 @@ pub(crate) struct UserDataBorrowMut<'a, T>(&'a UserDataVariant<T>);
326341
impl<T> Drop for UserDataBorrowMut<'_, T> {
327342
#[inline]
328343
fn drop(&mut self) {
329-
unsafe { self.0.raw_lock().unlock_exclusive() };
344+
unsafe {
345+
self.0.borrow_count().set(self.0.borrow_count().get() - 1);
346+
self.0.raw_lock().unlock_exclusive();
347+
}
330348
}
331349
}
332350

@@ -354,6 +372,7 @@ impl<'a, T> TryFrom<&'a UserDataVariant<T>> for UserDataBorrowMut<'a, T> {
354372
if !variant.raw_lock().try_lock_exclusive() {
355373
return Err(Error::UserDataBorrowMutError);
356374
}
375+
variant.borrow_count().set(variant.borrow_count().get() + 1);
357376
Ok(UserDataBorrowMut(variant))
358377
}
359378
}
@@ -470,6 +489,14 @@ impl<T> UserDataStorage<T> {
470489
Self::Scoped(ScopedUserDataVariant::Boxed(RefCell::new(data)))
471490
}
472491

492+
#[inline(always)]
493+
pub(crate) fn is_borrowed(&self) -> bool {
494+
match self {
495+
Self::Owned(variant) => variant.borrow_count().get() > 0,
496+
Self::Scoped(_) => true,
497+
}
498+
}
499+
473500
#[inline]
474501
pub(crate) fn try_borrow_scoped<R>(&self, f: impl FnOnce(&T) -> R) -> Result<R> {
475502
match self {

src/util/mod.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,6 @@ pub(crate) use userdata::{
2121
pub(crate) use userdata::push_uninit_userdata;
2222
pub(crate) use userdata::push_userdata;
2323

24-
#[cfg(not(feature = "luau"))]
25-
pub(crate) use userdata::userdata_destructor;
26-
2724
// Checks that Lua has enough free stack space for future stack operations. On failure, this will
2825
// panic with an internal error message.
2926
#[inline]

src/util/userdata.rs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,6 @@ pub(crate) unsafe fn init_userdata_metatable(
152152
field_getters: Option<c_int>,
153153
field_setters: Option<c_int>,
154154
methods: Option<c_int>,
155-
extra_init: Option<fn(*mut ffi::lua_State, c_int) -> Result<()>>,
156155
) -> Result<()> {
157156
if field_getters.is_some() || methods.is_some() {
158157
// Push `__index` generator function
@@ -195,11 +194,6 @@ pub(crate) unsafe fn init_userdata_metatable(
195194
rawset_field(state, metatable, "__newindex")?;
196195
}
197196

198-
// Additional initialization
199-
if let Some(extra_init) = extra_init {
200-
extra_init(state, metatable)?;
201-
}
202-
203197
ffi::lua_pushboolean(state, 0);
204198
rawset_field(state, metatable, "__metatable")?;
205199

@@ -345,7 +339,7 @@ unsafe fn init_userdata_metatable_newindex(state: *mut ffi::lua_State) -> Result
345339
}
346340

347341
#[cfg(not(feature = "luau"))]
348-
pub(crate) unsafe extern "C-unwind" fn userdata_destructor<T>(state: *mut ffi::lua_State) -> c_int {
342+
unsafe extern "C-unwind" fn userdata_destructor<T>(state: *mut ffi::lua_State) -> c_int {
349343
// It's probably NOT a good idea to catch Rust panics in finalizer
350344
// Lua 5.4 ignores it, other versions generates `LUA_ERRGCMM` without calling message handler
351345
take_userdata::<T>(state);

tests/userdata.rs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,18 @@ fn test_userdata_take() -> Result<()> {
376376
fn test_userdata_destroy() -> Result<()> {
377377
struct MyUserdata(#[allow(unused)] Arc<()>);
378378

379-
impl UserData for MyUserdata {}
379+
impl UserData for MyUserdata {
380+
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
381+
methods.add_method("try_destroy", |lua, _this, ()| {
382+
let ud = lua.globals().get::<AnyUserData>("ud")?;
383+
match ud.destroy() {
384+
Err(Error::UserDataBorrowMutError) => {}
385+
r => panic!("expected `UserDataBorrowMutError` error, got {:?}", r),
386+
}
387+
Ok(())
388+
});
389+
}
390+
}
380391

381392
let rc = Arc::new(());
382393

@@ -394,6 +405,23 @@ fn test_userdata_destroy() -> Result<()> {
394405

395406
assert_eq!(Arc::strong_count(&rc), 1);
396407

408+
let ud = lua.create_userdata(MyUserdata(rc.clone()))?;
409+
assert_eq!(Arc::strong_count(&rc), 2);
410+
let ud_ref = ud.borrow::<MyUserdata>()?;
411+
// With active `UserDataRef` this methods only marks userdata as destructed
412+
// without running destructor
413+
ud.destroy()?;
414+
assert_eq!(Arc::strong_count(&rc), 2);
415+
drop(ud_ref);
416+
assert_eq!(Arc::strong_count(&rc), 1);
417+
418+
// We cannot destroy (internally) borrowed userdata
419+
let ud = lua.create_userdata(MyUserdata(rc.clone()))?;
420+
lua.globals().set("ud", &ud)?;
421+
lua.load("ud:try_destroy()").exec().unwrap();
422+
ud.destroy()?;
423+
assert_eq!(Arc::strong_count(&rc), 1);
424+
397425
Ok(())
398426
}
399427

0 commit comments

Comments
 (0)