Skip to content

Commit c7094d4

Browse files
committed
Add Scope::create_any_userdata to create Lua objects from any non-static Rust types.
1 parent a7d0691 commit c7094d4

File tree

2 files changed

+87
-26
lines changed

2 files changed

+87
-26
lines changed

src/scope.rs

+68-18
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> {
171171
let _sg = StackGuard::new(state);
172172
check_stack(state, 3)?;
173173

174-
// // We don't write the data to the userdata until pushing the metatable
174+
// We don't write the data to the userdata until pushing the metatable
175175
let protect = !self.lua.unlikely_memory_error();
176176
#[cfg(feature = "luau")]
177177
let ud_ptr = {
@@ -194,29 +194,79 @@ impl<'scope, 'env: 'scope> Scope<'scope, 'env> {
194194
ffi::lua_setmetatable(state, -2);
195195

196196
let ud = AnyUserData(self.lua.pop_ref());
197+
self.attach_destructor::<T>(&ud);
197198

198-
let destructor: DestructorCallback = Box::new(|rawlua, vref| {
199-
let state = rawlua.state();
200-
let _sg = StackGuard::new(state);
201-
assert_stack(state, 2);
199+
Ok(ud)
200+
}
201+
}
202202

203-
// Check that userdata is valid (very likely)
204-
if rawlua.push_userdata_ref(&vref).is_err() {
205-
return vec![];
206-
}
203+
/// Creates a Lua userdata object from a custom Rust type.
204+
///
205+
/// Since the Rust type is not required to be static and implement [`UserData`] trait,
206+
/// you need to provide a function to register fields or methods for the object.
207+
///
208+
/// See also [`Scope::create_userdata`] for more details about non-static limitations.
209+
pub fn create_any_userdata<T>(
210+
&'scope self,
211+
data: T,
212+
register: impl FnOnce(&mut UserDataRegistry<T>),
213+
) -> Result<AnyUserData>
214+
where
215+
T: 'env,
216+
{
217+
let state = self.lua.state();
218+
let ud = unsafe {
219+
let _sg = StackGuard::new(state);
220+
check_stack(state, 3)?;
207221

208-
// Deregister metatable
209-
let mt_ptr = get_metatable_ptr(state, -1);
210-
rawlua.deregister_userdata_metatable(mt_ptr);
222+
// We don't write the data to the userdata until pushing the metatable
223+
let protect = !self.lua.unlikely_memory_error();
224+
#[cfg(feature = "luau")]
225+
let ud_ptr = {
226+
let data = UserDataStorage::new_scoped(data);
227+
util::push_userdata::<UserDataStorage<T>>(state, data, protect)?
228+
};
229+
#[cfg(not(feature = "luau"))]
230+
let ud_ptr = util::push_uninit_userdata::<UserDataStorage<T>>(state, protect)?;
211231

212-
let ud = take_userdata::<UserDataStorage<T>>(state);
232+
// Push the metatable and register it with no TypeId
233+
let mut registry = UserDataRegistry::new_unique(ud_ptr as *mut _);
234+
register(&mut registry);
235+
self.lua.push_userdata_metatable(registry)?;
236+
let mt_ptr = ffi::lua_topointer(state, -1);
237+
self.lua.register_userdata_metatable(mt_ptr, None);
213238

214-
vec![Box::new(move || drop(ud))]
215-
});
216-
self.destructors.0.borrow_mut().push((ud.0.clone(), destructor));
239+
// Write data to the pointer and attach metatable
240+
#[cfg(not(feature = "luau"))]
241+
std::ptr::write(ud_ptr, UserDataStorage::new_scoped(data));
242+
ffi::lua_setmetatable(state, -2);
217243

218-
Ok(ud)
219-
}
244+
AnyUserData(self.lua.pop_ref())
245+
};
246+
self.attach_destructor::<T>(&ud);
247+
Ok(ud)
248+
}
249+
250+
fn attach_destructor<T: 'env>(&'scope self, ud: &AnyUserData) {
251+
let destructor: DestructorCallback = Box::new(|rawlua, vref| unsafe {
252+
let state = rawlua.state();
253+
let _sg = StackGuard::new(state);
254+
assert_stack(state, 2);
255+
256+
// Check that userdata is valid (very likely)
257+
if rawlua.push_userdata_ref(&vref).is_err() {
258+
return vec![];
259+
}
260+
261+
// Deregister metatable
262+
let mt_ptr = get_metatable_ptr(state, -1);
263+
rawlua.deregister_userdata_metatable(mt_ptr);
264+
265+
let ud = take_userdata::<UserDataStorage<T>>(state);
266+
267+
vec![Box::new(move || drop(ud))]
268+
});
269+
self.destructors.0.borrow_mut().push((ud.0.clone(), destructor));
220270
}
221271

222272
/// Adds a destructor function to be run when the scope ends.

tests/scope.rs

+19-8
Original file line numberDiff line numberDiff line change
@@ -410,15 +410,26 @@ fn test_scope_userdata_ref_mut() -> Result<()> {
410410
fn test_scope_any_userdata() -> Result<()> {
411411
let lua = Lua::new();
412412

413-
lua.register_userdata_type::<StdString>(|reg| {
414-
reg.add_meta_method("__tostring", |_, data, ()| Ok(data.clone()));
415-
})?;
413+
fn register(reg: &mut UserDataRegistry<&mut StdString>) {
414+
reg.add_method_mut("push", |_, this, s: String| {
415+
this.push_str(&s.to_str()?);
416+
Ok(())
417+
});
418+
reg.add_meta_method("__tostring", |_, data, ()| Ok((*data).clone()));
419+
}
416420

417-
let data = StdString::from("foo");
421+
let mut data = StdString::from("foo");
418422
lua.scope(|scope| {
419-
let ud = scope.create_any_userdata_ref(&data)?;
423+
let ud = scope.create_any_userdata(&mut data, register)?;
420424
lua.globals().set("ud", ud)?;
421-
lua.load("assert(tostring(ud) == 'foo')").exec()
425+
lua.load(
426+
r#"
427+
assert(tostring(ud) == "foo")
428+
ud:push("bar")
429+
assert(tostring(ud) == "foobar")
430+
"#,
431+
)
432+
.exec()
422433
})?;
423434

424435
// Check that userdata is destructed
@@ -498,7 +509,7 @@ fn test_scope_destructors() -> Result<()> {
498509
let ud = lua.create_any_userdata(arc_str.clone())?;
499510
lua.scope(|scope| {
500511
scope.add_destructor(|| {
501-
assert!(ud.take::<Arc<StdString>>().is_ok());
512+
assert!(ud.destroy().is_ok());
502513
});
503514
Ok(())
504515
})?;
@@ -510,7 +521,7 @@ fn test_scope_destructors() -> Result<()> {
510521
assert_eq!(arc_str.as_str(), "foo");
511522
lua.scope(|scope| {
512523
scope.add_destructor(|| {
513-
assert!(ud.take::<Arc<StdString>>().is_err());
524+
assert!(ud.destroy().is_err());
514525
});
515526
Ok(())
516527
})

0 commit comments

Comments
 (0)