Compare commits

..

7 Commits

Author SHA1 Message Date
Alex Orlenko 259eb09ae1 v0.6.5 2021-10-05 18:11:32 +01:00
Alex Orlenko a544e41b33 Add (hidden) method UserData::take() to take out value from userdata 2021-10-05 15:46:50 +01:00
Alex Orlenko 235fba821e Update CHANGELOG 2021-10-04 23:28:24 +01:00
Alex Orlenko c8c64a1b5a Add serializing i128/u128 types.
Fixes #81.
2021-10-04 23:20:11 +01:00
Alex Orlenko eff0bbb052 Add Location::caller() information to Lua::load() if chunk's name is None 2021-10-03 23:20:07 +01:00
Alex Orlenko d098c9ccf6 Refactor Waker handling in async code.
Instead of storing `Option<Waker>` in the Lua registry, store it on the reference thread.
It gives approx +10% performance gain when calling async function.
2021-10-03 22:09:19 +01:00
Alex Orlenko c62b17a5c8 Fixed bug when polling async futures (#77)
We expect first value returned via coroutine.yield() to be a special Pending type.
But instead we checked second value.
2021-10-02 07:23:24 +01:00
13 changed files with 273 additions and 148 deletions
+7
View File
@@ -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
View File
@@ -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]
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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"));
+2
View File
@@ -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
View File
@@ -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
View File
@@ -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),
}
}
}
+1
View File
@@ -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))
}
-24
View File
@@ -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
View File
@@ -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<()>);