Compare commits

...

14 Commits

Author SHA1 Message Date
Alex Orlenko 270b98a429 v0.9.6 2024-02-29 15:39:14 +00:00
Alex Orlenko 0ee3324462 Add LUA_TCDATA to util::to_string() helper 2024-02-29 12:57:04 +00:00
Alex Orlenko 8a9c4f0b15 Optimize table array traversal during serialization 2024-02-11 23:02:36 +00:00
Alex Orlenko 34db5f985e Refactor benchmarks 2024-02-11 17:48:25 +00:00
Alex Orlenko 020e8a78a8 Impl FromLua for RegistryKey 2024-02-10 15:41:48 +00:00
Alex Orlenko 3ca7b4942e Implement IntoLua for &Value 2024-02-07 00:10:49 +00:00
Alex Orlenko 1754226c74 Impl IntoLua::push_into_stack for integers 2024-02-03 21:32:49 +00:00
Alex Orlenko 3014c4d7a1 Add REF_STACK_RESERVE constant 2024-02-02 23:26:33 +00:00
Alex Orlenko 908f37656a Add to_pointer function to Function/Table/Thread 2024-02-02 09:23:16 +00:00
Alex Orlenko f4d783cb41 Impl push_into_stack for StdResult 2024-02-01 23:42:44 +00:00
Alex Orlenko f5982bc204 Add inline to FromLua<bool>::from_stack 2024-01-27 14:40:32 +00:00
Alex Orlenko e30b425224 Fix crash when initializing Luau sandbox without stdlibs (#361) 2024-01-27 11:51:59 +00:00
Alex Orlenko 512921404c Add fastpath push_into_stack/from_stack methods for bool type 2024-01-26 14:07:56 +00:00
Alex Orlenko dfd82edc42 Add Lua::push() helper 2024-01-26 13:52:19 +00:00
24 changed files with 598 additions and 343 deletions
+8
View File
@@ -1,3 +1,11 @@
## v0.9.6
- Added `to_pointer` function to `Function`/`Table`/`Thread`
- Implemented `IntoLua` for `&Value`
- Implemented `FromLua` for `RegistryKey`
- Faster (~5%) table array traversal during serialization
- Some performance improvements for bool/int types
## v0.9.5
- Minimal Luau updated to 0.609
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "mlua"
version = "0.9.5" # remember to update mlua_derive
version = "0.9.6" # remember to update mlua_derive
authors = ["Aleksandr Orlenko <zxteam@pm.me>", "kyren <catherine@kyju.org>"]
rust-version = "1.71"
edition = "2021"
+187 -166
View File
@@ -1,5 +1,7 @@
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::Duration;
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
use tokio::runtime::Runtime;
use tokio::task;
@@ -10,10 +12,10 @@ fn collect_gc_twice(lua: &Lua) {
lua.gc_collect().unwrap();
}
fn create_table(c: &mut Criterion) {
fn table_create_empty(c: &mut Criterion) {
let lua = Lua::new();
c.bench_function("create [table empty]", |b| {
c.bench_function("table [create empty]", |b| {
b.iter_batched(
|| collect_gc_twice(&lua),
|_| {
@@ -24,35 +26,33 @@ fn create_table(c: &mut Criterion) {
});
}
fn create_array(c: &mut Criterion) {
fn table_create_array(c: &mut Criterion) {
let lua = Lua::new();
c.bench_function("create [array] 10", |b| {
c.bench_function("table [create array]", |b| {
b.iter_batched(
|| collect_gc_twice(&lua),
|_| {
let table = lua.create_table().unwrap();
for i in 1..=10 {
table.set(i, i).unwrap();
}
lua.create_sequence_from(1..=10).unwrap();
},
BatchSize::SmallInput,
);
});
}
fn create_string_table(c: &mut Criterion) {
fn table_create_hash(c: &mut Criterion) {
let lua = Lua::new();
c.bench_function("create [table string] 10", |b| {
c.bench_function("table [create hash]", |b| {
b.iter_batched(
|| collect_gc_twice(&lua),
|_| {
let table = lua.create_table().unwrap();
for &s in &["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"] {
let s = lua.create_string(s).unwrap();
table.set(s.clone(), s).unwrap();
}
lua.create_table_from(
["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]
.into_iter()
.map(|s| (s, s)),
)
.unwrap();
},
BatchSize::SmallInput,
);
@@ -62,17 +62,15 @@ fn create_string_table(c: &mut Criterion) {
fn table_get_set(c: &mut Criterion) {
let lua = Lua::new();
let table = lua.create_table().unwrap();
c.bench_function("table raw_get and raw_set [10]", |b| {
c.bench_function("table [get and set]", |b| {
b.iter_batched(
|| {
collect_gc_twice(&lua);
table.clear().unwrap();
lua.create_table().unwrap()
},
|_| {
for (i, &s) in ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]
.iter()
|table| {
for (i, s) in ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]
.into_iter()
.enumerate()
{
table.raw_set(s, i).unwrap();
@@ -87,7 +85,7 @@ fn table_get_set(c: &mut Criterion) {
fn table_traversal_pairs(c: &mut Criterion) {
let lua = Lua::new();
c.bench_function("table traversal [pairs]", |b| {
c.bench_function("table [traversal pairs]", |b| {
b.iter_batched(
|| lua.globals(),
|globals| {
@@ -103,7 +101,7 @@ fn table_traversal_pairs(c: &mut Criterion) {
fn table_traversal_for_each(c: &mut Criterion) {
let lua = Lua::new();
c.bench_function("table traversal [for_each]", |b| {
c.bench_function("table [traversal for_each]", |b| {
b.iter_batched(
|| lua.globals(),
|globals| globals.for_each::<String, LuaValue>(|_k, _v| Ok(())),
@@ -117,7 +115,7 @@ fn table_traversal_sequence(c: &mut Criterion) {
let table = lua.create_sequence_from(1..1000).unwrap();
c.bench_function("table traversal [sequence]", |b| {
c.bench_function("table [traversal sequence]", |b| {
b.iter_batched(
|| table.clone(),
|table| {
@@ -130,236 +128,255 @@ fn table_traversal_sequence(c: &mut Criterion) {
});
}
fn create_function(c: &mut Criterion) {
fn function_create(c: &mut Criterion) {
let lua = Lua::new();
c.bench_function("create [function] 10", |b| {
c.bench_function("function [create Rust]", |b| {
b.iter_batched(
|| collect_gc_twice(&lua),
|_| {
for i in 0..10 {
lua.create_function(move |_, ()| Ok(i)).unwrap();
}
lua.create_function(|_, ()| Ok(123)).unwrap();
},
BatchSize::SmallInput,
);
});
}
fn call_lua_function(c: &mut Criterion) {
fn function_call_sum(c: &mut Criterion) {
let lua = Lua::new();
c.bench_function("call Lua function [sum] 3 10", |b| {
b.iter_batched_ref(
|| {
collect_gc_twice(&lua);
lua.load("function(a, b, c) return a + b + c end")
.eval::<LuaFunction>()
.unwrap()
},
|function| {
for i in 0..10 {
let _result: i64 = function.call((i, i + 1, i + 2)).unwrap();
}
},
BatchSize::SmallInput,
);
});
}
fn call_sum_callback(c: &mut Criterion) {
let lua = Lua::new();
let callback = lua
.create_function(|_, (a, b, c): (i64, i64, i64)| Ok(a + b + c))
let sum = lua
.create_function(|_, (a, b, c): (i64, i64, i64)| Ok(a + b - c))
.unwrap();
lua.globals().set("callback", callback).unwrap();
c.bench_function("call Rust callback [sum] 3 10", |b| {
b.iter_batched_ref(
|| {
collect_gc_twice(&lua);
lua.load("function() for i = 1,10 do callback(i, i+1, i+2) end end")
.eval::<LuaFunction>()
.unwrap()
},
|function| {
function.call::<_, ()>(()).unwrap();
c.bench_function("function [call Rust sum]", |b| {
b.iter_batched(
|| collect_gc_twice(&lua),
|_| {
assert_eq!(sum.call::<_, i64>((10, 20, 30)).unwrap(), 0);
},
BatchSize::SmallInput,
);
});
}
fn call_async_sum_callback(c: &mut Criterion) {
let options = LuaOptions::new().thread_pool_size(1024);
let lua = Lua::new_with(LuaStdLib::ALL_SAFE, options).unwrap();
let callback = lua
.create_async_function(|_, (a, b, c): (i64, i64, i64)| async move {
task::yield_now().await;
Ok(a + b + c)
})
.unwrap();
lua.globals().set("callback", callback).unwrap();
c.bench_function("call async Rust callback [sum] 3 10", |b| {
let rt = Runtime::new().unwrap();
b.to_async(rt).iter_batched(
|| {
collect_gc_twice(&lua);
lua.load("function() for i = 1,10 do callback(i, i+1, i+2) end end")
.eval::<LuaFunction>()
.unwrap()
},
|function| async move {
function.call_async::<_, ()>(()).await.unwrap();
},
BatchSize::SmallInput,
);
});
}
fn call_concat_callback(c: &mut Criterion) {
fn function_call_lua_sum(c: &mut Criterion) {
let lua = Lua::new();
let callback = lua
let sum = lua
.load("function(a, b, c) return a + b - c end")
.eval::<LuaFunction>()
.unwrap();
c.bench_function("function [call Lua sum]", |b| {
b.iter_batched(
|| collect_gc_twice(&lua),
|_| {
assert_eq!(sum.call::<_, i64>((10, 20, 30)).unwrap(), 0);
},
BatchSize::SmallInput,
);
});
}
fn function_call_concat(c: &mut Criterion) {
let lua = Lua::new();
let concat = lua
.create_function(|_, (a, b): (LuaString, LuaString)| {
Ok(format!("{}{}", a.to_str()?, b.to_str()?))
})
.unwrap();
lua.globals().set("callback", callback).unwrap();
let i = AtomicUsize::new(0);
c.bench_function("call Rust callback [concat string] 10", |b| {
b.iter_batched_ref(
c.bench_function("function [call Rust concat string]", |b| {
b.iter_batched(
|| {
collect_gc_twice(&lua);
lua.load("function() for i = 1,10 do callback('a', tostring(i)) end end")
.eval::<LuaFunction>()
.unwrap()
i.fetch_add(1, Ordering::Relaxed)
},
|function| {
function.call::<_, ()>(()).unwrap();
|i| {
assert_eq!(
concat.call::<_, LuaString>(("num:", i)).unwrap(),
format!("num:{i}")
);
},
BatchSize::SmallInput,
);
});
}
fn create_registry_values(c: &mut Criterion) {
fn function_call_lua_concat(c: &mut Criterion) {
let lua = Lua::new();
c.bench_function("create [registry value] 10", |b| {
let concat = lua
.load("function(a, b) return a..b end")
.eval::<LuaFunction>()
.unwrap();
let i = AtomicUsize::new(0);
c.bench_function("function [call Lua concat string]", |b| {
b.iter_batched(
|| collect_gc_twice(&lua),
|_| {
for _ in 0..10 {
lua.create_registry_value(lua.pack(true).unwrap()).unwrap();
}
lua.expire_registry_values();
|| {
collect_gc_twice(&lua);
i.fetch_add(1, Ordering::Relaxed)
},
|i| {
assert_eq!(
concat.call::<_, LuaString>(("num:", i)).unwrap(),
format!("num:{i}")
);
},
BatchSize::SmallInput,
);
});
}
fn create_userdata(c: &mut Criterion) {
fn function_async_call_sum(c: &mut Criterion) {
let options = LuaOptions::new().thread_pool_size(1024);
let lua = Lua::new_with(LuaStdLib::ALL_SAFE, options).unwrap();
let sum = lua
.create_async_function(|_, (a, b, c): (i64, i64, i64)| async move {
task::yield_now().await;
Ok(a + b - c)
})
.unwrap();
c.bench_function("function [async call Rust sum]", |b| {
let rt = Runtime::new().unwrap();
b.to_async(rt).iter_batched(
|| collect_gc_twice(&lua),
|_| async {
assert_eq!(sum.call_async::<_, i64>((10, 20, 30)).await.unwrap(), 0);
},
BatchSize::SmallInput,
);
});
}
fn registry_value_create(c: &mut Criterion) {
let lua = Lua::new();
lua.gc_stop();
c.bench_function("registry value [create]", |b| {
b.iter_batched(
|| collect_gc_twice(&lua),
|_| lua.create_registry_value("hello").unwrap(),
BatchSize::SmallInput,
);
});
}
fn userdata_create(c: &mut Criterion) {
struct UserData(i64);
impl LuaUserData for UserData {}
let lua = Lua::new();
c.bench_function("create [table userdata] 10", |b| {
c.bench_function("userdata [create]", |b| {
b.iter_batched(
|| collect_gc_twice(&lua),
|_| {
let table: LuaTable = lua.create_table().unwrap();
for i in 1..11 {
table.set(i, UserData(i)).unwrap();
}
lua.create_userdata(UserData(123)).unwrap();
},
BatchSize::SmallInput,
);
});
}
fn call_userdata_index(c: &mut Criterion) {
fn userdata_call_index(c: &mut Criterion) {
struct UserData(i64);
impl LuaUserData for UserData {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_method(LuaMetaMethod::Index, move |_, _, index: String| Ok(index));
methods.add_meta_method(LuaMetaMethod::Index, move |_, _, key: LuaString| Ok(key));
}
}
let lua = Lua::new();
lua.globals().set("userdata", UserData(10)).unwrap();
let ud = lua.create_userdata(UserData(123)).unwrap();
let index = lua
.load("function(ud) return ud.test end")
.eval::<LuaFunction>()
.unwrap();
c.bench_function("call [userdata index] 10", |b| {
b.iter_batched_ref(
|| {
collect_gc_twice(&lua);
lua.load("function() for i = 1,10 do local v = userdata.test end end")
.eval::<LuaFunction>()
.unwrap()
},
|function| {
function.call::<_, ()>(()).unwrap();
c.bench_function("userdata [call index]", |b| {
b.iter_batched(
|| collect_gc_twice(&lua),
|_| {
assert_eq!(index.call::<_, LuaString>(&ud).unwrap(), "test");
},
BatchSize::SmallInput,
);
});
}
fn call_userdata_method(c: &mut Criterion) {
fn userdata_call_method(c: &mut Criterion) {
struct UserData(i64);
impl LuaUserData for UserData {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_method("method", |_, this, ()| Ok(this.0));
methods.add_method("add", |_, this, i: i64| Ok(this.0 + i));
}
}
let lua = Lua::new();
lua.globals().set("userdata", UserData(10)).unwrap();
let ud = lua.create_userdata(UserData(123)).unwrap();
let method = lua
.load("function(ud, i) return ud:add(i) end")
.eval::<LuaFunction>()
.unwrap();
let i = AtomicUsize::new(0);
c.bench_function("call [userdata method] 10", |b| {
b.iter_batched_ref(
c.bench_function("userdata [call method]", |b| {
b.iter_batched(
|| {
collect_gc_twice(&lua);
lua.load("function() for i = 1,10 do userdata:method() end end")
.eval::<LuaFunction>()
.unwrap()
i.fetch_add(1, Ordering::Relaxed)
},
|function| {
function.call::<_, ()>(()).unwrap();
|i| {
assert_eq!(method.call::<_, usize>((&ud, i)).unwrap(), 123 + i);
},
BatchSize::SmallInput,
);
});
}
fn call_async_userdata_method(c: &mut Criterion) {
struct UserData(String);
fn userdata_async_call_method(c: &mut Criterion) {
struct UserData(i64);
impl LuaUserData for UserData {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_async_method("method", |_, this, ()| async move { Ok(this.0.clone()) });
methods.add_async_method("add", |_, this, i: i64| async move {
task::yield_now().await;
Ok(this.0 + i)
});
}
}
let options = LuaOptions::new().thread_pool_size(1024);
let lua = Lua::new_with(LuaStdLib::ALL_SAFE, options).unwrap();
lua.globals()
.set("userdata", UserData("hello".to_string()))
let ud = lua.create_userdata(UserData(123)).unwrap();
let method = lua
.load("function(ud, i) return ud:add(i) end")
.eval::<LuaFunction>()
.unwrap();
let i = AtomicUsize::new(0);
c.bench_function("call async [userdata method] 10", |b| {
c.bench_function("userdata [async call method] 10", |b| {
let rt = Runtime::new().unwrap();
b.to_async(rt).iter_batched(
|| {
collect_gc_twice(&lua);
lua.load("function() for i = 1,10 do userdata:method() end end")
.eval::<LuaFunction>()
.unwrap()
(
method.clone(),
ud.clone(),
i.fetch_add(1, Ordering::Relaxed),
)
},
|function| async move {
function.call_async::<_, ()>(()).await.unwrap();
|(method, ud, i)| async move {
assert_eq!(
method.call_async::<_, usize>((ud, i)).await.unwrap(),
123 + i
);
},
BatchSize::SmallInput,
);
@@ -369,27 +386,31 @@ fn call_async_userdata_method(c: &mut Criterion) {
criterion_group! {
name = benches;
config = Criterion::default()
.sample_size(300)
.sample_size(500)
.measurement_time(Duration::from_secs(10))
.noise_threshold(0.02);
targets =
create_table,
create_array,
create_string_table,
table_create_empty,
table_create_array,
table_create_hash,
table_get_set,
table_traversal_pairs,
table_traversal_for_each,
table_traversal_sequence,
create_function,
call_lua_function,
call_sum_callback,
call_async_sum_callback,
call_concat_callback,
create_registry_values,
create_userdata,
call_userdata_index,
call_userdata_method,
call_async_userdata_method,
function_create,
function_call_sum,
function_call_lua_sum,
function_call_concat,
function_call_lua_concat,
function_async_call_sum,
registry_value_create,
userdata_create,
userdata_call_index,
userdata_call_method,
userdata_async_call_method,
}
criterion_main!(benches);
+25 -33
View File
@@ -8,43 +8,35 @@ fn collect_gc_twice(lua: &Lua) {
lua.gc_collect().unwrap();
}
fn serialize_json(c: &mut Criterion) {
fn encode_json(c: &mut Criterion) {
let lua = Lua::new();
lua.globals()
.set(
"encode",
LuaFunction::wrap(|_, t: LuaValue| Ok(serde_json::to_string(&t).unwrap())),
let encode = lua
.create_function(|_, t: LuaValue| Ok(serde_json::to_string(&t).unwrap()))
.unwrap();
let table = lua
.load(
r#"{
name = "Clark Kent",
address = {
city = "Smallville",
state = "Kansas",
country = "USA",
},
age = 22,
parents = {"Jonathan Kent", "Martha Kent"},
superman = true,
interests = {"flying", "saving the world", "kryptonite"},
}"#,
)
.eval::<LuaTable>()
.unwrap();
c.bench_function("serialize table to json [10]", |b| {
c.bench_function("serialize json", |b| {
b.iter_batched(
|| {
collect_gc_twice(&lua);
lua.load(
r#"
local encode = encode
return function()
for i = 1, 10 do
encode({
name = "Clark Kent",
nickname = "Superman",
address = {
city = "Metropolis",
},
age = 32,
superman = true,
})
end
end
"#,
)
.eval::<LuaFunction>()
.unwrap()
},
|func| {
func.call::<_, ()>(()).unwrap();
|| collect_gc_twice(&lua),
|_| {
encode.call::<_, LuaString>(&table).unwrap();
},
BatchSize::SmallInput,
);
@@ -54,11 +46,11 @@ fn serialize_json(c: &mut Criterion) {
criterion_group! {
name = benches;
config = Criterion::default()
.sample_size(300)
.sample_size(500)
.measurement_time(Duration::from_secs(10))
.noise_threshold(0.02);
targets =
serialize_json,
encode_json,
}
criterion_main!(benches);
+6 -3
View File
@@ -144,9 +144,12 @@ pub unsafe fn luaL_sandbox(L: *mut lua_State, enabled: c_int) {
// set all builtin metatables to read-only
lua_pushliteral(L, "");
lua_getmetatable(L, -1);
lua_setreadonly(L, -1, enabled);
lua_pop(L, 2);
if lua_getmetatable(L, -1) != 0 {
lua_setreadonly(L, -1, enabled);
lua_pop(L, 2);
} else {
lua_pop(L, 1);
}
// set globals to readonly and activate safeenv since the env is immutable
lua_setreadonly(L, LUA_GLOBALSINDEX, enabled);
+39
View File
@@ -33,6 +33,18 @@ impl<'lua> IntoLua<'lua> for Value<'lua> {
}
}
impl<'lua> IntoLua<'lua> for &Value<'lua> {
#[inline]
fn into_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
Ok(self.clone())
}
#[inline]
unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> {
lua.push_value_ref(self)
}
}
impl<'lua> FromLua<'lua> for Value<'lua> {
#[inline]
fn from_lua(lua_value: Value<'lua>, _: &'lua Lua) -> Result<Self> {
@@ -429,11 +441,24 @@ impl<'lua> IntoLua<'lua> for &RegistryKey {
}
}
impl<'lua> FromLua<'lua> for RegistryKey {
#[inline]
fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result<RegistryKey> {
lua.create_registry_value(value)
}
}
impl<'lua> IntoLua<'lua> for bool {
#[inline]
fn into_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Boolean(self))
}
#[inline]
unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> {
ffi::lua_pushboolean(lua.state(), self as c_int);
Ok(())
}
}
impl<'lua> FromLua<'lua> for bool {
@@ -445,6 +470,11 @@ impl<'lua> FromLua<'lua> for bool {
_ => Ok(true),
}
}
#[inline]
unsafe fn from_stack(idx: c_int, lua: &'lua Lua) -> Result<Self> {
Ok(ffi::lua_toboolean(lua.state(), idx) != 0)
}
}
impl<'lua> IntoLua<'lua> for LightUserData {
@@ -686,6 +716,15 @@ macro_rules! lua_convert_int {
message: Some("out of range".to_owned()),
})
}
#[inline]
unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> {
match cast(self) {
Some(i) => ffi::lua_pushinteger(lua.state(), i),
None => ffi::lua_pushnumber(lua.state(), self as ffi::lua_Number),
}
Ok(())
}
}
impl<'lua> FromLua<'lua> for $x {
+10
View File
@@ -494,6 +494,16 @@ impl<'lua> Function<'lua> {
}
}
/// Converts this function to a generic C pointer.
///
/// There is no way to convert the pointer back to its original value.
///
/// Typically this function is used only for hashing and debug information.
#[inline]
pub fn to_pointer(&self) -> *const c_void {
self.0.to_pointer()
}
/// Convert this handle to owned version.
#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))]
+46 -58
View File
@@ -226,6 +226,7 @@ pub(crate) static EXTRA_REGISTRY_KEY: u8 = 0;
const WRAPPED_FAILURE_POOL_SIZE: usize = 64;
const MULTIVALUE_POOL_SIZE: usize = 64;
const REF_STACK_RESERVE: c_int = 1;
/// Requires `feature = "send"`
#[cfg(feature = "send")]
@@ -519,8 +520,8 @@ impl Lua {
#[cfg(feature = "module")]
skip_memory_check: false,
ref_thread,
// We need 1 extra stack space to move values in and out of the ref stack.
ref_stack_size: ffi::LUA_MINSTACK - 1,
// We need some reserved stack space to move values in and out of the ref stack.
ref_stack_size: ffi::LUA_MINSTACK - REF_STACK_RESERVE,
ref_stack_top: ffi::lua_gettop(ref_thread),
ref_free: Vec::new(),
wrapped_failure_pool: Vec::with_capacity(WRAPPED_FAILURE_POOL_SIZE),
@@ -1413,8 +1414,8 @@ impl Lua {
let protect = !self.unlikely_memory_error();
push_table(state, 0, lower_bound, protect)?;
for (k, v) in iter {
self.push_value(k.into_lua(self)?)?;
self.push_value(v.into_lua(self)?)?;
self.push(k)?;
self.push(v)?;
if protect {
protect_lua!(state, 3, 1, fn(state) ffi::lua_rawset(state, -3))?;
} else {
@@ -1442,7 +1443,7 @@ impl Lua {
let protect = !self.unlikely_memory_error();
push_table(state, lower_bound, 0, protect)?;
for (i, v) in iter.enumerate() {
self.push_value(v.into_lua(self)?)?;
self.push(v)?;
if protect {
protect_lua!(state, 2, 1, |state| {
ffi::lua_rawseti(state, -2, (i + 1) as Integer);
@@ -1977,12 +1978,11 @@ impl Lua {
T: IntoLua<'lua>,
{
let state = self.state();
let t = t.into_lua(self)?;
unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 5)?;
self.push_value(t)?;
self.push(t)?;
rawset_field(state, ffi::LUA_REGISTRYINDEX, name)
}
}
@@ -2269,33 +2269,38 @@ impl Lua {
extra.app_data.remove()
}
/// Pushes a value onto the Lua stack.
/// Pushes a value that implements `IntoLua` onto the Lua stack.
///
/// Uses 2 stack spaces, does not call checkstack.
#[doc(hidden)]
#[inline(always)]
pub unsafe fn push<'lua>(&'lua self, value: impl IntoLua<'lua>) -> Result<()> {
value.push_into_stack(self)
}
/// Pushes a `Value` onto the Lua stack.
///
/// Uses 2 stack spaces, does not call checkstack.
#[doc(hidden)]
pub unsafe fn push_value(&self, value: Value) -> Result<()> {
if let Value::Error(err) = value {
let protect = !self.unlikely_memory_error();
return push_gc_userdata(self.state(), WrappedFailure::Error(err), protect);
}
self.push_value_ref(&value)
}
/// Pushes a `&Value` (by reference) onto the Lua stack.
///
/// Similar to [`Lua::push_value`], uses 2 stack spaces, does not call checkstack.
pub(crate) unsafe fn push_value_ref(&self, value: &Value) -> Result<()> {
let state = self.state();
match value {
Value::Nil => {
ffi::lua_pushnil(state);
}
Value::Boolean(b) => {
ffi::lua_pushboolean(state, b as c_int);
}
Value::LightUserData(ud) => {
ffi::lua_pushlightuserdata(state, ud.0);
}
Value::Integer(i) => {
ffi::lua_pushinteger(state, i);
}
Value::Number(n) => {
ffi::lua_pushnumber(state, n);
}
Value::Nil => ffi::lua_pushnil(state),
Value::Boolean(b) => ffi::lua_pushboolean(state, *b as c_int),
Value::LightUserData(ud) => ffi::lua_pushlightuserdata(state, ud.0),
Value::Integer(i) => ffi::lua_pushinteger(state, *i),
Value::Number(n) => ffi::lua_pushnumber(state, *n),
#[cfg(feature = "luau")]
Value::Vector(v) => {
#[cfg(not(feature = "luau-vector4"))]
@@ -2303,33 +2308,16 @@ impl Lua {
#[cfg(feature = "luau-vector4")]
ffi::lua_pushvector(state, v.x(), v.y(), v.z(), v.w());
}
Value::String(s) => {
self.push_ref(&s.0);
}
Value::Table(t) => {
self.push_ref(&t.0);
}
Value::Function(f) => {
self.push_ref(&f.0);
}
Value::Thread(t) => {
self.push_ref(&t.0);
}
Value::UserData(ud) => {
self.push_ref(&ud.0);
}
Value::String(s) => self.push_ref(&s.0),
Value::Table(t) => self.push_ref(&t.0),
Value::Function(f) => self.push_ref(&f.0),
Value::Thread(t) => self.push_ref(&t.0),
Value::UserData(ud) => self.push_ref(&ud.0),
Value::Error(err) => {
let protect = !self.unlikely_memory_error();
push_gc_userdata(state, WrappedFailure::Error(err), protect)?;
push_gc_userdata(state, WrappedFailure::Error(err.clone()), protect)?;
}
}
Ok(())
}
@@ -2643,12 +2631,12 @@ impl Lua {
let metatable_nrec = metatable_nrec + registry.async_meta_methods.len();
push_table(state, 0, metatable_nrec, true)?;
for (k, m) in registry.meta_methods {
self.push_value(Value::Function(self.create_callback(m)?))?;
self.push(self.create_callback(m)?)?;
rawset_field(state, -2, MetaMethod::validate(&k)?)?;
}
#[cfg(feature = "async")]
for (k, m) in registry.async_meta_methods {
self.push_value(Value::Function(self.create_async_callback(m)?))?;
self.push(self.create_async_callback(m)?)?;
rawset_field(state, -2, MetaMethod::validate(&k)?)?;
}
let mut has_name = false;
@@ -2699,7 +2687,7 @@ impl Lua {
if field_getters_nrec > 0 {
push_table(state, 0, field_getters_nrec, true)?;
for (k, m) in registry.field_getters {
self.push_value(Value::Function(self.create_callback(m)?))?;
self.push(self.create_callback(m)?)?;
rawset_field(state, -2, &k)?;
}
field_getters_index = Some(ffi::lua_absindex(state, -1));
@@ -2711,7 +2699,7 @@ impl Lua {
if field_setters_nrec > 0 {
push_table(state, 0, field_setters_nrec, true)?;
for (k, m) in registry.field_setters {
self.push_value(Value::Function(self.create_callback(m)?))?;
self.push(self.create_callback(m)?)?;
rawset_field(state, -2, &k)?;
}
field_setters_index = Some(ffi::lua_absindex(state, -1));
@@ -2734,12 +2722,12 @@ impl Lua {
}
}
for (k, m) in registry.methods {
self.push_value(Value::Function(self.create_callback(m)?))?;
self.push(self.create_callback(m)?)?;
rawset_field(state, -2, &k)?;
}
#[cfg(feature = "async")]
for (k, m) in registry.async_methods {
self.push_value(Value::Function(self.create_async_callback(m)?))?;
self.push(self.create_async_callback(m)?)?;
rawset_field(state, -2, &k)?;
}
match index_type {
@@ -2990,7 +2978,7 @@ impl Lua {
nresults => {
let results = MultiValue::from_stack_multi(nresults, lua)?;
ffi::lua_pushinteger(state, nresults as _);
lua.push_value(Value::Table(lua.create_sequence_from(results)?))?;
lua.push(lua.create_sequence_from(results)?)?;
Ok(2)
}
}
+28
View File
@@ -23,6 +23,20 @@ impl<'lua, T: IntoLua<'lua>, E: IntoLua<'lua>> IntoLuaMulti<'lua> for StdResult<
}
Ok(result)
}
#[inline]
unsafe fn push_into_stack_multi(self, lua: &'lua Lua) -> Result<c_int> {
match self {
Ok(v) => v.push_into_stack(lua).map(|_| 1),
Err(e) => {
let state = lua.state();
check_stack(state, 3)?;
ffi::lua_pushnil(state);
e.push_into_stack(lua)?;
Ok(2)
}
}
}
}
impl<'lua, E: IntoLua<'lua>> IntoLuaMulti<'lua> for StdResult<(), E> {
@@ -38,6 +52,20 @@ impl<'lua, E: IntoLua<'lua>> IntoLuaMulti<'lua> for StdResult<(), E> {
}
}
}
#[inline]
unsafe fn push_into_stack_multi(self, lua: &'lua Lua) -> Result<c_int> {
match self {
Ok(_) => Ok(0),
Err(e) => {
let state = lua.state();
check_stack(state, 3)?;
ffi::lua_pushnil(state);
e.push_into_stack(lua)?;
Ok(2)
}
}
}
}
impl<'lua, T: IntoLua<'lua>> IntoLuaMulti<'lua> for T {
+5 -5
View File
@@ -19,7 +19,7 @@ use crate::util::{
self, assert_stack, check_stack, init_userdata_metatable, push_string, push_table,
rawset_field, short_type_name, take_userdata, StackGuard,
};
use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Value};
use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti};
#[cfg(feature = "lua54")]
use crate::userdata::USER_VALUE_MAXSLOT;
@@ -405,7 +405,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
let meta_methods_nrec = registry.meta_methods.len() + registry.meta_fields.len() + 1;
push_table(state, 0, meta_methods_nrec, true)?;
for (k, m) in registry.meta_methods {
lua.push_value(Value::Function(wrap_method(self, ud_ptr, &k, m)?))?;
lua.push(wrap_method(self, ud_ptr, &k, m)?)?;
rawset_field(state, -2, MetaMethod::validate(&k)?)?;
}
let mut has_name = false;
@@ -455,7 +455,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
if field_getters_nrec > 0 {
push_table(state, 0, field_getters_nrec, true)?;
for (k, m) in registry.field_getters {
lua.push_value(Value::Function(wrap_method(self, ud_ptr, &k, m)?))?;
lua.push(wrap_method(self, ud_ptr, &k, m)?)?;
rawset_field(state, -2, &k)?;
}
field_getters_index = Some(ffi::lua_absindex(state, -1));
@@ -466,7 +466,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
if field_setters_nrec > 0 {
push_table(state, 0, field_setters_nrec, true)?;
for (k, m) in registry.field_setters {
lua.push_value(Value::Function(wrap_method(self, ud_ptr, &k, m)?))?;
lua.push(wrap_method(self, ud_ptr, &k, m)?)?;
rawset_field(state, -2, &k)?;
}
field_setters_index = Some(ffi::lua_absindex(state, -1));
@@ -478,7 +478,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
// Create table used for methods lookup
push_table(state, 0, methods_nrec, true)?;
for (k, m) in registry.methods {
lua.push_value(Value::Function(wrap_method(self, ud_ptr, &k, m)?))?;
lua.push(wrap_method(self, ud_ptr, &k, m)?)?;
rawset_field(state, -2, &k)?;
}
methods_index = Some(ffi::lua_absindex(state, -1));
+1 -1
View File
@@ -132,7 +132,7 @@ impl<'lua> String<'lua> {
}
}
/// Converts the string to a generic C pointer.
/// Converts this string to a generic C pointer.
///
/// There is no way to convert the pointer back to its original value.
///
+42 -33
View File
@@ -591,7 +591,7 @@ impl<'lua> Table<'lua> {
unsafe { ffi::lua_getreadonly(ref_thread, self.0.index) != 0 }
}
/// Converts the table to a generic C pointer.
/// Converts this table to a generic C pointer.
///
/// Different tables will give different pointers.
/// There is no way to convert the pointer back to its original value.
@@ -721,7 +721,6 @@ impl<'lua> Table<'lua> {
TableSequence {
table: self.0,
index: 1,
len: None,
_phantom: PhantomData,
}
}
@@ -733,17 +732,25 @@ impl<'lua> Table<'lua> {
}
#[cfg(feature = "serialize")]
pub(crate) fn sequence_values_by_len<V: FromLua<'lua>>(
self,
len: Option<usize>,
) -> TableSequence<'lua, V> {
let len = len.unwrap_or_else(|| self.raw_len()) as Integer;
TableSequence {
table: self.0,
index: 1,
len: Some(len),
_phantom: PhantomData,
pub(crate) fn for_each_value<V>(&self, mut f: impl FnMut(V) -> Result<()>) -> Result<()>
where
V: FromLua<'lua>,
{
let lua = self.0.lua;
let state = lua.state();
unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 4)?;
lua.push_ref(&self.0);
let len = ffi::lua_rawlen(state, -1);
for i in 1..=len {
ffi::lua_rawgeti(state, -1, i as _);
f(V::from_stack(-1, lua)?)?;
ffi::lua_pop(state, 1);
}
}
Ok(())
}
/// Sets element value at position `idx` without invoking metamethods.
@@ -1085,6 +1092,13 @@ impl<'a, 'lua> Serialize for SerializableTable<'a, 'lua> {
use crate::serde::de::{check_value_for_skip, MapPairs};
use crate::value::SerializableValue;
let convert_result = |res: Result<()>, serialize_err: Option<S::Error>| match res {
Ok(v) => Ok(v),
Err(Error::SerializeError(_)) if serialize_err.is_some() => Err(serialize_err.unwrap()),
Err(Error::SerializeError(msg)) => Err(serde::ser::Error::custom(msg)),
Err(err) => Err(serde::ser::Error::custom(err.to_string())),
};
let options = self.options;
let visited = &self.visited;
visited.borrow_mut().insert(self.table.to_pointer());
@@ -1093,15 +1107,21 @@ impl<'a, 'lua> Serialize for SerializableTable<'a, 'lua> {
let len = self.table.raw_len();
if len > 0 || self.table.is_array() {
let mut seq = serializer.serialize_seq(Some(len))?;
for value in self.table.clone().sequence_values_by_len::<Value>(None) {
let value = &value.map_err(serde::ser::Error::custom)?;
let skip = check_value_for_skip(value, self.options, &self.visited)
.map_err(serde::ser::Error::custom)?;
let mut serialize_err = None;
let res = self.table.for_each_value::<Value>(|value| {
let skip = check_value_for_skip(&value, self.options, &self.visited)
.map_err(|err| Error::SerializeError(err.to_string()))?;
if skip {
continue;
// continue iteration
return Ok(());
}
seq.serialize_element(&SerializableValue::new(value, options, Some(visited)))?;
}
seq.serialize_element(&SerializableValue::new(&value, options, Some(visited)))
.map_err(|err| {
serialize_err = Some(err);
Error::SerializeError(String::new())
})
});
convert_result(res, serialize_err)?;
return seq.end();
}
@@ -1138,18 +1158,7 @@ impl<'a, 'lua> Serialize for SerializableTable<'a, 'lua> {
process_pair(key, value)
})
};
match res {
Ok(_) => {}
Err(Error::SerializeError(_)) if serialize_err.is_some() => {
return Err(serialize_err.unwrap());
}
Err(Error::SerializeError(msg)) => {
return Err(serde::ser::Error::custom(msg));
}
Err(err) => {
return Err(serde::ser::Error::custom(err.to_string()));
}
}
convert_result(res, serialize_err)?;
map.end()
}
}
@@ -1219,9 +1228,9 @@ where
///
/// [`Table::sequence_values`]: crate::Table::sequence_values
pub struct TableSequence<'lua, V> {
// TODO: Use `&Table`
table: LuaRef<'lua>,
index: Integer,
len: Option<Integer>,
_phantom: PhantomData<V>,
}
@@ -1242,7 +1251,7 @@ where
lua.push_ref(&self.table);
match ffi::lua_rawgeti(state, -1, self.index) {
ffi::LUA_TNIL if self.index > self.len.unwrap_or(0) => None,
ffi::LUA_TNIL => None,
_ => {
self.index += 1;
Some(V::from_stack(-1, lua))
+11 -1
View File
@@ -1,4 +1,4 @@
use std::os::raw::c_int;
use std::os::raw::{c_int, c_void};
use crate::error::{Error, Result};
#[allow(unused)]
@@ -375,6 +375,16 @@ impl<'lua> Thread<'lua> {
}
}
/// Converts this thread to a generic C pointer.
///
/// There is no way to convert the pointer back to its original value.
///
/// Typically this function is used only for hashing and debug information.
#[inline]
pub fn to_pointer(&self) -> *const c_void {
self.0.to_pointer()
}
/// Convert this handle to owned version.
#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))]
+13 -3
View File
@@ -5,7 +5,7 @@ use std::fmt;
use std::hash::Hash;
use std::mem;
use std::ops::{Deref, DerefMut};
use std::os::raw::{c_char, c_int};
use std::os::raw::{c_char, c_int, c_void};
use std::string::String as StdString;
#[cfg(feature = "async")]
@@ -919,7 +919,7 @@ impl<'lua> AnyUserData<'lua> {
check_stack(state, 5)?;
lua.push_userdata_ref(&self.0)?;
lua.push_value(v.into_lua(lua)?)?;
lua.push(v)?;
#[cfg(feature = "lua54")]
if n < USER_VALUE_MAXSLOT {
@@ -1014,7 +1014,7 @@ impl<'lua> AnyUserData<'lua> {
check_stack(state, 5)?;
lua.push_userdata_ref(&self.0)?;
lua.push_value(v.into_lua(lua)?)?;
lua.push(v)?;
// Multiple (extra) user values are emulated by storing them in a table
protect_lua!(state, 2, 0, |state| {
@@ -1096,6 +1096,16 @@ impl<'lua> AnyUserData<'lua> {
}
}
/// Converts this userdata to a generic C pointer.
///
/// There is no way to convert the pointer back to its original value.
///
/// Typically this function is used only for hashing and debug information.
#[inline]
pub fn to_pointer(&self) -> *const c_void {
self.0.to_pointer()
}
/// Convert this handle to owned version.
#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))]
+2
View File
@@ -1045,6 +1045,8 @@ pub(crate) unsafe fn to_string(state: *mut ffi::lua_State, index: c_int) -> Stri
ffi::LUA_TTHREAD => format!("<thread {:?}>", ffi::lua_topointer(state, index)),
#[cfg(feature = "luau")]
ffi::LUA_TBUFFER => format!("<buffer {:?}>", ffi::lua_topointer(state, index)),
#[cfg(feature = "luajit")]
ffi::LUA_TCDATA => format!("<cdata {:?}>", ffi::lua_topointer(state, index)),
_ => "<unknown>".to_string(),
}
}
+32 -1
View File
@@ -3,7 +3,27 @@ use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::ffi::{CStr, CString};
use maplit::{btreemap, btreeset, hashmap, hashset};
use mlua::{AnyUserData, Error, Function, IntoLua, Lua, Result, Table, Thread, UserDataRef, Value};
use mlua::{
AnyUserData, Error, Function, IntoLua, Lua, RegistryKey, Result, Table, Thread, UserDataRef,
Value,
};
#[test]
fn test_value_into_lua() -> Result<()> {
let lua = Lua::new();
// Direct conversion
let v = Value::Boolean(true);
let v2 = (&v).into_lua(&lua)?;
assert_eq!(v, v2);
// Push into stack
let table = lua.create_table()?;
table.set("v", &v)?;
assert_eq!(v, table.get::<_, Value>("v")?);
Ok(())
}
#[test]
fn test_string_into_lua() -> Result<()> {
@@ -237,6 +257,17 @@ fn test_registry_value_into_lua() -> Result<()> {
Ok(())
}
#[test]
fn test_registry_key_from_lua() -> Result<()> {
let lua = Lua::new();
let fkey = lua.load("function() return 1 end").eval::<RegistryKey>()?;
let f = lua.registry_value::<Function>(&fkey)?;
assert_eq!(f.call::<_, i32>(())?, 1);
Ok(())
}
#[test]
fn test_conv_vec() -> Result<()> {
let lua = Lua::new();
+13
View File
@@ -231,6 +231,19 @@ fn test_function_info() -> Result<()> {
Ok(())
}
#[test]
fn test_function_pointer() -> Result<()> {
let lua = Lua::new();
let func1 = lua.load("return function() end").into_function()?;
let func2 = func1.call::<_, Function>(())?;
assert_eq!(func1.to_pointer(), func1.clone().to_pointer());
assert_ne!(func1.to_pointer(), func2.to_pointer());
Ok(())
}
#[test]
fn test_function_wrap() -> Result<()> {
use mlua::Error;
+16
View File
@@ -275,6 +275,22 @@ fn test_sandbox() -> Result<()> {
Ok(())
}
#[test]
fn test_sandbox_nolibs() -> Result<()> {
let lua = Lua::new_with(StdLib::NONE, LuaOptions::default()).unwrap();
lua.sandbox(true)?;
lua.load("global = 123").exec()?;
let n: i32 = lua.load("return global").eval()?;
assert_eq!(n, 123);
assert_eq!(lua.globals().get::<_, Option<i32>>("global")?, Some(123));
lua.sandbox(false)?;
assert_eq!(lua.globals().get::<_, Option<i32>>("global")?, None);
Ok(())
}
#[test]
fn test_sandbox_threads() -> Result<()> {
let lua = Lua::new();
+60
View File
@@ -0,0 +1,60 @@
use mlua::{Error, ExternalError, IntoLuaMulti, Lua, Result, String, Value};
#[test]
fn test_result_conversions() -> Result<()> {
let lua = Lua::new();
let globals = lua.globals();
let ok = lua.create_function(|_, ()| Ok(Ok::<(), Error>(())))?;
let err = lua.create_function(|_, ()| Ok(Err::<(), _>("failure1".into_lua_err())))?;
let ok2 = lua.create_function(|_, ()| Ok(Ok::<_, Error>("!".to_owned())))?;
let err2 = lua.create_function(|_, ()| Ok(Err::<String, _>("failure2".into_lua_err())))?;
globals.set("ok", ok)?;
globals.set("ok2", ok2)?;
globals.set("err", err)?;
globals.set("err2", err2)?;
lua.load(
r#"
local r, e = ok()
assert(r == nil and e == nil)
local r, e = err()
assert(r == nil)
assert(tostring(e):find("failure1") ~= nil)
local r, e = ok2()
assert(r == "!")
assert(e == nil)
local r, e = err2()
assert(r == nil)
assert(tostring(e):find("failure2") ~= nil)
"#,
)
.exec()?;
// Try to convert Result into MultiValue
let ok1 = Ok::<(), Error>(());
let multi_ok1 = ok1.into_lua_multi(&lua)?;
assert_eq!(multi_ok1.len(), 0);
let err1 = Err::<(), _>("failure1");
let multi_err1 = err1.into_lua_multi(&lua)?;
assert_eq!(multi_err1.len(), 2);
assert_eq!(multi_err1[0], Value::Nil);
assert_eq!(multi_err1[1].as_str().unwrap(), "failure1");
let ok2 = Ok::<_, Error>("!");
let multi_ok2 = ok2.into_lua_multi(&lua)?;
assert_eq!(multi_ok2.len(), 1);
assert_eq!(multi_ok2[0].as_str().unwrap(), "!");
let err2 = Err::<String, _>("failure2".into_lua_err());
let multi_err2 = err2.into_lua_multi(&lua)?;
assert_eq!(multi_err2.len(), 2);
assert_eq!(multi_err2[0], Value::Nil);
assert!(matches!(multi_err2[1], Value::Error(_)));
assert_eq!(multi_err2[1].to_string()?, "failure2");
Ok(())
}
+13
View File
@@ -99,6 +99,19 @@ fn test_string_debug() -> Result<()> {
Ok(())
}
#[test]
fn test_string_pointer() -> Result<()> {
let lua = Lua::new();
let str1 = lua.create_string("hello")?;
let str2 = lua.create_string("hello")?;
// Lua uses string interning, so these should be the same
assert_eq!(str1.to_pointer(), str2.to_pointer());
Ok(())
}
#[cfg(all(feature = "unstable", not(feature = "send")))]
#[test]
fn test_owned_string() -> Result<()> {
+13
View File
@@ -369,6 +369,19 @@ fn test_table_eq() -> Result<()> {
Ok(())
}
#[test]
fn test_table_pointer() -> Result<()> {
let lua = Lua::new();
let table1 = lua.create_table()?;
let table2 = lua.create_table()?;
assert_eq!(table1.to_pointer(), table1.clone().to_pointer());
assert_ne!(table1.to_pointer(), table2.to_pointer());
Ok(())
}
#[test]
fn test_table_error() -> Result<()> {
let lua = Lua::new();
-38
View File
@@ -504,44 +504,6 @@ fn test_panic() -> Result<()> {
Ok(())
}
#[test]
fn test_result_conversions() -> Result<()> {
let lua = Lua::new();
let globals = lua.globals();
let ok = lua.create_function(|_, ()| Ok(Ok::<(), Error>(())))?;
let err = lua.create_function(|_, ()| Ok(Err::<(), _>("failure1".into_lua_err())))?;
let ok2 = lua.create_function(|_, ()| Ok(Ok::<_, Error>("!".to_owned())))?;
let err2 = lua.create_function(|_, ()| Ok(Err::<String, _>("failure2".into_lua_err())))?;
globals.set("ok", ok)?;
globals.set("ok2", ok2)?;
globals.set("err", err)?;
globals.set("err2", err2)?;
lua.load(
r#"
local r, e = ok()
assert(r == nil and e == nil)
local r, e = err()
assert(r == nil)
assert(tostring(e):find("failure1") ~= nil)
local r, e = ok2()
assert(r == "!")
assert(e == nil)
local r, e = err2()
assert(r == nil)
assert(tostring(e):find("failure2") ~= nil)
"#,
)
.exec()?;
Ok(())
}
#[test]
fn test_num_conversion() -> Result<()> {
let lua = Lua::new();
+13
View File
@@ -191,6 +191,19 @@ fn test_coroutine_panic() {
}
}
#[test]
fn test_thread_pointer() -> Result<()> {
let lua = Lua::new();
let func = lua.load("return 123").into_function()?;
let thread = lua.create_thread(func.clone())?;
assert_eq!(thread.to_pointer(), thread.clone().to_pointer());
assert_ne!(thread.to_pointer(), lua.current_thread().to_pointer());
Ok(())
}
#[cfg(all(feature = "unstable", not(feature = "send")))]
#[test]
fn test_owned_thread() -> Result<()> {
+14
View File
@@ -951,6 +951,20 @@ fn test_userdata_method_errors() -> Result<()> {
Ok(())
}
#[test]
fn test_userdata_pointer() -> Result<()> {
let lua = Lua::new();
let ud1 = lua.create_any_userdata("hello")?;
let ud2 = lua.create_any_userdata("hello")?;
assert_eq!(ud1.to_pointer(), ud1.clone().to_pointer());
// Different userdata objects with the same value should have different pointers
assert_ne!(ud1.to_pointer(), ud2.to_pointer());
Ok(())
}
#[cfg(all(feature = "unstable", not(feature = "send")))]
#[test]
fn test_owned_userdata() -> Result<()> {