Compare commits

...

18 Commits

Author SHA1 Message Date
Alex Orlenko f1ceaf0ff1 v0.9.9 2024-06-18 16:09:08 +01:00
Alex Orlenko 884a025b52 Fix some clippy warnings 2024-06-18 16:09:08 +01:00
Alex Orlenko c23fa5aa6c Optimize RegistryKey internals
- Store single `AtomicI32` field instead of pair i32,AtomicBool
- Make creation faster by skipping intermediate `Value` layer
Add new `RegistryKey::id()` method to return underlying identifier
2024-06-18 16:09:06 +01:00
Alex Orlenko 4f1d2abbcb Bump rustc-hash to 2.0 2024-06-18 11:18:55 +01:00
Alex Orlenko a25e81036e Do not allow already running coroutines to be reset or resumed.
This is a wrong use of the Lua API and is not supported.
See #416 for the reference.
2024-06-12 23:18:46 +01:00
Alex Orlenko b46cad1db1 Support Luau v0.629 2024-06-09 00:37:58 +01:00
Alex Orlenko f2d48ce296 v0.9.8 2024-05-15 23:17:30 +01:00
Alex Orlenko 0fa39d431d mlua_derive: v0.9.3 2024-05-15 23:14:23 +01:00
Alex Orlenko 0aa86c4d47 Update CHANGELOG 2024-05-15 23:11:50 +01:00
Alex Orlenko ea2faa3755 clippy 2024-05-15 23:11:33 +01:00
Alex Orlenko 59c9abbac7 Fix serializing same table multiple times.
Fixes #408
2024-05-14 00:42:04 +01:00
Alex Orlenko 8f3de8aa19 mlua-sys: v0.6.0 2024-05-04 21:22:17 +01:00
Alex Orlenko 317ce7caa6 Add Lua::set_fflag() to control Luau feature flags 2024-05-04 21:15:22 +01:00
Alex Orlenko 3a44729a48 Mark lua_Callbacks as non exhaustive (Luau) 2024-05-04 21:11:29 +01:00
Alex Orlenko 3d46fad459 Update luau-src to v0.9
Mark `lua_CompileOptions` as non exhaustive
2024-05-04 21:10:24 +01:00
Joris Willems ffc4bd599c Fix module imports for export (#394) 2024-04-18 10:22:41 +01:00
Eric Stokes 1c969da286 update build script to fix cross compilation of windows dlls from unix (#397) 2024-04-17 20:29:41 +01:00
Alex Orlenko 45fd2fa40a mlua-sys: v0.5.2 2024-04-05 14:04:15 +01:00
25 changed files with 306 additions and 137 deletions
+12
View File
@@ -1,3 +1,15 @@
## v0.9.9
- Minimal Luau updated to 0.629
- Fixed bug when attempting to reset or resume already running coroutines (#416).
- Added `RegistryKey::id()` method to get the underlying Lua registry key id.
## v0.9.8
- Fixed serializing same table multiple times (#408)
- Use `mlua-sys` v0.6 (to support Luau 0.624+)
- Fixed cross compilation of windows dlls from unix (#394)
## v0.9.7
- Implemented `IntoLua` for `RegistryKey`
+8 -5
View File
@@ -1,6 +1,6 @@
[package]
name = "mlua"
version = "0.9.7" # remember to update mlua_derive
version = "0.9.9" # remember to update mlua_derive
authors = ["Aleksandr Orlenko <zxteam@pm.me>", "kyren <catherine@kyju.org>"]
rust-version = "1.71"
edition = "2021"
@@ -44,18 +44,18 @@ macros = ["mlua_derive/macros"]
unstable = []
[dependencies]
mlua_derive = { version = "=0.9.2", optional = true, path = "mlua_derive" }
bstr = { version = "1.0", features = ["std"], default_features = false }
mlua_derive = { version = "=0.9.3", optional = true, path = "mlua_derive" }
bstr = { version = "1.0", features = ["std"], default-features = false }
once_cell = { version = "1.0" }
num-traits = { version = "0.2.14" }
rustc-hash = "1.0"
rustc-hash = "2.0"
futures-util = { version = "0.3", optional = true, default-features = false, features = ["std"] }
serde = { version = "1.0", optional = true }
erased-serde = { version = "0.4", optional = true }
serde-value = { version = "0.7", optional = true }
parking_lot = { version = "0.12", optional = true }
ffi = { package = "mlua-sys", version = "0.5.1", path = "mlua-sys" }
ffi = { package = "mlua-sys", version = "0.6.1", path = "mlua-sys" }
[target.'cfg(unix)'.dependencies]
libloading = { version = "0.8", optional = true }
@@ -79,6 +79,9 @@ criterion = { version = "0.5", features = ["async_tokio"] }
rustyline = "14.0"
tokio = { version = "1.0", features = ["full"] }
[lints.rust]
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(tarpaulin_include)'] }
[[bench]]
name = "benchmark"
harness = false
+2 -2
View File
@@ -133,7 +133,7 @@ Add to `Cargo.toml` :
``` toml
[dependencies]
mlua = { version = "0.9.7", features = ["lua54", "vendored"] }
mlua = { version = "0.9.9", features = ["lua54", "vendored"] }
```
`main.rs`
@@ -168,7 +168,7 @@ Add to `Cargo.toml` :
crate-type = ["cdylib"]
[dependencies]
mlua = { version = "0.9.7", features = ["lua54", "module"] }
mlua = { version = "0.9.9", features = ["lua54", "module"] }
```
`lib.rs` :
+18
View File
@@ -268,6 +268,23 @@ fn registry_value_create(c: &mut Criterion) {
});
}
fn registry_value_get(c: &mut Criterion) {
let lua = Lua::new();
lua.gc_stop();
let value = lua.create_registry_value("hello").unwrap();
c.bench_function("registry value [get]", |b| {
b.iter_batched(
|| collect_gc_twice(&lua),
|_| {
assert_eq!(lua.registry_value::<LuaString>(&value).unwrap(), "hello");
},
BatchSize::SmallInput,
);
});
}
fn userdata_create(c: &mut Criterion) {
struct UserData(#[allow(unused)] i64);
impl LuaUserData for UserData {}
@@ -406,6 +423,7 @@ criterion_group! {
function_async_call_sum,
registry_value_create,
registry_value_get,
userdata_create,
userdata_call_index,
+5 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "mlua-sys"
version = "0.5.1"
version = "0.6.1"
authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
rust-version = "1.71"
edition = "2021"
@@ -40,4 +40,7 @@ cfg-if = "1.0"
pkg-config = "0.3.17"
lua-src = { version = ">= 546.0.2, < 546.1.0", optional = true }
luajit-src = { version = ">= 210.5.0, < 210.6.0", optional = true }
luau0-src = { version = "0.8.0", optional = true }
luau0-src = { version = "0.10.0", optional = true }
[lints.rust]
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(raw_dylib)'] }
+4 -2
View File
@@ -1,3 +1,5 @@
use std::env;
cfg_if::cfg_if! {
if #[cfg(any(feature = "luau", feature = "vendored"))] {
#[path = "find_vendored.rs"]
@@ -17,8 +19,8 @@ fn main() {
println!("cargo:rerun-if-changed=build");
#[cfg(windows)]
if cfg!(feature = "module") {
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
if target_os == "windows" && cfg!(feature = "module") {
if !std::env::var("LUA_LIB_NAME").unwrap_or_default().is_empty() {
// Don't use raw-dylib linking
find::probe_lua();
+13 -6
View File
@@ -39,20 +39,25 @@ pub const LUA_MAX_UPVALUES: c_int = 200;
#[doc(hidden)]
pub const LUA_TRACEBACK_STACK: c_int = 11;
// Copied from https://github.com/rust-lang/rust/blob/master/library/std/src/sys/pal/common/alloc.rs
// The minimum alignment guaranteed by the architecture. This value is used to
// add fast paths for low alignment values.
// Copied from https://github.com/rust-lang/rust/blob/master/library/std/src/sys/common/alloc.rs
#[cfg(any(
target_arch = "x86",
target_arch = "arm",
target_arch = "m68k",
target_arch = "csky",
target_arch = "mips",
target_arch = "mips32r6",
target_arch = "powerpc",
target_arch = "powerpc64",
target_arch = "sparc",
target_arch = "asmjs",
target_arch = "wasm32",
target_arch = "hexagon",
all(target_arch = "riscv32", not(target_os = "espidf")),
all(
target_arch = "riscv32",
not(any(target_os = "espidf", target_os = "zkvm"))
),
all(target_arch = "xtensa", not(target_os = "espidf")),
))]
#[doc(hidden)]
@@ -60,18 +65,20 @@ pub const SYS_MIN_ALIGN: usize = 8;
#[cfg(any(
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "arm64ec",
target_arch = "loongarch64",
target_arch = "mips64",
target_arch = "mips64r6",
target_arch = "s390x",
target_arch = "sparc64",
target_arch = "riscv64",
target_arch = "wasm64",
target_arch = "loongarch64",
))]
#[doc(hidden)]
pub const SYS_MIN_ALIGN: usize = 16;
// The allocator on the esp-idf platform guarentees 4 byte alignment.
// The allocator on the esp-idf and zkvm platforms guarantee 4 byte alignment.
#[cfg(any(
all(target_arch = "riscv32", target_os = "espidf"),
all(target_arch = "riscv32", any(target_os = "espidf", target_os = "zkvm")),
all(target_arch = "xtensa", target_os = "espidf"),
))]
#[doc(hidden)]
+8
View File
@@ -288,6 +288,8 @@ extern "C-unwind" {
pub fn lua_setuserdatatag(L: *mut lua_State, idx: c_int, tag: c_int);
pub fn lua_setuserdatadtor(L: *mut lua_State, tag: c_int, dtor: Option<lua_Destructor>);
pub fn lua_getuserdatadtor(L: *mut lua_State, tag: c_int) -> Option<lua_Destructor>;
pub fn lua_setuserdatametatable(L: *mut lua_State, tag: c_int, idx: c_int);
pub fn lua_getuserdatametatable(L: *mut lua_State, tag: c_int);
pub fn lua_setlightuserdataname(L: *mut lua_State, tag: c_int, name: *const c_char);
pub fn lua_getlightuserdataname(L: *mut lua_State, tag: c_int) -> *const c_char;
pub fn lua_clonefunction(L: *mut lua_State, idx: c_int);
@@ -525,6 +527,7 @@ pub struct lua_Debug {
//
#[repr(C)]
#[non_exhaustive]
pub struct lua_Callbacks {
/// arbitrary userdata pointer that is never overwritten by Luau
pub userdata: *mut c_void,
@@ -552,3 +555,8 @@ pub struct lua_Callbacks {
extern "C" {
pub fn lua_callbacks(L: *mut lua_State) -> *mut lua_Callbacks;
}
// Functions from customization lib
extern "C" {
pub fn luau_setfflag(name: *const c_char, value: c_int) -> c_int;
}
+20 -1
View File
@@ -1,17 +1,36 @@
//! Contains definitions from `luacode.h`.
use std::os::raw::{c_char, c_int, c_void};
use std::slice;
use std::{ptr, slice};
#[repr(C)]
#[non_exhaustive]
pub struct lua_CompileOptions {
pub optimizationLevel: c_int,
pub debugLevel: c_int,
pub typeInfoLevel: c_int,
pub coverageLevel: c_int,
pub vectorLib: *const c_char,
pub vectorCtor: *const c_char,
pub vectorType: *const c_char,
pub mutableGlobals: *const *const c_char,
pub userdataTypes: *const *const c_char,
}
impl Default for lua_CompileOptions {
fn default() -> Self {
Self {
optimizationLevel: 1,
debugLevel: 1,
typeInfoLevel: 0,
coverageLevel: 0,
vectorLib: ptr::null(),
vectorCtor: ptr::null(),
vectorType: ptr::null(),
mutableGlobals: ptr::null(),
userdataTypes: ptr::null(),
}
}
}
extern "C-unwind" {
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "mlua_derive"
version = "0.9.2"
version = "0.9.3"
authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
edition = "2021"
description = "Procedural macros for the mlua crate."
+4 -4
View File
@@ -58,13 +58,13 @@ pub fn lua_module(attr: TokenStream, item: TokenStream) -> TokenStream {
};
let wrapped = quote! {
::mlua::require_module_feature!();
mlua::require_module_feature!();
#func
#[no_mangle]
unsafe extern "C-unwind" fn #ext_entrypoint_name(state: *mut ::mlua::lua_State) -> ::std::os::raw::c_int {
let lua = ::mlua::Lua::init_from_ptr(state);
unsafe extern "C-unwind" fn #ext_entrypoint_name(state: *mut mlua::lua_State) -> ::std::os::raw::c_int {
let lua = mlua::Lua::init_from_ptr(state);
#skip_memory_check
lua.entrypoint1(state, #func_name)
}
@@ -95,7 +95,7 @@ pub fn chunk(input: TokenStream) -> TokenStream {
});
let wrapped_code = quote! {{
use ::mlua::{AsChunk, ChunkMode, Lua, Result, Table};
use mlua::{AsChunk, ChunkMode, Lua, Result, Table};
use ::std::borrow::Cow;
use ::std::cell::Cell;
use ::std::io::Result as IoResult;
+50 -24
View File
@@ -122,11 +122,13 @@ pub enum ChunkMode {
pub struct Compiler {
optimization_level: u8,
debug_level: u8,
type_info_level: u8,
coverage_level: u8,
vector_lib: Option<String>,
vector_ctor: Option<String>,
vector_type: Option<String>,
mutable_globals: Vec<String>,
userdata_types: Vec<String>,
}
#[cfg(any(feature = "luau", doc))]
@@ -144,11 +146,13 @@ impl Compiler {
Compiler {
optimization_level: 1,
debug_level: 1,
type_info_level: 0,
coverage_level: 0,
vector_lib: None,
vector_ctor: None,
vector_type: None,
mutable_globals: Vec::new(),
userdata_types: Vec::new(),
}
}
@@ -176,6 +180,16 @@ impl Compiler {
self
}
/// Sets Luau type information level used to guide native code generation decisions.
///
/// Possible values:
/// * 0 - generate for native modules (default)
/// * 1 - generate for all modules
pub const fn set_type_info_level(mut self, level: u8) -> Self {
self.type_info_level = level;
self
}
/// Sets Luau compiler code coverage level.
///
/// Possible values:
@@ -218,6 +232,13 @@ impl Compiler {
self
}
/// Sets a list of userdata types that will be included in the type information.
#[must_use]
pub fn set_userdata_types(mut self, types: Vec<String>) -> Self {
self.userdata_types = types;
self
}
/// Compiles the `source` into bytecode.
pub fn compile(&self, source: impl AsRef<[u8]>) -> Vec<u8> {
use std::os::raw::c_int;
@@ -233,32 +254,37 @@ impl Compiler {
let vector_type = vector_type.and_then(|t| CString::new(t).ok());
let vector_type = vector_type.as_ref();
let mutable_globals = self
.mutable_globals
.iter()
.map(|name| CString::new(name.clone()).ok())
.collect::<Option<Vec<_>>>()
.unwrap_or_default();
let mut mutable_globals = mutable_globals
.iter()
.map(|s| s.as_ptr())
.collect::<Vec<_>>();
let mut mutable_globals_ptr = ptr::null();
if !mutable_globals.is_empty() {
mutable_globals.push(ptr::null());
mutable_globals_ptr = mutable_globals.as_ptr();
macro_rules! vec2cstring_ptr {
($name:ident, $name_ptr:ident) => {
let $name = self
.$name
.iter()
.map(|name| CString::new(name.clone()).ok())
.collect::<Option<Vec<_>>>()
.unwrap_or_default();
let mut $name = $name.iter().map(|s| s.as_ptr()).collect::<Vec<_>>();
let mut $name_ptr = ptr::null();
if !$name.is_empty() {
$name.push(ptr::null());
$name_ptr = $name.as_ptr();
}
};
}
vec2cstring_ptr!(mutable_globals, mutable_globals_ptr);
vec2cstring_ptr!(userdata_types, userdata_types_ptr);
unsafe {
let options = ffi::lua_CompileOptions {
optimizationLevel: self.optimization_level as c_int,
debugLevel: self.debug_level as c_int,
coverageLevel: self.coverage_level as c_int,
vectorLib: vector_lib.map_or(ptr::null(), |s| s.as_ptr()),
vectorCtor: vector_ctor.map_or(ptr::null(), |s| s.as_ptr()),
vectorType: vector_type.map_or(ptr::null(), |s| s.as_ptr()),
mutableGlobals: mutable_globals_ptr,
};
let mut options = ffi::lua_CompileOptions::default();
options.optimizationLevel = self.optimization_level as c_int;
options.debugLevel = self.debug_level as c_int;
options.typeInfoLevel = self.type_info_level as c_int;
options.coverageLevel = self.coverage_level as c_int;
options.vectorLib = vector_lib.map_or(ptr::null(), |s| s.as_ptr());
options.vectorCtor = vector_ctor.map_or(ptr::null(), |s| s.as_ptr());
options.vectorType = vector_type.map_or(ptr::null(), |s| s.as_ptr());
options.mutableGlobals = mutable_globals_ptr;
options.userdataTypes = userdata_types_ptr;
ffi::luau_compile(source.as_ref(), options)
}
}
@@ -315,7 +341,7 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
///
/// This is equivalent to calling the chunk function with no arguments and no return values.
pub fn exec(self) -> Result<()> {
self.call(())?;
self.call::<_, ()>(())?;
Ok(())
}
+5 -4
View File
@@ -453,10 +453,11 @@ impl<'lua> IntoLua<'lua> for &RegistryKey {
return Err(Error::MismatchedRegistryKey);
}
if self.is_nil() {
ffi::lua_pushnil(lua.state());
} else {
ffi::lua_rawgeti(lua.state(), ffi::LUA_REGISTRYINDEX, self.registry_id as _);
match self.id() {
ffi::LUA_REFNIL => ffi::lua_pushnil(lua.state()),
id => {
ffi::lua_rawgeti(lua.state(), ffi::LUA_REGISTRYINDEX, id as _);
}
}
Ok(())
}
+1 -1
View File
@@ -101,7 +101,7 @@ pub enum Error {
/// [`Thread::resume`] was called on an inactive coroutine.
///
/// A coroutine is inactive if its main function has returned or if an error has occurred inside
/// the coroutine.
/// the coroutine. Already running coroutines are also marked as inactive (unresumable).
///
/// [`Thread::status`] can be used to check if the coroutine can be resumed without causing this
/// error.
+1 -1
View File
@@ -73,7 +73,7 @@
// Deny warnings inside doc tests / examples. When this isn't present, rustdoc doesn't show *any*
// warnings at all.
#![doc(test(attr(deny(warnings))))]
#![doc(test(attr(warn(warnings))))] // FIXME: Remove this when rust-lang/rust#123748 is fixed
#![cfg_attr(docsrs, feature(doc_cfg))]
#[macro_use]
+52 -38
View File
@@ -1288,6 +1288,21 @@ impl Lua {
unsafe { (*self.extra.get()).enable_jit = enable };
}
/// Sets Luau feature flag (global setting).
///
/// See https://github.com/luau-lang/luau/blob/master/CONTRIBUTING.md#feature-flags for details.
#[cfg(feature = "luau")]
#[doc(hidden)]
#[allow(clippy::result_unit_err)]
pub fn set_fflag(name: &str, enabled: bool) -> StdResult<(), ()> {
if let Ok(name) = CString::new(name) {
if unsafe { ffi::luau_setfflag(name.as_ptr(), enabled as c_int) != 0 } {
return Ok(());
}
}
Err(())
}
/// Returns Lua source code as a `Chunk` builder type.
///
/// In order to actually compile or run the resulting code, you must call [`Chunk::exec`] or
@@ -2034,7 +2049,7 @@ impl Lua {
T: FromLua<'lua>,
{
let state = self.state();
let value = unsafe {
unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 3)?;
@@ -2042,9 +2057,8 @@ impl Lua {
push_string(state, name.as_bytes(), protect)?;
ffi::lua_rawget(state, ffi::LUA_REGISTRYINDEX);
self.pop_value()
};
T::from_lua(value, self)
T::from_stack(-1, self)
}
}
/// Removes a named value in the Lua registry.
@@ -2067,22 +2081,21 @@ impl Lua {
///
/// [`RegistryKey`]: crate::RegistryKey
pub fn create_registry_value<'lua, T: IntoLua<'lua>>(&'lua self, t: T) -> Result<RegistryKey> {
let t = t.into_lua(self)?;
if t == Value::Nil {
// Special case to skip calling `luaL_ref` and use `LUA_REFNIL` instead
let unref_list = unsafe { (*self.extra.get()).registry_unref_list.clone() };
return Ok(RegistryKey::new(ffi::LUA_REFNIL, unref_list));
}
let state = self.state();
unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 4)?;
self.push_value(t)?;
self.push(t)?;
let unref_list = (*self.extra.get()).registry_unref_list.clone();
// Check if the value is nil (no need to store it in the registry)
if ffi::lua_isnil(state, -1) != 0 {
return Ok(RegistryKey::new(ffi::LUA_REFNIL, unref_list));
}
// Try to reuse previously allocated slot
let unref_list = (*self.extra.get()).registry_unref_list.clone();
let free_registry_id = mlua_expect!(unref_list.lock(), "unref list poisoned")
.as_mut()
.and_then(|x| x.pop());
@@ -2092,7 +2105,7 @@ impl Lua {
return Ok(RegistryKey::new(registry_id, unref_list));
}
// Allocate a new RegistryKey
// Allocate a new RegistryKey slot
let registry_id = if self.unlikely_memory_error() {
ffi::luaL_ref(state, ffi::LUA_REGISTRYINDEX)
} else {
@@ -2116,18 +2129,16 @@ impl Lua {
}
let state = self.state();
let value = match key.is_nil() {
true => Value::Nil,
false => unsafe {
match key.id() {
ffi::LUA_REFNIL => T::from_lua(Value::Nil, self),
registry_id => unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 1)?;
let id = key.registry_id as Integer;
ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, id);
self.pop_value()
ffi::lua_rawgeti(state, ffi::LUA_REGISTRYINDEX, registry_id as Integer);
T::from_stack(-1, self)
},
};
T::from_lua(value, self)
}
}
/// Removes a value from the Lua registry.
@@ -2165,29 +2176,32 @@ impl Lua {
}
let t = t.into_lua(self)?;
if t == Value::Nil && key.is_nil() {
// Nothing to replace
return Ok(());
} else if t != Value::Nil && key.registry_id == ffi::LUA_REFNIL {
// We cannot update `LUA_REFNIL` slot
return Err(Error::runtime("cannot replace nil value with non-nil"));
}
let state = self.state();
unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 2)?;
let id = key.registry_id as Integer;
if t == Value::Nil {
self.push_value(Value::Integer(id))?;
key.set_nil(true);
} else {
self.push_value(t)?;
key.set_nil(false);
match (t, key.id()) {
(Value::Nil, ffi::LUA_REFNIL) => {
// Do nothing, no need to replace nil with nil
}
(Value::Nil, registry_id) => {
// Remove the value
ffi::luaL_unref(state, ffi::LUA_REGISTRYINDEX, registry_id);
key.set_id(ffi::LUA_REFNIL);
}
(value, ffi::LUA_REFNIL) => {
// Allocate a new `RegistryKey`
let new_key = self.create_registry_value(value)?;
key.set_id(new_key.take());
}
(value, registry_id) => {
// It must be safe to replace the value without triggering memory error
self.push_value(value)?;
ffi::lua_rawseti(state, ffi::LUA_REGISTRYINDEX, registry_id as Integer);
}
}
// It must be safe to replace the value without triggering memory error
ffi::lua_rawseti(state, ffi::LUA_REGISTRYINDEX, id);
}
Ok(())
}
+2 -2
View File
@@ -660,14 +660,14 @@ impl<'lua, 'de> de::VariantAccess<'de> for VariantDeserializer<'lua> {
// Adds `ptr` to the `visited` map and removes on drop
// Used to track recursive tables but allow to traverse same tables multiple times
struct RecursionGuard {
pub(crate) struct RecursionGuard {
ptr: *const c_void,
visited: Rc<RefCell<FxHashSet<*const c_void>>>,
}
impl RecursionGuard {
#[inline]
fn new(table: &Table, visited: &Rc<RefCell<FxHashSet<*const c_void>>>) -> Self {
pub(crate) fn new(table: &Table, visited: &Rc<RefCell<FxHashSet<*const c_void>>>) -> Self {
let visited = Rc::clone(visited);
let ptr = table.to_pointer();
visited.borrow_mut().insert(ptr);
-1
View File
@@ -1,5 +1,4 @@
use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign};
use std::u32;
/// Flags describing the set of lua standard libraries to load.
#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
+5 -5
View File
@@ -1089,7 +1089,7 @@ impl<'a, 'lua> Serialize for SerializableTable<'a, 'lua> {
where
S: Serializer,
{
use crate::serde::de::{check_value_for_skip, MapPairs};
use crate::serde::de::{check_value_for_skip, MapPairs, RecursionGuard};
use crate::value::SerializableValue;
let convert_result = |res: Result<()>, serialize_err: Option<S::Error>| match res {
@@ -1101,7 +1101,7 @@ impl<'a, 'lua> Serialize for SerializableTable<'a, 'lua> {
let options = self.options;
let visited = &self.visited;
visited.borrow_mut().insert(self.table.to_pointer());
let _guard = RecursionGuard::new(self.table, visited);
// Array
let len = self.table.raw_len();
@@ -1109,7 +1109,7 @@ impl<'a, 'lua> Serialize for SerializableTable<'a, 'lua> {
let mut seq = serializer.serialize_seq(Some(len))?;
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)
let skip = check_value_for_skip(&value, self.options, visited)
.map_err(|err| Error::SerializeError(err.to_string()))?;
if skip {
// continue iteration
@@ -1129,9 +1129,9 @@ impl<'a, 'lua> Serialize for SerializableTable<'a, 'lua> {
let mut map = serializer.serialize_map(None)?;
let mut serialize_err = None;
let mut process_pair = |key, value| {
let skip_key = check_value_for_skip(&key, self.options, &self.visited)
let skip_key = check_value_for_skip(&key, self.options, visited)
.map_err(|err| Error::SerializeError(err.to_string()))?;
let skip_value = check_value_for_skip(&value, self.options, &self.visited)
let skip_value = check_value_for_skip(&value, self.options, visited)
.map_err(|err| Error::SerializeError(err.to_string()))?;
if skip_key || skip_value {
// continue iteration
+11 -4
View File
@@ -142,6 +142,10 @@ impl<'lua> Thread<'lua> {
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua>,
{
if self.status() != ThreadStatus::Resumable {
return Err(Error::CoroutineInactive);
}
let lua = self.0.lua;
let state = lua.state();
let thread_state = self.state();
@@ -165,10 +169,6 @@ impl<'lua> Thread<'lua> {
let state = lua.state();
let thread_state = self.state();
if self.status() != ThreadStatus::Resumable {
return Err(Error::CoroutineInactive);
}
let nargs = args.push_into_stack_multi(lua)?;
if nargs > 0 {
check_stack(thread_state, nargs)?;
@@ -196,6 +196,10 @@ impl<'lua> Thread<'lua> {
/// Gets the status of the thread.
pub fn status(&self) -> ThreadStatus {
let thread_state = self.state();
if thread_state == self.0.lua.state() {
// The coroutine is currently running
return ThreadStatus::Unresumable;
}
unsafe {
let status = ffi::lua_status(thread_state);
if status != ffi::LUA_OK && status != ffi::LUA_YIELD {
@@ -243,6 +247,9 @@ impl<'lua> Thread<'lua> {
pub fn reset(&self, func: crate::function::Function<'lua>) -> Result<()> {
let lua = self.0.lua;
let thread_state = self.state();
if thread_state == lua.state() {
return Err(Error::runtime("cannot reset a running thread"));
}
unsafe {
#[cfg(all(feature = "lua54", not(feature = "vendored")))]
let status = ffi::lua_resetthread(thread_state);
+25 -29
View File
@@ -4,7 +4,7 @@ use std::hash::{Hash, Hasher};
use std::ops::{Deref, DerefMut};
use std::os::raw::{c_int, c_void};
use std::result::Result as StdResult;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::atomic::{AtomicI32, Ordering};
use std::sync::{Arc, Mutex};
use std::{fmt, mem, ptr};
@@ -204,26 +204,25 @@ pub(crate) struct DestructedUserdata;
/// [`AnyUserData::set_user_value`]: crate::AnyUserData::set_user_value
/// [`AnyUserData::user_value`]: crate::AnyUserData::user_value
pub struct RegistryKey {
pub(crate) registry_id: c_int,
pub(crate) is_nil: AtomicBool,
pub(crate) registry_id: AtomicI32,
pub(crate) unref_list: Arc<Mutex<Option<Vec<c_int>>>>,
}
impl fmt::Debug for RegistryKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "RegistryKey({})", self.registry_id)
write!(f, "RegistryKey({})", self.id())
}
}
impl Hash for RegistryKey {
fn hash<H: Hasher>(&self, state: &mut H) {
self.registry_id.hash(state)
self.id().hash(state)
}
}
impl PartialEq for RegistryKey {
fn eq(&self, other: &RegistryKey) -> bool {
self.registry_id == other.registry_id && Arc::ptr_eq(&self.unref_list, &other.unref_list)
self.id() == other.id() && Arc::ptr_eq(&self.unref_list, &other.unref_list)
}
}
@@ -231,50 +230,47 @@ impl Eq for RegistryKey {}
impl Drop for RegistryKey {
fn drop(&mut self) {
let registry_id = self.id();
// We don't need to collect nil slot
if self.registry_id > ffi::LUA_REFNIL {
if registry_id > ffi::LUA_REFNIL {
let mut unref_list = mlua_expect!(self.unref_list.lock(), "unref list poisoned");
if let Some(list) = unref_list.as_mut() {
list.push(self.registry_id);
list.push(registry_id);
}
}
}
}
impl RegistryKey {
// Creates a new instance of `RegistryKey`
/// Creates a new instance of `RegistryKey`
pub(crate) const fn new(id: c_int, unref_list: Arc<Mutex<Option<Vec<c_int>>>>) -> Self {
RegistryKey {
registry_id: id,
is_nil: AtomicBool::new(id == ffi::LUA_REFNIL),
registry_id: AtomicI32::new(id),
unref_list,
}
}
// Destroys the `RegistryKey` without adding to the unref list
pub(crate) fn take(self) -> c_int {
let registry_id = self.registry_id;
/// Returns the underlying Lua reference of this `RegistryKey`
#[inline(always)]
pub fn id(&self) -> c_int {
self.registry_id.load(Ordering::Relaxed)
}
/// Sets the unique Lua reference key of this `RegistryKey`
#[inline(always)]
pub(crate) fn set_id(&self, id: c_int) {
self.registry_id.store(id, Ordering::Relaxed);
}
/// Destroys the `RegistryKey` without adding to the unref list
pub(crate) fn take(self) -> i32 {
let registry_id = self.id();
unsafe {
ptr::read(&self.unref_list);
mem::forget(self);
}
registry_id
}
// Returns true if this `RegistryKey` holds a nil value
#[inline(always)]
pub(crate) fn is_nil(&self) -> bool {
self.is_nil.load(Ordering::Relaxed)
}
// Marks value of this `RegistryKey` as `Nil`
#[inline(always)]
pub(crate) fn set_nil(&self, enabled: bool) {
// We cannot replace previous value with nil in as this will break
// Lua mechanism to find free keys.
// Instead, we set a special flag to mark value as nil.
self.is_nil.store(enabled, Ordering::Relaxed);
}
}
pub(crate) struct LuaRef<'lua> {
+6
View File
@@ -484,3 +484,9 @@ fn test_buffer() -> Result<()> {
Ok(())
}
#[test]
fn test_fflags() {
// We cannot really on any particular feature flag to be present
assert!(Lua::set_fflag("UnknownFlag", true).is_err());
}
+21
View File
@@ -269,6 +269,27 @@ fn test_serialize_globals() -> LuaResult<()> {
Ok(())
}
#[test]
fn test_serialize_same_table_twice() -> LuaResult<()> {
let lua = Lua::new();
let value = lua
.load(
r#"
local foo = {}
return {
a = foo,
b = foo,
}
"#,
)
.eval::<Value>()?;
let json = serde_json::to_string(&value.to_serializable().sort_keys(true)).unwrap();
assert_eq!(json, r#"{"a":{},"b":{}}"#);
Ok(())
}
#[test]
fn test_to_value_struct() -> LuaResult<()> {
let lua = Lua::new();
+4 -5
View File
@@ -775,12 +775,11 @@ fn test_replace_registry_value() -> Result<()> {
lua.replace_registry_value(&key, 123)?;
assert_eq!(lua.registry_value::<i32>(&key)?, 123);
// It should be impossible to replace (initial) nil value with non-nil
let key2 = lua.create_registry_value(Value::Nil)?;
match lua.replace_registry_value(&key2, "abc") {
Err(Error::RuntimeError(_)) => {}
r => panic!("expected RuntimeError, got {r:?}"),
}
lua.replace_registry_value(&key2, Value::Nil)?;
assert_eq!(lua.registry_value::<Value>(&key2)?, Value::Nil);
lua.replace_registry_value(&key2, "abc")?;
assert_eq!(lua.registry_value::<String>(&key2)?, "abc");
Ok(())
}
+28
View File
@@ -90,6 +90,19 @@ fn test_thread() -> Result<()> {
_ => panic!("resuming dead coroutine did not return error"),
}
// Already running thread must be unresumable
let thread = lua.create_thread(lua.create_function(|lua, ()| {
assert_eq!(lua.current_thread().status(), ThreadStatus::Unresumable);
let result = lua.current_thread().resume::<_, ()>(());
assert!(
matches!(result, Err(Error::CoroutineInactive)),
"unexpected result: {result:?}",
);
Ok(())
})?)?;
let result = thread.resume::<_, ()>(());
assert!(result.is_ok(), "unexpected result: {result:?}");
Ok(())
}
@@ -146,6 +159,21 @@ fn test_thread_reset() -> Result<()> {
assert_eq!(thread.status(), ThreadStatus::Resumable);
}
// Try reset running thread
let thread = lua.create_thread(lua.create_function(|lua, ()| {
let this = lua.current_thread();
this.reset(lua.create_function(|_, ()| Ok(()))?)?;
Ok(())
})?)?;
let result = thread.resume::<_, ()>(());
assert!(
matches!(result, Err(Error::CallbackError{ ref cause, ..})
if matches!(cause.as_ref(), Error::RuntimeError(ref err)
if err == "cannot reset a running thread")
),
"unexpected result: {result:?}",
);
Ok(())
}