mirror of
https://github.com/mlua-rs/mlua
synced 2026-06-08 16:05:43 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 259eb09ae1 | |||
| a544e41b33 | |||
| 235fba821e | |||
| c8c64a1b5a | |||
| eff0bbb052 | |||
| d098c9ccf6 | |||
| c62b17a5c8 |
@@ -1,3 +1,10 @@
|
||||
## v0.6.5
|
||||
|
||||
- Fixed bug when polling async futures (#77)
|
||||
- Refactor Waker handling in async code (+10% performance gain when calling async functions)
|
||||
- Added `Location::caller()` information to `Lua::load()` if chunk's name is None (Rust 1.46+)
|
||||
- Added serialization of i128/u128 types (serde)
|
||||
|
||||
## v0.6.4
|
||||
|
||||
- Performance optimizations
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "mlua"
|
||||
version = "0.6.4" # remember to update html_root_url and mlua_derive
|
||||
version = "0.6.5" # remember to update html_root_url and mlua_derive
|
||||
authors = ["Aleksandr Orlenko <zxteam@pm.me>", "kyren <catherine@chucklefish.org>"]
|
||||
edition = "2018"
|
||||
repository = "https://github.com/khvzak/mlua"
|
||||
@@ -13,7 +13,7 @@ links = "lua"
|
||||
build = "build/main.rs"
|
||||
description = """
|
||||
High level bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT)
|
||||
with async/await features and support of writing native lua modules in Rust.
|
||||
with async/await features and support of writing native Lua modules in Rust.
|
||||
"""
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
|
||||
@@ -351,6 +351,7 @@ macro_rules! lua_convert_int {
|
||||
if let Some(i) = cast(self) {
|
||||
Ok(Value::Integer(i))
|
||||
} else {
|
||||
// TODO: Remove conversion to Number in v0.7
|
||||
cast(self)
|
||||
.ok_or_else(|| Error::ToLuaConversionError {
|
||||
from: stringify!($x),
|
||||
|
||||
+1
-1
@@ -72,7 +72,7 @@
|
||||
//! [`serde::Deserialize`]: https://docs.serde.rs/serde/de/trait.Deserialize.html
|
||||
|
||||
// mlua types in rustdoc of other crates get linked to here.
|
||||
#![doc(html_root_url = "https://docs.rs/mlua/0.6.4")]
|
||||
#![doc(html_root_url = "https://docs.rs/mlua/0.6.5")]
|
||||
// Deny warnings inside doc tests / examples. When this isn't present, rustdoc doesn't show *any*
|
||||
// warnings at all.
|
||||
#![doc(test(attr(deny(warnings))))]
|
||||
|
||||
+60
-27
@@ -5,7 +5,7 @@ use std::ffi::CString;
|
||||
use std::fmt;
|
||||
use std::marker::PhantomData;
|
||||
use std::os::raw::{c_char, c_int, c_void};
|
||||
use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe};
|
||||
use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe, Location};
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use std::{mem, ptr, str};
|
||||
|
||||
@@ -76,9 +76,13 @@ struct ExtraData {
|
||||
ref_stack_top: c_int,
|
||||
ref_free: Vec<c_int>,
|
||||
|
||||
// Pool of preallocated `WrappedFailure` enums
|
||||
// Pool of preallocated `WrappedFailure` enums on the ref thread
|
||||
wrapped_failures_pool: Vec<c_int>,
|
||||
|
||||
// Index of `Option<Waker>` userdata on the ref thread
|
||||
#[cfg(feature = "async")]
|
||||
ref_waker_idx: c_int,
|
||||
|
||||
hook_callback: Option<HookCallback>,
|
||||
}
|
||||
|
||||
@@ -148,8 +152,6 @@ impl LuaOptions {
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
pub(crate) static ASYNC_POLL_PENDING: u8 = 0;
|
||||
#[cfg(feature = "async")]
|
||||
pub(crate) static WAKER_REGISTRY_KEY: u8 = 0;
|
||||
pub(crate) static EXTRA_REGISTRY_KEY: u8 = 0;
|
||||
|
||||
const WRAPPED_FAILURES_POOL_SIZE: usize = 16;
|
||||
@@ -169,6 +171,13 @@ impl Drop for Lua {
|
||||
ffi::lua_replace(extra.ref_thread, index);
|
||||
extra.ref_free.push(index);
|
||||
}
|
||||
#[cfg(feature = "async")]
|
||||
{
|
||||
// Destroy Waker slot
|
||||
ffi::lua_pushnil(extra.ref_thread);
|
||||
ffi::lua_replace(extra.ref_thread, extra.ref_waker_idx);
|
||||
extra.ref_free.push(extra.ref_waker_idx);
|
||||
}
|
||||
mlua_debug_assert!(
|
||||
ffi::lua_gettop(extra.ref_thread) == extra.ref_stack_top
|
||||
&& extra.ref_stack_top as usize == extra.ref_free.len(),
|
||||
@@ -411,13 +420,6 @@ impl Lua {
|
||||
init_gc_metatable::<AsyncCallbackUpvalue>(state, None)?;
|
||||
init_gc_metatable::<AsyncPollUpvalue>(state, None)?;
|
||||
init_gc_metatable::<Option<Waker>>(state, None)?;
|
||||
|
||||
// Create empty Waker slot
|
||||
push_gc_userdata::<Option<Waker>>(state, None)?;
|
||||
protect_lua!(state, 1, 0, fn(state) {
|
||||
let waker_key = &WAKER_REGISTRY_KEY as *const u8 as *const c_void;
|
||||
ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, waker_key);
|
||||
})?;
|
||||
}
|
||||
|
||||
// Init serde metatables
|
||||
@@ -440,6 +442,17 @@ impl Lua {
|
||||
"Error while creating ref thread",
|
||||
);
|
||||
|
||||
// Create empty Waker slot on the ref thread
|
||||
#[cfg(feature = "async")]
|
||||
let ref_waker_idx = {
|
||||
mlua_expect!(
|
||||
push_gc_userdata::<Option<Waker>>(ref_thread, None),
|
||||
"Error while creating Waker slot"
|
||||
);
|
||||
ffi::lua_gettop(ref_thread)
|
||||
};
|
||||
let ref_stack_top = ffi::lua_gettop(ref_thread);
|
||||
|
||||
// Create ExtraData
|
||||
|
||||
let extra = Arc::new(UnsafeCell::new(ExtraData {
|
||||
@@ -452,9 +465,11 @@ impl Lua {
|
||||
safe: false,
|
||||
// We need 1 extra stack space to move values in and out of the ref stack.
|
||||
ref_stack_size: ffi::LUA_MINSTACK - 1,
|
||||
ref_stack_top: 0,
|
||||
ref_stack_top,
|
||||
ref_free: Vec::new(),
|
||||
wrapped_failures_pool: Vec::new(),
|
||||
#[cfg(feature = "async")]
|
||||
ref_waker_idx,
|
||||
hook_callback: None,
|
||||
}));
|
||||
|
||||
@@ -895,6 +910,7 @@ impl Lua {
|
||||
/// chunks of either text or binary type, as if passing `bt` mode to `luaL_loadbufferx`.
|
||||
///
|
||||
/// [`Chunk::exec`]: struct.Chunk.html#method.exec
|
||||
#[track_caller]
|
||||
pub fn load<'lua, 'a, S>(&'lua self, source: &'a S) -> Chunk<'lua, 'a>
|
||||
where
|
||||
S: AsChunk<'lua> + ?Sized,
|
||||
@@ -902,7 +918,10 @@ impl Lua {
|
||||
Chunk {
|
||||
lua: self,
|
||||
source: source.source(),
|
||||
name: source.name(),
|
||||
name: match source.name() {
|
||||
Some(name) => Some(name),
|
||||
None => CString::new(Location::caller().to_string()).ok(),
|
||||
},
|
||||
env: source.env(self),
|
||||
mode: source.mode(),
|
||||
}
|
||||
@@ -1720,10 +1739,14 @@ impl Lua {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
/// Executes the function provided on the ref thread
|
||||
#[inline]
|
||||
pub(crate) unsafe fn get_ref_ptr(&self, lref: &LuaRef) -> *const c_void {
|
||||
ffi::lua_topointer((*self.extra.get()).ref_thread, lref.index)
|
||||
pub(crate) unsafe fn ref_thread_exec<F, R>(&self, f: F) -> R
|
||||
where
|
||||
F: FnOnce(*mut ffi::lua_State) -> R,
|
||||
{
|
||||
let ref_thread = (*self.extra.get()).ref_thread;
|
||||
f(ref_thread)
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn push_userdata_metatable<T: 'static + UserData>(&self) -> Result<()> {
|
||||
@@ -2008,14 +2031,7 @@ impl Lua {
|
||||
lua.state = state;
|
||||
|
||||
// Try to get an outer poll waker
|
||||
let waker_key = &WAKER_REGISTRY_KEY as *const u8 as *const c_void;
|
||||
ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, waker_key);
|
||||
let waker = match get_gc_userdata::<Option<Waker>>(state, -1).as_ref() {
|
||||
Some(Some(waker)) => waker.clone(),
|
||||
_ => noop_waker(),
|
||||
};
|
||||
ffi::lua_pop(state, 1);
|
||||
|
||||
let waker = lua.waker().unwrap_or_else(noop_waker);
|
||||
let mut ctx = Context::from_waker(&waker);
|
||||
|
||||
let fut = &mut (*upvalue).fut;
|
||||
@@ -2090,6 +2106,22 @@ impl Lua {
|
||||
.into_function()
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
pub(crate) unsafe fn waker(&self) -> Option<Waker> {
|
||||
let extra = &*self.extra.get();
|
||||
(*get_userdata::<Option<Waker>>(extra.ref_thread, extra.ref_waker_idx)).clone()
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
pub(crate) unsafe fn set_waker(&self, waker: Option<Waker>) -> Option<Waker> {
|
||||
let extra = &*self.extra.get();
|
||||
let waker_slot = &mut *get_userdata::<Option<Waker>>(extra.ref_thread, extra.ref_waker_idx);
|
||||
match waker {
|
||||
Some(waker) => waker_slot.replace(waker),
|
||||
None => waker_slot.take(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn make_userdata<T>(&self, data: UserDataCell<T>) -> Result<AnyUserData>
|
||||
where
|
||||
T: 'static + UserData,
|
||||
@@ -2097,10 +2129,11 @@ impl Lua {
|
||||
let _sg = StackGuard::new(self.state);
|
||||
check_stack(self.state, 3)?;
|
||||
|
||||
// It's safe to push userdata first and then metatable.
|
||||
// If the first push failed, unlikely we moved `data` to allocated memory.
|
||||
push_userdata(self.state, data)?;
|
||||
// We push metatable first to ensure having correct metatable with `__gc` method
|
||||
ffi::lua_pushnil(self.state);
|
||||
self.push_userdata_metatable::<T>()?;
|
||||
push_userdata(self.state, data)?;
|
||||
ffi::lua_replace(self.state, -3);
|
||||
ffi::lua_setmetatable(self.state, -2);
|
||||
|
||||
Ok(AnyUserData(self.pop_ref()))
|
||||
|
||||
+8
-6
@@ -192,9 +192,10 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
|
||||
let _sg = StackGuard::new(state);
|
||||
assert_stack(state, 2);
|
||||
|
||||
ud.lua.push_ref(&ud);
|
||||
|
||||
// We know the destructor has not run yet because we hold a reference to the userdata.
|
||||
// Check that userdata is not destructed (via `take()` call)
|
||||
if ud.lua.push_userdata_ref(&ud).is_err() {
|
||||
return vec![];
|
||||
}
|
||||
|
||||
// Clear uservalue
|
||||
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
|
||||
@@ -404,9 +405,10 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
|
||||
let _sg = StackGuard::new(state);
|
||||
assert_stack(state, 2);
|
||||
|
||||
ud.lua.push_ref(&ud);
|
||||
|
||||
// We know the destructor has not run yet because we hold a reference to the userdata.
|
||||
// Check that userdata is valid (very likely)
|
||||
if ud.lua.push_userdata_ref(&ud).is_err() {
|
||||
return vec![];
|
||||
}
|
||||
|
||||
// Deregister metatable
|
||||
ffi::lua_getmetatable(state, -1);
|
||||
|
||||
+7
-3
@@ -7,6 +7,7 @@ use std::string::String as StdString;
|
||||
use serde::de::{self, IntoDeserializer};
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use crate::ffi;
|
||||
use crate::table::{Table, TablePairs, TableSequence};
|
||||
use crate::value::Value;
|
||||
|
||||
@@ -298,7 +299,7 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
|
||||
}
|
||||
|
||||
serde::forward_to_deserialize_any! {
|
||||
bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string bytes
|
||||
bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string bytes
|
||||
byte_buf unit unit_struct newtype_struct
|
||||
identifier ignored_any
|
||||
}
|
||||
@@ -500,7 +501,9 @@ impl RecursionGuard {
|
||||
#[inline]
|
||||
fn new(table: &Table, visited: &Rc<RefCell<HashSet<*const c_void>>>) -> Self {
|
||||
let visited = Rc::clone(visited);
|
||||
let ptr = unsafe { table.0.lua.get_ref_ptr(&table.0) };
|
||||
let lua = table.0.lua;
|
||||
let ptr =
|
||||
unsafe { lua.ref_thread_exec(|refthr| ffi::lua_topointer(refthr, table.0.index)) };
|
||||
visited.borrow_mut().insert(ptr);
|
||||
RecursionGuard { ptr, visited }
|
||||
}
|
||||
@@ -521,7 +524,8 @@ fn check_value_if_skip(
|
||||
match value {
|
||||
Value::Table(table) => {
|
||||
let lua = table.0.lua;
|
||||
let ptr = unsafe { lua.get_ref_ptr(&table.0) };
|
||||
let ptr =
|
||||
unsafe { lua.ref_thread_exec(|refthr| ffi::lua_topointer(refthr, table.0.index)) };
|
||||
if visited.borrow().contains(&ptr) {
|
||||
if options.deny_recursive_tables {
|
||||
return Err(de::Error::custom("recursive table detected"));
|
||||
|
||||
@@ -139,6 +139,8 @@ impl<'lua> ser::Serializer for Serializer<'lua> {
|
||||
lua_serialize_number!(serialize_u32, u32);
|
||||
lua_serialize_number!(serialize_i64, i64);
|
||||
lua_serialize_number!(serialize_u64, u64);
|
||||
lua_serialize_number!(serialize_i128, i128);
|
||||
lua_serialize_number!(serialize_u128, u128);
|
||||
|
||||
lua_serialize_number!(serialize_f32, f32);
|
||||
lua_serialize_number!(serialize_f64, f64);
|
||||
|
||||
+21
-38
@@ -4,7 +4,7 @@ use std::os::raw::c_int;
|
||||
use crate::error::{Error, Result};
|
||||
use crate::ffi;
|
||||
use crate::types::LuaRef;
|
||||
use crate::util::{assert_stack, check_stack, error_traceback, pop_error, StackGuard};
|
||||
use crate::util::{check_stack, error_traceback, pop_error, StackGuard};
|
||||
use crate::value::{FromLuaMulti, MultiValue, ToLuaMulti};
|
||||
|
||||
#[cfg(any(feature = "lua54", all(feature = "luajit", feature = "vendored"), doc))]
|
||||
@@ -13,15 +13,13 @@ use crate::function::Function;
|
||||
#[cfg(feature = "async")]
|
||||
use {
|
||||
crate::{
|
||||
lua::{ASYNC_POLL_PENDING, WAKER_REGISTRY_KEY},
|
||||
util::get_gc_userdata,
|
||||
lua::{Lua, ASYNC_POLL_PENDING},
|
||||
value::Value,
|
||||
},
|
||||
futures_core::{future::Future, stream::Stream},
|
||||
std::{
|
||||
cell::RefCell,
|
||||
marker::PhantomData,
|
||||
mem,
|
||||
os::raw::c_void,
|
||||
pin::Pin,
|
||||
task::{Context, Poll, Waker},
|
||||
@@ -114,11 +112,10 @@ impl<'lua> Thread<'lua> {
|
||||
let nargs = args.len() as c_int;
|
||||
let results = unsafe {
|
||||
let _sg = StackGuard::new(lua.state);
|
||||
check_stack(lua.state, cmp::min(nargs + 1, 3))?;
|
||||
check_stack(lua.state, cmp::max(nargs + 1, 3))?;
|
||||
|
||||
lua.push_ref(&self.0);
|
||||
let thread_state = ffi::lua_tothread(lua.state, -1);
|
||||
ffi::lua_pop(lua.state, 1);
|
||||
let thread_state =
|
||||
lua.ref_thread_exec(|ref_thread| ffi::lua_tothread(ref_thread, self.0.index));
|
||||
|
||||
let status = ffi::lua_status(thread_state);
|
||||
if status != ffi::LUA_YIELD && ffi::lua_gettop(thread_state) == 0 {
|
||||
@@ -155,12 +152,8 @@ impl<'lua> Thread<'lua> {
|
||||
pub fn status(&self) -> ThreadStatus {
|
||||
let lua = self.0.lua;
|
||||
unsafe {
|
||||
let _sg = StackGuard::new(lua.state);
|
||||
assert_stack(lua.state, 1);
|
||||
|
||||
lua.push_ref(&self.0);
|
||||
let thread_state = ffi::lua_tothread(lua.state, -1);
|
||||
ffi::lua_pop(lua.state, 1);
|
||||
let thread_state =
|
||||
lua.ref_thread_exec(|ref_thread| ffi::lua_tothread(ref_thread, self.0.index));
|
||||
|
||||
let status = ffi::lua_status(thread_state);
|
||||
if status != ffi::LUA_OK && status != ffi::LUA_YIELD {
|
||||
@@ -288,7 +281,7 @@ where
|
||||
_ => return Poll::Ready(None),
|
||||
};
|
||||
|
||||
let _wg = WakerGuard::new(lua.state, cx.waker().clone());
|
||||
let _wg = WakerGuard::new(lua, cx.waker().clone());
|
||||
let ret: MultiValue = if let Some(args) = self.args0.borrow_mut().take() {
|
||||
self.thread.resume(args?)?
|
||||
} else {
|
||||
@@ -319,7 +312,7 @@ where
|
||||
_ => return Poll::Ready(Err(Error::CoroutineInactive)),
|
||||
};
|
||||
|
||||
let _wg = WakerGuard::new(lua.state, cx.waker().clone());
|
||||
let _wg = WakerGuard::new(lua, cx.waker().clone());
|
||||
let ret: MultiValue = if let Some(args) = self.args0.borrow_mut().take() {
|
||||
self.thread.resume(args?)?
|
||||
} else {
|
||||
@@ -344,7 +337,7 @@ where
|
||||
#[inline(always)]
|
||||
fn is_poll_pending(val: &MultiValue) -> bool {
|
||||
match val.iter().enumerate().last() {
|
||||
Some((1, Value::LightUserData(ud))) => {
|
||||
Some((0, Value::LightUserData(ud))) => {
|
||||
ud.0 == &ASYNC_POLL_PENDING as *const u8 as *mut c_void
|
||||
}
|
||||
_ => false,
|
||||
@@ -352,37 +345,27 @@ fn is_poll_pending(val: &MultiValue) -> bool {
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
struct WakerGuard(*mut ffi::lua_State, Option<Waker>);
|
||||
struct WakerGuard<'lua> {
|
||||
lua: &'lua Lua,
|
||||
prev: Option<Waker>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
impl WakerGuard {
|
||||
pub fn new(state: *mut ffi::lua_State, waker: Waker) -> Result<WakerGuard> {
|
||||
impl<'lua> WakerGuard<'lua> {
|
||||
#[inline]
|
||||
pub fn new(lua: &Lua, waker: Waker) -> Result<WakerGuard> {
|
||||
unsafe {
|
||||
let _sg = StackGuard::new(state);
|
||||
check_stack(state, 3)?;
|
||||
|
||||
let waker_key = &WAKER_REGISTRY_KEY as *const u8 as *const c_void;
|
||||
ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, waker_key);
|
||||
let waker_slot = get_gc_userdata::<Option<Waker>>(state, -1).as_mut();
|
||||
let old = mlua_expect!(waker_slot, "Waker is destroyed").replace(waker);
|
||||
|
||||
Ok(WakerGuard(state, old))
|
||||
let prev = lua.set_waker(Some(waker));
|
||||
Ok(WakerGuard { lua, prev })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "async")]
|
||||
impl Drop for WakerGuard {
|
||||
impl<'lua> Drop for WakerGuard<'lua> {
|
||||
fn drop(&mut self) {
|
||||
let state = self.0;
|
||||
unsafe {
|
||||
let _sg = StackGuard::new(state);
|
||||
assert_stack(state, 3);
|
||||
|
||||
let waker_key = &WAKER_REGISTRY_KEY as *const u8 as *const c_void;
|
||||
ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, waker_key);
|
||||
let waker_slot = get_gc_userdata::<Option<Waker>>(state, -1).as_mut();
|
||||
mem::swap(mlua_expect!(waker_slot, "Waker is destroyed"), &mut self.1);
|
||||
self.lua.set_waker(self.prev.take());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+61
-25
@@ -11,7 +11,6 @@ use std::future::Future;
|
||||
#[cfg(feature = "serialize")]
|
||||
use {
|
||||
serde::ser::{self, Serialize, Serializer},
|
||||
std::os::raw::c_void,
|
||||
std::result::Result as StdResult,
|
||||
};
|
||||
|
||||
@@ -21,7 +20,7 @@ use crate::function::Function;
|
||||
use crate::lua::Lua;
|
||||
use crate::table::{Table, TablePairs};
|
||||
use crate::types::{Callback, LuaRef, MaybeSend};
|
||||
use crate::util::{check_stack, get_userdata, StackGuard};
|
||||
use crate::util::{check_stack, get_userdata, take_userdata, StackGuard};
|
||||
use crate::value::{FromLua, FromLuaMulti, ToLua, ToLuaMulti};
|
||||
|
||||
#[cfg(any(feature = "lua52", feature = "lua51", feature = "luajit"))]
|
||||
@@ -626,18 +625,24 @@ impl<T> UserDataCell<T> {
|
||||
.map(|r| RefMut::map(r, |r| r.deref_mut()))
|
||||
.map_err(|_| Error::UserDataBorrowMutError)
|
||||
}
|
||||
|
||||
// Consumes this `UserDataCell`, returning the wrapped value.
|
||||
#[inline]
|
||||
fn into_inner(self) -> T {
|
||||
self.0.into_inner().into_inner()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) enum UserDataWrapped<T> {
|
||||
Default(T),
|
||||
Default(Box<T>),
|
||||
#[cfg(feature = "serialize")]
|
||||
Serializable(*mut T, *const dyn erased_serde::Serialize),
|
||||
Serializable(Box<dyn erased_serde::Serialize>),
|
||||
}
|
||||
|
||||
impl<T> UserDataWrapped<T> {
|
||||
#[inline]
|
||||
fn new(data: T) -> Self {
|
||||
UserDataWrapped::Default(data)
|
||||
UserDataWrapped::Default(Box::new(data))
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
@@ -646,16 +651,15 @@ impl<T> UserDataWrapped<T> {
|
||||
where
|
||||
T: 'static + Serialize,
|
||||
{
|
||||
let data_raw = Box::into_raw(Box::new(data));
|
||||
UserDataWrapped::Serializable(data_raw, data_raw)
|
||||
UserDataWrapped::Serializable(Box::new(data))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
impl<T> Drop for UserDataWrapped<T> {
|
||||
fn drop(&mut self) {
|
||||
if let UserDataWrapped::Serializable(data, _) = *self {
|
||||
drop(unsafe { Box::from_raw(data) });
|
||||
#[inline]
|
||||
fn into_inner(self) -> T {
|
||||
match self {
|
||||
Self::Default(data) => *data,
|
||||
#[cfg(feature = "serialize")]
|
||||
Self::Serializable(data) => unsafe { *Box::from_raw(Box::into_raw(data) as *mut T) },
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -668,7 +672,9 @@ impl<T> Deref for UserDataWrapped<T> {
|
||||
match self {
|
||||
Self::Default(data) => data,
|
||||
#[cfg(feature = "serialize")]
|
||||
Self::Serializable(data, _) => unsafe { &**data },
|
||||
Self::Serializable(data) => unsafe {
|
||||
&*(data.as_ref() as *const _ as *const Self::Target)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -679,7 +685,9 @@ impl<T> DerefMut for UserDataWrapped<T> {
|
||||
match self {
|
||||
Self::Default(data) => data,
|
||||
#[cfg(feature = "serialize")]
|
||||
Self::Serializable(data, _) => unsafe { &mut **data },
|
||||
Self::Serializable(data) => unsafe {
|
||||
&mut *(data.as_mut() as *mut _ as *mut Self::Target)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -748,6 +756,35 @@ impl<'lua> AnyUserData<'lua> {
|
||||
self.inspect(|cell| cell.try_borrow_mut())
|
||||
}
|
||||
|
||||
/// Takes out the value of `UserData` and sets the special "destructed" metatable that prevents
|
||||
/// any further operations with this userdata.
|
||||
#[doc(hidden)]
|
||||
pub fn take<T: 'static + UserData>(&self) -> Result<T> {
|
||||
let lua = self.0.lua;
|
||||
unsafe {
|
||||
let _sg = StackGuard::new(lua.state);
|
||||
check_stack(lua.state, 2)?;
|
||||
|
||||
let type_id = lua.push_userdata_ref(&self.0)?;
|
||||
match type_id {
|
||||
Some(type_id) if type_id == TypeId::of::<T>() => {
|
||||
// Try to borrow userdata exclusively
|
||||
let _ = (*get_userdata::<UserDataCell<T>>(lua.state, -1)).try_borrow_mut()?;
|
||||
|
||||
// Clear uservalue
|
||||
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
|
||||
ffi::lua_pushnil(lua.state);
|
||||
#[cfg(any(feature = "lua51", feature = "luajit"))]
|
||||
protect_lua!(lua.state, 0, 1, fn(state) ffi::lua_newtable(state))?;
|
||||
ffi::lua_setuservalue(lua.state, -2);
|
||||
|
||||
Ok(take_userdata::<UserDataCell<T>>(lua.state).into_inner())
|
||||
}
|
||||
_ => Err(Error::UserDataTypeMismatch),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets an associated value to this `AnyUserData`.
|
||||
///
|
||||
/// The value may be any Lua value whatsoever, and can be retrieved with [`get_user_value`].
|
||||
@@ -960,20 +997,19 @@ impl<'lua> Serialize for AnyUserData<'lua> {
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
unsafe {
|
||||
let lua = self.0.lua;
|
||||
let lua = self.0.lua;
|
||||
let data = unsafe {
|
||||
let _sg = StackGuard::new(lua.state);
|
||||
check_stack(lua.state, 3).map_err(ser::Error::custom)?;
|
||||
|
||||
lua.push_userdata_ref(&self.0).map_err(ser::Error::custom)?;
|
||||
let ud = &*get_userdata::<UserDataCell<c_void>>(lua.state, -1);
|
||||
let data =
|
||||
ud.0.try_borrow()
|
||||
.map_err(|_| ser::Error::custom(Error::UserDataBorrowError))?;
|
||||
match *data {
|
||||
UserDataWrapped::Default(_) => UserDataSerializeError.serialize(serializer),
|
||||
UserDataWrapped::Serializable(_, ser) => (&*ser).serialize(serializer),
|
||||
}
|
||||
let ud = &*get_userdata::<UserDataCell<()>>(lua.state, -1);
|
||||
ud.0.try_borrow()
|
||||
.map_err(|_| ser::Error::custom(Error::UserDataBorrowError))?
|
||||
};
|
||||
match &*data {
|
||||
UserDataWrapped::Default(_) => UserDataSerializeError.serialize(serializer),
|
||||
UserDataWrapped::Serializable(ser) => ser.serialize(serializer),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -805,6 +805,7 @@ pub unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<()> {
|
||||
// Create destructed userdata metatable
|
||||
|
||||
unsafe extern "C" fn destructed_error(state: *mut ffi::lua_State) -> c_int {
|
||||
// TODO: Consider changing error to UserDataDestructed in v0.7
|
||||
callback_error(state, |_| Err(Error::CallbackDestructed))
|
||||
}
|
||||
|
||||
|
||||
@@ -178,30 +178,6 @@ fn test_to_value_struct() -> LuaResult<()> {
|
||||
fn test_to_value_enum() -> LuaResult<()> {
|
||||
let lua = Lua::new();
|
||||
let globals = lua.globals();
|
||||
globals.set("null", lua.null())?;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct Test {
|
||||
name: String,
|
||||
key: i64,
|
||||
data: Option<bool>,
|
||||
}
|
||||
|
||||
let test = Test {
|
||||
name: "alex".to_string(),
|
||||
key: -16,
|
||||
data: None,
|
||||
};
|
||||
|
||||
globals.set("value", lua.to_value(&test)?)?;
|
||||
lua.load(
|
||||
r#"
|
||||
assert(value["name"] == "alex")
|
||||
assert(value["key"] == -16)
|
||||
assert(value["data"] == null)
|
||||
"#,
|
||||
)
|
||||
.exec()?;
|
||||
|
||||
#[derive(Serialize)]
|
||||
enum E {
|
||||
|
||||
+102
-22
@@ -36,6 +36,7 @@ fn test_user_data() -> Result<()> {
|
||||
|
||||
#[test]
|
||||
fn test_methods() -> Result<()> {
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
|
||||
struct MyUserData(i64);
|
||||
|
||||
impl UserData for MyUserData {
|
||||
@@ -48,29 +49,38 @@ fn test_methods() -> Result<()> {
|
||||
}
|
||||
}
|
||||
|
||||
let lua = Lua::new();
|
||||
let globals = lua.globals();
|
||||
let userdata = lua.create_userdata(MyUserData(42))?;
|
||||
globals.set("userdata", userdata.clone())?;
|
||||
lua.load(
|
||||
r#"
|
||||
function get_it()
|
||||
return userdata:get_value()
|
||||
end
|
||||
fn check_methods(lua: &Lua, userdata: AnyUserData) -> Result<()> {
|
||||
let globals = lua.globals();
|
||||
globals.set("userdata", userdata.clone())?;
|
||||
lua.load(
|
||||
r#"
|
||||
function get_it()
|
||||
return userdata:get_value()
|
||||
end
|
||||
|
||||
function set_it(i)
|
||||
return userdata:set_value(i)
|
||||
end
|
||||
"#,
|
||||
)
|
||||
.exec()?;
|
||||
let get = globals.get::<_, Function>("get_it")?;
|
||||
let set = globals.get::<_, Function>("set_it")?;
|
||||
assert_eq!(get.call::<_, i64>(())?, 42);
|
||||
userdata.borrow_mut::<MyUserData>()?.0 = 64;
|
||||
assert_eq!(get.call::<_, i64>(())?, 64);
|
||||
set.call::<_, ()>(100)?;
|
||||
assert_eq!(get.call::<_, i64>(())?, 100);
|
||||
function set_it(i)
|
||||
return userdata:set_value(i)
|
||||
end
|
||||
"#,
|
||||
)
|
||||
.exec()?;
|
||||
let get = globals.get::<_, Function>("get_it")?;
|
||||
let set = globals.get::<_, Function>("set_it")?;
|
||||
assert_eq!(get.call::<_, i64>(())?, 42);
|
||||
userdata.borrow_mut::<MyUserData>()?.0 = 64;
|
||||
assert_eq!(get.call::<_, i64>(())?, 64);
|
||||
set.call::<_, ()>(100)?;
|
||||
assert_eq!(get.call::<_, i64>(())?, 100);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
let lua = Lua::new();
|
||||
|
||||
check_methods(&lua, lua.create_userdata(MyUserData(42))?)?;
|
||||
|
||||
// Additionally check serializable userdata
|
||||
#[cfg(feature = "serialize")]
|
||||
check_methods(&lua, lua.create_ser_userdata(MyUserData(42))?)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -252,6 +262,76 @@ fn test_gc_userdata() -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_userdata_take() -> Result<()> {
|
||||
#[derive(Debug)]
|
||||
struct MyUserdata(Arc<i64>);
|
||||
|
||||
impl UserData for MyUserdata {
|
||||
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_method("num", |_, this, ()| Ok(*this.0))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
impl serde::Serialize for MyUserdata {
|
||||
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_i64(*self.0)
|
||||
}
|
||||
}
|
||||
|
||||
fn check_userdata_take(lua: &Lua, userdata: AnyUserData, rc: Arc<i64>) -> Result<()> {
|
||||
lua.globals().set("userdata", userdata.clone())?;
|
||||
assert_eq!(Arc::strong_count(&rc), 2);
|
||||
let userdata_copy = userdata.clone();
|
||||
{
|
||||
let _value = userdata.borrow::<MyUserdata>()?;
|
||||
// We should not be able to take userdata if it's borrowed
|
||||
match userdata_copy.take::<MyUserdata>() {
|
||||
Err(Error::UserDataBorrowMutError) => {}
|
||||
r => panic!("expected `UserDataBorrowMutError` error, got {:?}", r),
|
||||
}
|
||||
}
|
||||
|
||||
let value = userdata_copy.take::<MyUserdata>()?;
|
||||
assert_eq!(*value.0, 18);
|
||||
drop(value);
|
||||
assert_eq!(Arc::strong_count(&rc), 1);
|
||||
|
||||
match userdata.borrow::<MyUserdata>() {
|
||||
Err(Error::UserDataDestructed) => {}
|
||||
r => panic!("expected `UserDataDestructed` error, got {:?}", r),
|
||||
}
|
||||
match lua.load("userdata:num()").exec() {
|
||||
Err(Error::CallbackError { ref cause, .. }) => match cause.as_ref() {
|
||||
Error::CallbackDestructed => {}
|
||||
err => panic!("expected `CallbackDestructed`, got {:?}", err),
|
||||
},
|
||||
r => panic!("improper return for destructed userdata: {:?}", r),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
let lua = Lua::new();
|
||||
|
||||
let rc = Arc::new(18);
|
||||
let userdata = lua.create_userdata(MyUserdata(rc.clone()))?;
|
||||
check_userdata_take(&lua, userdata, rc)?;
|
||||
|
||||
// Additionally check serializable userdata
|
||||
#[cfg(feature = "serialize")]
|
||||
{
|
||||
let rc = Arc::new(18);
|
||||
let userdata = lua.create_ser_userdata(MyUserdata(rc.clone()))?;
|
||||
check_userdata_take(&lua, userdata, rc)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_destroy_userdata() -> Result<()> {
|
||||
struct MyUserdata(Arc<()>);
|
||||
|
||||
Reference in New Issue
Block a user