Compare commits

...

81 Commits

Author SHA1 Message Date
Alex Orlenko 12472de1d2 v0.9.3 2024-01-08 21:02:45 +00:00
Alex Orlenko a68708c12e Update README & CHANGELOG 2024-01-08 18:44:49 +00:00
Alex Orlenko 4c0474d573 Fix docsrs attr for Thread::reset 2024-01-06 15:04:18 +00:00
Alex Orlenko 60e859f643 Fix (nightly) warning in doc 2024-01-06 12:55:18 +00:00
Alex Orlenko 9ed0d90746 Run tests for wasm32-unknown-emscripten 2024-01-06 12:12:56 +00:00
Alex Orlenko 514ec24252 Fix lua53/lua54 luaL_error definition (for wasm32) 2024-01-03 12:02:53 +00:00
Alex Orlenko 244e6c9c12 Bump rustyline dependency 2024-01-03 11:34:50 +00:00
Alex Orlenko 4749e3a22a Update minimal lua(u) versions (needed for wasm32) 2024-01-03 11:34:34 +00:00
Alex Orlenko cf153f38de Panic when try to build for wasm32 without vendored feature (except luau) 2024-01-03 11:33:42 +00:00
Alex Orlenko c0a0983025 mlua-sys: always inline lua_error 2024-01-03 11:31:39 +00:00
Alex Orlenko 69ff0c5509 mlua-sys: fix Lua 5.2 lua_sethook definition 2024-01-03 10:22:42 +00:00
Aymen-Hakim bf79d6c212 Update lauxlib.rs (#351)
lua54 in lua53 src.
2023-12-27 15:07:59 -05:00
ByteDream 0b9a85e183 Add lua emscripten support (#338) 2023-12-14 14:54:57 +00:00
Alex Orlenko 59974d7bde Merge pull request #337 from tari/emscripten-support
Fix build for emscripten target
2023-12-13 13:55:26 +00:00
Joel Natividad 61e846326c Update Cargo.toml (#342) 2023-12-10 17:04:37 +00:00
Alex Orlenko 3547985bb0 Merge pull request #339 from eatradish/fix-loongarch64-build
Add loongarch64 architecture support
2023-12-08 09:13:07 +00:00
eatradish 4c92580201 Add loongarch64 architecture support 2023-12-08 11:16:52 +08:00
Peter Marheine e3f34f319c Correct C return type for lua_error
The definition of lua_error in all of Lua 5.1 through 5.4 says lua_error
returns int, but the Rust definition of the same function treats it as
void (because it's known not to return). This causes link-time errors when
building for wasm targets because the wasm linker is aware of function return
types and errors out if they differ between definition and declaration.
2023-12-06 21:16:52 +11:00
Peter Marheine b16f3895a0 lua54: use changed return type for lua_rawlen
Lua 5.4 changed lua_rawlen to return lua_Unsigned, versus size_t in earlier
versions. Change the C API signature to match, and add a wrapper function with
the same name that maintains a stable Rust API by casting to usize.
2023-12-06 21:09:25 +11:00
Alex Orlenko a4c919231c Don't clone function name when calling async userdata method 2023-12-03 19:55:27 +00:00
Alex Orlenko e4d6e92287 (async) Move "pending" poll value from env to poll_future() results 2023-12-02 15:01:40 +00:00
Alex Orlenko 642201a7e0 Remove locals from __mlua_async_poll helper 2023-12-01 18:11:06 +00:00
Alex Orlenko c36808b251 Faster Function::call() for lua51/jit/luau 2023-12-01 12:03:46 +00:00
Alex Orlenko 2022de2156 v0.9.2 2023-11-21 22:35:59 +00:00
Alex Orlenko 66e01548ce Update Luau+windows require dylib failed test 2023-11-20 22:16:44 +00:00
Alex Orlenko 93b505cff9 Integrate Luau package into mlua api.
Eg. `Lua::load_from_std_lib` with `StdLib::PACKAGE` is now supported for Luau.
2023-11-20 12:08:08 +00:00
Alex Orlenko 44f5688c32 Include luau to ci module tests 2023-11-16 17:54:40 +00:00
Alex Orlenko 2bee5ed33a Support binary modules for Luau on cfg(unix) 2023-11-16 15:54:25 +00:00
Alex Orlenko 2d775695ef Rewrite Luau require function to support module loaders.
Also add `package` library with `path`/`loaded`/`loaders`.
2023-11-16 14:39:17 +00:00
Alex Orlenko 34476ebf53 Support LuaJIT cdata type (produced by ffi module) 2023-11-16 13:33:23 +00:00
Alex Orlenko 5043447f23 Support Luau buffer type and and library.
Buffer is an object that represents a fixed-size mutable block of memory and added to Luau 0.601.
See https://luau-lang.org/library#buffer-library for more details.
2023-11-16 12:55:58 +00:00
Alex Orlenko b879abc418 Add lua_newuserdata_t helper to mlua-sys/luau 2023-10-24 23:58:47 +01:00
Alex Orlenko a1e39a8620 Remove MemoryState from ExtraData and retrieve using lua_getallocf (recently added to Luau) 2023-10-24 00:23:32 +01:00
Alex Orlenko f5021daaea Fix typo in table_traversal_sequence becnhmark 2023-10-12 11:07:00 +01:00
Alex Orlenko ec0fb7614e Optimize Table serialization (faster traversal) 2023-10-12 10:37:52 +01:00
Alex Orlenko 04c0763146 Optimize iterating over array part of table 2023-10-12 09:52:34 +01:00
Alex Orlenko 7d0aa7c01b Add "table traversal sequence" benchmark 2023-10-12 09:49:36 +01:00
Alex Orlenko 2a3980ef62 Add Table::for_each method for fast (faster than pairs()) table pairs traversal. 2023-10-10 12:52:26 +01:00
Alex Orlenko adb979761e Optimize table traversal (pairs iterator). 2023-10-10 11:25:21 +01:00
Alex Orlenko 37d722d71c Add table traversal benchmark 2023-10-10 11:00:58 +01:00
Alex Orlenko 6e3af593f6 Add serde benchmark 2023-10-10 00:25:47 +01:00
Alex Orlenko 54c14cc5b8 Update luajit (vendored) to 210.5.0
This includes switch from openresty's to vanilla luajit
2023-09-29 12:57:29 +01:00
Alex Orlenko 7f0de5790a Update github CI actions/checkout to v4 2023-09-13 12:03:50 +01:00
Alex Orlenko 6488477bc4 rustfmt 1.72+ 2023-08-27 23:26:27 +01:00
Alex Orlenko 53b7b5b70b Fix setting userdata (static) fields when it has __index metamethod/function.
Closes #312
2023-08-27 23:01:37 +01:00
Alex Orlenko fc159e0c46 v0.9.1 2023-08-24 01:41:32 +01:00
Alex Orlenko a802276c56 Fix an edge case when using invalidated (relative) userdata index after processing varargs.
This causes Lua API correctness check assertion in debug mode.
Fixes #311.
2023-08-24 00:54:50 +01:00
Alex Orlenko 65b816f2f0 Update README 2023-08-21 22:00:04 +01:00
Alex Orlenko e2b3464ec9 impl IntoLuaMulti for StdResult<(), E> 2023-08-20 14:17:18 +01:00
Alex Orlenko 89cf5bf362 impl Default for Lua 2023-08-20 12:16:11 +01:00
Alex Orlenko bb0a6070c4 v0.9.0 2023-08-17 11:38:42 +01:00
Alex Orlenko 60f1c16ddb mlua-sys: v0.3.2 2023-08-16 14:59:16 +01:00
Alex Orlenko 17809a390c mlua_derive: v0.9.0 2023-08-16 01:25:12 +01:00
Alex Orlenko 7662a7e4ff Update non-static (scoped) userdata:
- Use the new stack api
- Support static fields constructor
- Better error messages (on bad arguments)
2023-08-15 01:22:57 +01:00
Alex Orlenko d769a95fc5 Add Scope::create_any_userdata() 2023-08-14 17:39:51 +01:00
Alex Orlenko 0e4476c2e3 Add Lua::set_vector_metatable() method (unstable) 2023-08-12 22:46:21 +01:00
Alex Orlenko d48a2b3f6c Add OwnedThread type (unstable) 2023-08-12 21:18:12 +01:00
Alex Orlenko b3592bc23e Update mutable_globals pointer type to const (luau 0.590) 2023-08-12 17:43:25 +01:00
Alex Orlenko 052310e93d Update doc for deny_unsupported_types option 2023-08-12 17:40:39 +01:00
Alex Orlenko 09eb7f251b Support options for Value::serialize() implementation
To match `lua.from_value_with()` functionality.
2023-08-12 17:24:57 +01:00
Alex Orlenko c137da7618 Update to Luau 0.590 2023-08-12 14:12:59 +01:00
Alex Orlenko c0c6a33f94 Add new option sort_keys to DeserializeOptions (Lua::from_value method)
Closes #303
2023-08-07 11:44:52 +01:00
Alex Orlenko 0cb0a345dd Hide clippy warning converting i32 to i32 2023-08-07 11:44:35 +01:00
Alex Orlenko 3e479be4e5 Cosmetic changes for the Value conversions + add tests 2023-08-06 23:41:27 +01:00
Alex Orlenko 170aa53e29 Change Table::raw_len() output type to usize. 2023-08-06 22:45:06 +01:00
Akase Cho 021ee946fc Add helper functions to Value (#299)
Add helper functions to `Value`
2023-08-06 22:44:32 +01:00
Alex Orlenko 0b928fdfee Faster table ops 2023-08-03 10:37:52 +01:00
Alex Orlenko e858384cd4 Add table get/set benchmark 2023-08-03 10:37:39 +01:00
Alex Orlenko c062cddd87 Fastpath IntoLua/FromLua for StdString and &str
This includes direct push to Lua stack and getting value from Lua stack.
2023-08-03 10:02:20 +01:00
Alex Orlenko 94a79656ad Faster Function::call() 2023-08-03 01:16:52 +01:00
Alex Orlenko cd0c8a4584 Optimize async functionality:
Rewrite using the new `push_into_stack()`/`from_stack()` methods.
Also store thread state (pointer) in `Thread` struct to avoid getting it every time.
Async userdata methods still need to have arguments stored in ref thread as stack is empty on every poll().
2023-08-03 00:56:17 +01:00
Alex Orlenko 4fff14a144 impl Drop for MultiValue
This action would automatically return container to the pool on drop (instead of doing it manually)
2023-08-01 11:21:26 +01:00
Alex Orlenko 196c09a0d6 New (unsafe and private) methods for IntoLua/FromLua traits: push_into_stack/from_stack.
They allow to push Value directly to Lua stack or get Value from Lua stack without creating MultiValue container.
This approach is a big optimization opportunity and already demonstrated great results.
For instance, obtaining `&T` for userdata methods are now work directly from stack value without copying to auxiliary stack.
2023-07-31 22:13:23 +01:00
Alex Orlenko 114f072269 Update module entrypoint function.
Return c_int instead of Result (and avoid potential panic on Rust side).
2023-07-31 17:49:07 +01:00
Alex Orlenko b3211f13ee Minor fixes in v0.9 release notes 2023-07-31 10:17:40 +01:00
Alex Orlenko 1f1463c482 v0.9.0-rc.3 2023-07-28 22:07:26 +01:00
Alex Orlenko bec40ee5ea mlua-sys: v0.3.1 2023-07-28 22:04:00 +01:00
Alex Orlenko dc94d51d97 Update Luau compiler options 2023-07-28 21:45:19 +01:00
Alex Orlenko 3a096ae64a Fix warning when compiling chunk tests 2023-07-28 21:45:13 +01:00
Alex Orlenko a85e757d4d Bump luau-src to v0.6.0+luau588
This release has better codegen support (and breaking changed unfortunately)
2023-07-28 20:30:46 +01:00
Alex Orlenko 312886846c Fix link to v0.9 release notes in README 2023-07-28 11:14:58 +01:00
64 changed files with 3281 additions and 1205 deletions
+34 -11
View File
@@ -18,7 +18,7 @@ jobs:
- os: windows-latest
target: x86_64-pc-windows-msvc
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ matrix.rust }}
@@ -44,7 +44,7 @@ jobs:
matrix:
lua: [lua54, lua53, lua52, lua51, luajit]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
@@ -60,7 +60,7 @@ jobs:
matrix:
lua: [lua54, lua53, lua52, lua51, luajit]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
@@ -82,7 +82,7 @@ jobs:
matrix:
lua: [lua54, lua53, lua52, lua51]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
@@ -113,7 +113,7 @@ jobs:
- os: windows-latest
target: x86_64-pc-windows-msvc
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ matrix.rust }}
@@ -145,7 +145,7 @@ jobs:
- os: ubuntu-22.04
target: x86_64-unknown-linux-gnu
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ matrix.rust }}
@@ -166,14 +166,14 @@ jobs:
matrix:
os: [ubuntu-22.04, macos-latest]
rust: [stable]
lua: [lua54, lua53, lua52, lua51, luajit]
lua: [lua54, lua53, lua52, lua51, luajit, luau]
include:
- os: ubuntu-22.04
target: x86_64-unknown-linux-gnu
- os: macos-latest
target: x86_64-apple-darwin
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ matrix.rust }}
@@ -197,7 +197,7 @@ jobs:
shell: msys2 {0}
steps:
- uses: msys2/setup-msys2@v2
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Install Rust & Lua
run: |
pacman -S --noconfirm mingw-w64-x86_64-rust mingw-w64-x86_64-lua mingw-w64-x86_64-luajit mingw-w64-x86_64-pkg-config
@@ -206,11 +206,34 @@ jobs:
(cd tests/module && cargo build --release --features "${{ matrix.lua }}")
(cd tests/module/loader && cargo test --release --features "${{ matrix.lua }}")
test_wasm32_emscripten:
name: Test on wasm32-unknown-emscripten
runs-on: ubuntu-22.04
needs: build
strategy:
matrix:
lua: [lua54, lua53, lua52, lua51, luau]
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
target: wasm32-unknown-emscripten
- name: Install Emscripten
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends emscripten
- name: Run ${{ matrix.lua }} tests
run: |
cargo test --tests --features "${{ matrix.lua }},vendored"
cargo test --tests --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot"
cargo test --tests --features "${{ matrix.lua }},vendored,async,serialize,macros,parking_lot,unstable"
rustfmt:
name: Rustfmt
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
@@ -224,7 +247,7 @@ jobs:
matrix:
lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: nightly
+52
View File
@@ -1,3 +1,43 @@
## v0.9.3
- WebAssembly support (`wasm32-unknown-emscripten` target)
- Performance improvements (faster Lua function calls for lua51/jit/luau)
## v0.9.2
- Added binary modules support to Luau
- Added Luau package module (uses `StdLib::PACKAGE`) with loaders (follows lua5.1 interface)
- Added support of Luau 0.601+ buffer type (represented as userdata in Rust)
- LuaJIT `cdata` type is also represented as userdata in Rust (instead of panic)
- Vendored LuaJIT switched to rolling vanilla (from openresty)
- Added `Table::for_each` method for fast table pairs traversal (faster than `pairs`)
- Performance improvements around table traversal (and faster serialization)
- Bug fixes and improvements
## v0.9.1
- impl Default for Lua
- impl IntoLuaMulti for `std::result::Result<(), E>`
- Fix using wrong userdata index after processing Variadic args (#311)
## v0.9.0
Changes since v0.9.0-rc.3
- Improved non-static (scoped) userdata support
- Added `Scope::create_any_userdata()` method
- Added `Lua::set_vector_metatable()` method (`unstable` feature flag)
- Added `OwnedThread` type (`unstable` feature flag)
- Minimal Luau updated to 0.590
- Added new option `sort_keys` to `DeserializeOptions` (`Lua::from_value()` method)
- Changed `Table::raw_len()` output type to `usize`
- Helper functions for `Value` (eg: `Value::as_number()`/`Value::as_string`/etc)
- Performance improvements
## v0.9.0-rc.3
- Minimal Luau updated to 0.588
## v0.9.0-rc.2
- Added `#[derive(FromLua)]` macro to opt-in into `FromLua<T> where T: 'static + Clone` (userdata type).
@@ -81,6 +121,18 @@ Other:
- Support setting module name in `#[lua_module(name = "...")]` macro
- Minor fixes and improvements
## v0.8.10
- Update to Luau 0.590 (luau0-src to 0.7.x)
- Fix loading luau code starting with \t
- Pin lua-src and luajit-src versions
## v0.8.9
- Update minimal (vendored) Lua 5.4 to 5.4.6
- Use `lua_closethread` instead of `lua_resetthread` in vendored mode (Lua 5.4.6)
- Allow deserializing Lua null into unit (`()`) or unit struct.
## v0.8.8
- Fix potential deadlock when trying to reuse dropped registry keys.
+19 -8
View File
@@ -1,6 +1,6 @@
[package]
name = "mlua"
version = "0.9.0-rc.2" # remember to update mlua_derive
version = "0.9.3" # remember to update mlua_derive
authors = ["Aleksandr Orlenko <zxteam@pm.me>", "kyren <catherine@chucklefish.org>"]
rust-version = "1.71"
edition = "2021"
@@ -32,7 +32,7 @@ lua52 = ["ffi/lua52"]
lua51 = ["ffi/lua51"]
luajit = ["ffi/luajit"]
luajit52 = ["luajit", "ffi/luajit52"]
luau = ["ffi/luau"]
luau = ["ffi/luau", "libloading"]
luau-jit = ["luau", "ffi/luau-codegen"]
luau-vector4 = ["luau", "ffi/luau-vector4"]
vendored = ["ffi/vendored"]
@@ -44,38 +44,49 @@ macros = ["mlua_derive/macros"]
unstable = []
[dependencies]
mlua_derive = { version = "=0.9.0-rc.2", optional = true, path = "mlua_derive" }
mlua_derive = { version = "=0.9.0", 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"
futures-util = { version = "0.3", optional = true, default-features = false, features = ["std"] }
serde = { version = "1.0", optional = true }
erased-serde = { version = "0.3", 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.3.0", path = "mlua-sys" }
ffi = { package = "mlua-sys", version = "0.5.0", path = "mlua-sys" }
[target.'cfg(unix)'.dependencies]
libloading = { version = "0.8", optional = true }
[dev-dependencies]
rustyline = "12.0"
criterion = { version = "0.5", features = ["async_tokio"] }
trybuild = "1.0"
futures = "0.3.5"
hyper = { version = "0.14", features = ["client", "server"] }
reqwest = { version = "0.11", features = ["json"] }
tokio = { version = "1.0", features = ["full"] }
tokio = { version = "1.0", features = ["macros", "rt", "time"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
maplit = "1.0"
tempfile = "3"
static_assertions = "1.0"
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
criterion = { version = "0.5", features = ["async_tokio"] }
rustyline = "13.0"
tokio = { version = "1.0", features = ["full"] }
[[bench]]
name = "benchmark"
harness = false
required-features = ["async"]
[[bench]]
name = "serde"
harness = false
required-features = ["serialize"]
[[example]]
name = "async_http_client"
required-features = ["async", "macros"]
+6 -6
View File
@@ -19,9 +19,7 @@
> **Note**
>
> Please see the [v0.8](https://github.com/khvzak/mlua/tree/v0.8) branch for the stable versions of `mlua` released to crates.io.
>
> v0.9 release notes can be found [here](docs/release_notes/v0.9.md).
> See v0.9 [release notes](https://github.com/khvzak/mlua/blob/master/docs/release_notes/v0.9.md).
`mlua` is bindings to [Lua](https://www.lua.org) programming language for Rust with a goal to provide
_safe_ (as far as it's possible), high level, easy to use, practical and flexible API.
@@ -30,6 +28,8 @@ Started as `rlua` fork, `mlua` supports Lua 5.4, 5.3, 5.2, 5.1 (including LuaJIT
`mlua` tested on Windows/macOS/Linux including module mode in [GitHub Actions] on `x86_64` platform and cross-compilation to `aarch64` (other targets are also supported).
WebAssembly (WASM) is supported through `wasm32-unknown-emscripten` target for all Lua versions excluding JIT.
[GitHub Actions]: https://github.com/khvzak/mlua/actions
[Roblox Luau]: https://luau-lang.org
@@ -47,7 +47,7 @@ Below is a list of the available feature flags. By default `mlua` does not enabl
* `luajit`: activate [LuaJIT] support
* `luajit52`: activate [LuaJIT] support with partial compatibility with Lua 5.2
* `luau`: activate [Luau] support (auto vendored mode)
* `luau-jit`: activate [Luau] support with experimental jit backend. This is unstable feature and not recommended to use.
* `luau-jit`: activate [Luau] support with JIT backend.
* `luau-vector4`: activate [Luau] support with 4-dimensional vector.
* `vendored`: build static Lua(JIT) library from sources during `mlua` compilation using [lua-src] or [luajit-src] crates
* `module`: enable module mode (building loadable `cdylib` library for Lua)
@@ -119,7 +119,7 @@ Add to `Cargo.toml` :
``` toml
[dependencies]
mlua = { version = "0.9.0-rc.2", features = ["lua54", "vendored"] }
mlua = { version = "0.9.1", features = ["lua54", "vendored"] }
```
`main.rs`
@@ -154,7 +154,7 @@ Add to `Cargo.toml` :
crate-type = ["cdylib"]
[dependencies]
mlua = { version = "0.9.0-rc.2", features = ["lua54", "vendored", "module"] }
mlua = { version = "0.9.1", features = ["lua54", "module"] }
```
`lib.rs` :
+75
View File
@@ -59,6 +59,77 @@ 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| {
b.iter_batched(
|| {
collect_gc_twice(&lua);
table.clear().unwrap();
},
|_| {
for (i, &s) in ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]
.iter()
.enumerate()
{
table.raw_set(s, i).unwrap();
assert_eq!(table.raw_get::<_, usize>(s).unwrap(), i);
}
},
BatchSize::SmallInput,
);
});
}
fn table_traversal_pairs(c: &mut Criterion) {
let lua = Lua::new();
c.bench_function("table traversal [pairs]", |b| {
b.iter_batched(
|| lua.globals(),
|globals| {
for kv in globals.pairs::<String, LuaValue>() {
let (_k, _v) = kv.unwrap();
}
},
BatchSize::SmallInput,
);
});
}
fn table_traversal_for_each(c: &mut Criterion) {
let lua = Lua::new();
c.bench_function("table traversal [for_each]", |b| {
b.iter_batched(
|| lua.globals(),
|globals| globals.for_each::<String, LuaValue>(|_k, _v| Ok(())),
BatchSize::SmallInput,
);
});
}
fn table_traversal_sequence(c: &mut Criterion) {
let lua = Lua::new();
let table = lua.create_sequence_from(1..1000).unwrap();
c.bench_function("table traversal [sequence]", |b| {
b.iter_batched(
|| table.clone(),
|table| {
for v in table.sequence_values::<i32>() {
let _i = v.unwrap();
}
},
BatchSize::SmallInput,
);
});
}
fn create_function(c: &mut Criterion) {
let lua = Lua::new();
@@ -305,6 +376,10 @@ criterion_group! {
create_table,
create_array,
create_string_table,
table_get_set,
table_traversal_pairs,
table_traversal_for_each,
table_traversal_sequence,
create_function,
call_lua_function,
call_sum_callback,
+64
View File
@@ -0,0 +1,64 @@
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
use std::time::Duration;
use mlua::prelude::*;
fn collect_gc_twice(lua: &Lua) {
lua.gc_collect().unwrap();
lua.gc_collect().unwrap();
}
fn serialize_json(c: &mut Criterion) {
let lua = Lua::new();
lua.globals()
.set(
"encode",
LuaFunction::wrap(|_, t: LuaValue| Ok(serde_json::to_string(&t).unwrap())),
)
.unwrap();
c.bench_function("serialize table to json [10]", |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();
},
BatchSize::SmallInput,
);
});
}
criterion_group! {
name = benches;
config = Criterion::default()
.sample_size(300)
.measurement_time(Duration::from_secs(10))
.noise_threshold(0.02);
targets =
serialize_json,
}
criterion_main!(benches);
+9 -12
View File
@@ -83,7 +83,7 @@ println!("{s}!");
One of the common questions was how to embed a Lua type into Rust struct to use it later. It was non-trivial to do because of the `'lua` lifetime attached to every Lua value.
In v0.9 mlua introduces "owned" types `OwnedTable`/`OwnedFunction`/`OwnedString`/`OwnedAnyUserData` that are `'static` (no lifetime attached).
In v0.9 mlua introduces "owned" types `OwnedTable`/`OwnedFunction`/`OwnedString`/`OwnedAnyUserData`/ `OwnedThread`that are `'static` (no lifetime attached).
```rust
let lua = Lua::new();
@@ -141,11 +141,10 @@ unsafe {
#### Luau JIT support
mlua brings support for the new experimental [Luau] JIT backend under the `luau-jit` feature flag. This backend is still under development and not yet ready for production use.
mlua brings support for the new [Luau] JIT backend under the `luau-jit` feature flag.
To enable it, just call `lua.enable_jit(true)` before loading Lua code. mlua will automatically trigger JIT compilation for new Lua chunks.
When calling this function with `false` argument, mlua will disable JIT compilation but any previously compiled chunks will remain JIT-compiled.
It will automatically trigger JIT compilation for new Lua chunks. To disable it, just call `lua.enable_jit(false)` before loading Lua code
(but any previously compiled chunks will remain JIT-compiled).
[Luau]: https://luau-lang.org
@@ -197,16 +196,14 @@ let read = lua.create_function(|lua, path: String| {
lua.load(chunk! {
local ok, err = pcall($read, "/nonexistent")
/// Prints:
/// Failed to open /nonexistent
/// No such file or directory (os error 2)
/// stack traceback:
/// ...
print(err)
})
.exec()?;
Prints:
```text
Failed to open /nonexistent
No such file or directory (os error 2)
stack traceback:
...
```
[`anyhow`]: https://crates.io/crates/anyhow
+1 -1
View File
@@ -19,7 +19,7 @@ impl UserData for BodyReader {
}
}
#[tokio::main]
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<()> {
let lua = Lua::new();
+1 -1
View File
@@ -1,6 +1,6 @@
use mlua::{chunk, ExternalResult, Lua, LuaSerdeExt, Result};
#[tokio::main]
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<()> {
let lua = Lua::new();
+5 -4
View File
@@ -1,7 +1,8 @@
[package]
name = "mlua-sys"
version = "0.3.0"
version = "0.5.0"
authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
rust-version = "1.71"
edition = "2021"
repository = "https://github.com/khvzak/mlua"
documentation = "https://docs.rs/mlua-sys"
@@ -37,6 +38,6 @@ module = []
cc = "1.0"
cfg-if = "1.0"
pkg-config = "0.3.17"
lua-src = { version = ">= 546.0.0, < 546.1.0", optional = true }
luajit-src = { version = ">= 210.4.6, < 210.6.0", optional = true }
luau0-src = { version = "0.5.11", optional = true }
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.7.11", optional = true }
+6
View File
@@ -4,6 +4,12 @@ use std::env;
use std::ops::Bound;
pub fn probe_lua() {
let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
if target_arch == "wasm32" && cfg!(not(feature = "vendored")) {
panic!("Please enable `vendored` feature to build for wasm32");
}
let lib_dir = env::var("LUA_LIB").unwrap_or_default();
let lua_lib = env::var("LUA_LIB_NAME").unwrap_or_default();
+2 -2
View File
@@ -9,8 +9,8 @@ cfg_if::cfg_if! {
}
fn main() {
#[cfg(all(feature = "luau", feature = "module"))]
compile_error!("Luau does not support `module` mode");
#[cfg(all(feature = "luau", feature = "module", windows))]
compile_error!("Luau does not support `module` mode on Windows");
#[cfg(all(feature = "module", feature = "vendored"))]
compile_error!("`vendored` and `module` features are mutually exclusive");
+1
View File
@@ -65,6 +65,7 @@ pub const SYS_MIN_ALIGN: usize = 8;
target_arch = "sparc64",
target_arch = "riscv64",
target_arch = "wasm64",
target_arch = "loongarch64",
))]
#[doc(hidden)]
pub const SYS_MIN_ALIGN: usize = 16;
+1 -1
View File
@@ -43,7 +43,7 @@ extern "C-unwind" {
pub fn luaL_checkudata(L: *mut lua_State, ud: c_int, tname: *const c_char) -> *mut c_void;
pub fn luaL_where(L: *mut lua_State, lvl: c_int);
pub fn luaL_error(L: *mut lua_State, fmt: *const c_char, ...) -> !;
pub fn luaL_error(L: *mut lua_State, fmt: *const c_char, ...) -> c_int;
pub fn luaL_checkoption(
L: *mut lua_State,
+11 -1
View File
@@ -228,13 +228,23 @@ extern "C-unwind" {
//
#[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))]
extern "C-unwind" {
pub fn lua_error(L: *mut lua_State) -> !;
#[link_name = "lua_error"]
fn lua_error_(L: *mut lua_State) -> c_int;
pub fn lua_next(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_concat(L: *mut lua_State, n: c_int);
pub fn lua_getallocf(L: *mut lua_State, ud: *mut *mut c_void) -> lua_Alloc;
pub fn lua_setallocf(L: *mut lua_State, f: lua_Alloc, ud: *mut c_void);
}
// lua_error does not return but is declared to return int, and Rust translates
// ! to void which can cause link-time errors if the platform linker is aware
// of return types and requires they match (for example: wasm does this).
#[inline(always)]
pub unsafe fn lua_error(L: *mut lua_State) -> ! {
lua_error_(L);
unreachable!();
}
//
// Some useful macros (implemented as Rust functions)
//
+1 -1
View File
@@ -49,7 +49,7 @@ extern "C-unwind" {
pub fn luaL_checkudata(L: *mut lua_State, ud: c_int, tname: *const c_char) -> *mut c_void;
pub fn luaL_where(L: *mut lua_State, lvl: c_int);
pub fn luaL_error(L: *mut lua_State, fmt: *const c_char, ...) -> !;
pub fn luaL_error(L: *mut lua_State, fmt: *const c_char, ...) -> c_int;
pub fn luaL_checkoption(
L: *mut lua_State,
+17 -2
View File
@@ -308,7 +308,8 @@ extern "C-unwind" {
//
// Miscellaneous functions
//
pub fn lua_error(L: *mut lua_State) -> !;
#[link_name = "lua_error"]
fn lua_error_(L: *mut lua_State) -> c_int;
pub fn lua_next(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_concat(L: *mut lua_State, n: c_int);
pub fn lua_len(L: *mut lua_State, idx: c_int);
@@ -316,6 +317,15 @@ extern "C-unwind" {
pub fn lua_setallocf(L: *mut lua_State, f: lua_Alloc, ud: *mut c_void);
}
// lua_error does not return but is declared to return int, and Rust translates
// ! to void which can cause link-time errors if the platform linker is aware
// of return types and requires they match (for example: wasm does this).
#[inline(always)]
pub unsafe fn lua_error(L: *mut lua_State) -> ! {
lua_error_(L);
unreachable!();
}
//
// Some useful macros (implemented as Rust functions)
//
@@ -453,7 +463,12 @@ extern "C-unwind" {
pub fn lua_upvalueid(L: *mut lua_State, fidx: c_int, n: c_int) -> *mut c_void;
pub fn lua_upvaluejoin(L: *mut lua_State, fidx1: c_int, n1: c_int, fidx2: c_int, n2: c_int);
pub fn lua_sethook(L: *mut lua_State, func: Option<lua_Hook>, mask: c_int, count: c_int);
pub fn lua_sethook(
L: *mut lua_State,
func: Option<lua_Hook>,
mask: c_int,
count: c_int,
) -> c_int;
pub fn lua_gethook(L: *mut lua_State) -> Option<lua_Hook>;
pub fn lua_gethookmask(L: *mut lua_State) -> c_int;
pub fn lua_gethookcount(L: *mut lua_State) -> c_int;
+2 -2
View File
@@ -20,7 +20,7 @@ pub struct luaL_Reg {
pub func: lua_CFunction,
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
extern "C-unwind" {
pub fn luaL_checkversion_(L: *mut lua_State, ver: lua_Number, sz: usize);
@@ -51,7 +51,7 @@ extern "C-unwind" {
pub fn luaL_checkudata(L: *mut lua_State, ud: c_int, tname: *const c_char) -> *mut c_void;
pub fn luaL_where(L: *mut lua_State, lvl: c_int);
pub fn luaL_error(L: *mut lua_State, fmt: *const c_char, ...) -> !;
pub fn luaL_error(L: *mut lua_State, fmt: *const c_char, ...) -> c_int;
pub fn luaL_checkoption(
L: *mut lua_State,
+11 -1
View File
@@ -314,7 +314,8 @@ extern "C-unwind" {
//
// Miscellaneous functions
//
pub fn lua_error(L: *mut lua_State) -> !;
#[link_name = "lua_error"]
fn lua_error_(L: *mut lua_State) -> c_int;
pub fn lua_next(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_concat(L: *mut lua_State, n: c_int);
pub fn lua_len(L: *mut lua_State, idx: c_int);
@@ -323,6 +324,15 @@ extern "C-unwind" {
pub fn lua_setallocf(L: *mut lua_State, f: lua_Alloc, ud: *mut c_void);
}
// lua_error does not return but is declared to return int, and Rust translates
// ! to void which can cause link-time errors if the platform linker is aware
// of return types and requires they match (for example: wasm does this).
#[inline(always)]
pub unsafe fn lua_error(L: *mut lua_State) -> ! {
lua_error_(L);
unreachable!();
}
//
// Some useful macros (implemented as Rust functions)
//
+1 -1
View File
@@ -50,7 +50,7 @@ extern "C-unwind" {
pub fn luaL_checkudata(L: *mut lua_State, ud: c_int, tname: *const c_char) -> *mut c_void;
pub fn luaL_where(L: *mut lua_State, lvl: c_int);
pub fn luaL_error(L: *mut lua_State, fmt: *const c_char, ...) -> !;
pub fn luaL_error(L: *mut lua_State, fmt: *const c_char, ...) -> c_int;
pub fn luaL_checkoption(
L: *mut lua_State,
+20 -2
View File
@@ -149,13 +149,21 @@ extern "C-unwind" {
pub fn lua_tointegerx(L: *mut lua_State, idx: c_int, isnum: *mut c_int) -> lua_Integer;
pub fn lua_toboolean(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) -> *const c_char;
pub fn lua_rawlen(L: *mut lua_State, idx: c_int) -> usize;
#[link_name = "lua_rawlen"]
fn lua_rawlen_(L: *mut lua_State, idx: c_int) -> lua_Unsigned;
pub fn lua_tocfunction(L: *mut lua_State, idx: c_int) -> Option<lua_CFunction>;
pub fn lua_touserdata(L: *mut lua_State, idx: c_int) -> *mut c_void;
pub fn lua_tothread(L: *mut lua_State, idx: c_int) -> *mut lua_State;
pub fn lua_topointer(L: *mut lua_State, idx: c_int) -> *const c_void;
}
// lua_rawlen's return type changed from size_t to lua_Unsigned int in Lua 5.4.
// This adapts the crate API to the new Lua ABI.
#[inline(always)]
pub unsafe fn lua_rawlen(L: *mut lua_State, idx: c_int) -> usize {
lua_rawlen_(L, idx) as usize
}
//
// Comparison and arithmetic functions
//
@@ -336,7 +344,8 @@ extern "C-unwind" {
//
// Miscellaneous functions
//
pub fn lua_error(L: *mut lua_State) -> !;
#[link_name = "lua_error"]
fn lua_error_(L: *mut lua_State) -> c_int;
pub fn lua_next(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_concat(L: *mut lua_State, n: c_int);
pub fn lua_len(L: *mut lua_State, idx: c_int);
@@ -348,6 +357,15 @@ extern "C-unwind" {
pub fn lua_closeslot(L: *mut lua_State, idx: c_int);
}
// lua_error does not return but is declared to return int, and Rust translates
// ! to void which can cause link-time errors if the platform linker is aware
// of return types and requires they match (for example: wasm does this).
#[inline(always)]
pub unsafe fn lua_error(L: *mut lua_State) -> ! {
lua_error_(L);
unreachable!();
}
//
// Some useful macros (implemented as Rust functions)
//
+46 -1
View File
@@ -52,6 +52,8 @@ extern "C-unwind" {
pub fn luaL_newmetatable_(L: *mut lua_State, tname: *const c_char) -> c_int;
pub fn luaL_checkudata(L: *mut lua_State, ud: c_int, tname: *const c_char) -> *mut c_void;
pub fn luaL_checkbuffer(L: *mut lua_State, narg: c_int, len: *mut usize) -> *mut c_void;
pub fn luaL_where(L: *mut lua_State, lvl: c_int);
#[link_name = "luaL_errorL"]
@@ -152,5 +154,48 @@ pub unsafe fn luaL_sandbox(L: *mut lua_State, enabled: c_int) {
}
//
// TODO: Generic Buffer Manipulation
// Generic Buffer Manipulation
//
/// Buffer size used for on-stack string operations. This limit depends on native stack size.
pub const LUA_BUFFERSIZE: usize = 512;
#[repr(C)]
pub struct luaL_Strbuf {
p: *mut c_char, // current position in buffer
end: *mut c_char, // end of the current buffer
L: *mut lua_State,
storage: *mut c_void, // TString
buffer: [c_char; LUA_BUFFERSIZE],
}
// For compatibility
pub type luaL_Buffer = luaL_Strbuf;
extern "C-unwind" {
pub fn luaL_buffinit(L: *mut lua_State, B: *mut luaL_Strbuf);
pub fn luaL_buffinitsize(L: *mut lua_State, B: *mut luaL_Strbuf, size: usize) -> *mut c_char;
pub fn luaL_prepbuffsize(B: *mut luaL_Strbuf, size: usize) -> *mut c_char;
pub fn luaL_addlstring(B: *mut luaL_Strbuf, s: *const c_char, l: usize);
pub fn luaL_addvalue(B: *mut luaL_Strbuf);
pub fn luaL_addvalueany(B: *mut luaL_Strbuf, idx: c_int);
pub fn luaL_pushresult(B: *mut luaL_Strbuf);
pub fn luaL_pushresultsize(B: *mut luaL_Strbuf, size: usize);
}
pub unsafe fn luaL_addchar(B: *mut luaL_Strbuf, c: c_char) {
if (*B).p >= (*B).end {
luaL_prepbuffsize(B, 1);
}
*(*B).p = c;
(*B).p = (*B).p.add(1);
}
pub unsafe fn luaL_addstring(B: *mut luaL_Strbuf, s: *const c_char) {
// Calculate length of s
let mut len = 0;
while *s.add(len) != 0 {
len += 1;
}
luaL_addlstring(B, s, len);
}
+20 -1
View File
@@ -2,7 +2,7 @@
use std::marker::{PhantomData, PhantomPinned};
use std::os::raw::{c_char, c_double, c_float, c_int, c_uint, c_void};
use std::ptr;
use std::{mem, ptr};
// Option for multiple returns in 'lua_pcall' and 'lua_call'
pub const LUA_MULTRET: c_int = -1;
@@ -55,6 +55,7 @@ pub const LUA_TTABLE: c_int = 6;
pub const LUA_TFUNCTION: c_int = 7;
pub const LUA_TUSERDATA: c_int = 8;
pub const LUA_TTHREAD: c_int = 9;
pub const LUA_TBUFFER: c_int = 10;
/// Guaranteed number of Lua stack slots available to a C function.
pub const LUA_MINSTACK: c_int = 20;
@@ -147,6 +148,7 @@ extern "C-unwind" {
pub fn lua_touserdatatagged(L: *mut lua_State, idx: c_int, tag: c_int) -> *mut c_void;
pub fn lua_userdatatag(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_tothread(L: *mut lua_State, idx: c_int) -> *mut lua_State;
pub fn lua_tobuffer(L: *mut lua_State, idx: c_int, len: *mut usize) -> *mut c_void;
pub fn lua_topointer(L: *mut lua_State, idx: c_int) -> *const c_void;
//
@@ -181,6 +183,8 @@ extern "C-unwind" {
pub fn lua_newuserdatatagged(L: *mut lua_State, sz: usize, tag: c_int) -> *mut c_void;
pub fn lua_newuserdatadtor(L: *mut lua_State, sz: usize, dtor: lua_Udestructor) -> *mut c_void;
pub fn lua_newbuffer(L: *mut lua_State, sz: usize) -> *mut c_void;
//
// Get functions (Lua -> stack)
//
@@ -278,6 +282,7 @@ extern "C-unwind" {
pub fn lua_getuserdatadtor(L: *mut lua_State, tag: c_int) -> Option<lua_Destructor>;
pub fn lua_clonefunction(L: *mut lua_State, idx: c_int);
pub fn lua_cleartable(L: *mut lua_State, idx: c_int);
pub fn lua_getallocf(L: *mut lua_State, ud: *mut *mut c_void) -> lua_Alloc;
}
//
@@ -325,6 +330,15 @@ pub unsafe fn lua_newuserdata(L: *mut lua_State, sz: usize) -> *mut c_void {
lua_newuserdatatagged(L, sz, 0)
}
#[inline(always)]
pub unsafe fn lua_newuserdata_t<T>(L: *mut lua_State) -> *mut T {
unsafe extern "C-unwind" fn destructor<T>(ud: *mut c_void) {
ptr::drop_in_place(ud as *mut T);
}
lua_newuserdatadtor(L, mem::size_of::<T>(), destructor::<T>) as *mut T
}
// TODO: lua_strlen
#[inline(always)]
@@ -362,6 +376,11 @@ pub unsafe fn lua_isthread(L: *mut lua_State, n: c_int) -> c_int {
(lua_type(L, n) == LUA_TTHREAD) as c_int
}
#[inline(always)]
pub unsafe fn lua_isbuffer(L: *mut lua_State, n: c_int) -> c_int {
(lua_type(L, n) == LUA_TBUFFER) as c_int
}
#[inline(always)]
pub unsafe fn lua_isnone(L: *mut lua_State, n: c_int) -> c_int {
(lua_type(L, n) == LUA_TNONE) as c_int
+2 -1
View File
@@ -10,7 +10,8 @@ pub struct lua_CompileOptions {
pub coverageLevel: c_int,
pub vectorLib: *const c_char,
pub vectorCtor: *const c_char,
pub mutableGlobals: *mut *const c_char,
pub vectorType: *const c_char,
pub mutableGlobals: *const *const c_char,
}
extern "C-unwind" {
+2
View File
@@ -9,6 +9,7 @@ pub const LUA_TABLIBNAME: &str = "table";
pub const LUA_OSLIBNAME: &str = "os";
pub const LUA_STRLIBNAME: &str = "string";
pub const LUA_BITLIBNAME: &str = "bit32";
pub const LUA_BUFFERLIBNAME: &str = "buffer";
pub const LUA_UTF8LIBNAME: &str = "utf8";
pub const LUA_MATHLIBNAME: &str = "math";
pub const LUA_DBLIBNAME: &str = "debug";
@@ -20,6 +21,7 @@ extern "C-unwind" {
pub fn luaopen_os(L: *mut lua_State) -> c_int;
pub fn luaopen_string(L: *mut lua_State) -> c_int;
pub fn luaopen_bit32(L: *mut lua_State) -> c_int;
pub fn luaopen_buffer(L: *mut lua_State) -> c_int;
pub fn luaopen_utf8(L: *mut lua_State) -> c_int;
pub fn luaopen_math(L: *mut lua_State) -> c_int;
pub fn luaopen_debug(L: *mut lua_State) -> c_int;
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "mlua_derive"
version = "0.9.0-rc.2"
version = "0.9.0"
authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
edition = "2021"
description = "Procedural macros for the mlua crate."
+2 -6
View File
@@ -61,12 +61,8 @@ pub fn lua_module(attr: TokenStream, item: TokenStream) -> TokenStream {
#[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);
if #skip_memory_check {
lua.skip_memory_check(true);
}
lua
.entrypoint1(#func_name)
.expect("cannot initialize module")
lua.skip_memory_check(#skip_memory_check);
lua.entrypoint1(#func_name)
}
};
+19 -6
View File
@@ -125,6 +125,7 @@ pub struct Compiler {
coverage_level: u8,
vector_lib: Option<String>,
vector_ctor: Option<String>,
vector_type: Option<String>,
mutable_globals: Vec<String>,
}
@@ -146,6 +147,7 @@ impl Compiler {
coverage_level: 0,
vector_lib: None,
vector_ctor: None,
vector_type: None,
mutable_globals: Vec::new(),
}
}
@@ -188,15 +190,22 @@ impl Compiler {
#[doc(hidden)]
#[must_use]
pub fn set_vector_lib(mut self, lib: Option<String>) -> Self {
self.vector_lib = lib;
pub fn set_vector_lib(mut self, lib: impl Into<String>) -> Self {
self.vector_lib = Some(lib.into());
self
}
#[doc(hidden)]
#[must_use]
pub fn set_vector_ctor(mut self, ctor: Option<String>) -> Self {
self.vector_ctor = ctor;
pub fn set_vector_ctor(mut self, ctor: impl Into<String>) -> Self {
self.vector_ctor = Some(ctor.into());
self
}
#[doc(hidden)]
#[must_use]
pub fn set_vector_type(mut self, r#type: impl Into<String>) -> Self {
self.vector_type = Some(r#type.into());
self
}
@@ -220,6 +229,9 @@ impl Compiler {
let vector_ctor = self.vector_ctor.clone();
let vector_ctor = vector_ctor.and_then(|ctor| CString::new(ctor).ok());
let vector_ctor = vector_ctor.as_ref();
let vector_type = self.vector_type.clone();
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
@@ -231,10 +243,10 @@ impl Compiler {
.iter()
.map(|s| s.as_ptr())
.collect::<Vec<_>>();
let mut mutable_globals_ptr = ptr::null_mut();
let mut mutable_globals_ptr = ptr::null();
if !mutable_globals.is_empty() {
mutable_globals.push(ptr::null());
mutable_globals_ptr = mutable_globals.as_mut_ptr();
mutable_globals_ptr = mutable_globals.as_ptr();
}
unsafe {
@@ -244,6 +256,7 @@ impl Compiler {
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,
};
ffi::luau_compile(source.as_ref(), options)
+50 -1
View File
@@ -3,7 +3,9 @@ use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::convert::TryInto;
use std::ffi::{CStr, CString};
use std::hash::{BuildHasher, Hash};
use std::os::raw::c_int;
use std::string::String as StdString;
use std::{slice, str};
use bstr::{BStr, BString};
use num_traits::cast;
@@ -180,7 +182,10 @@ impl<'lua> FromLua<'lua> for AnyUserData<'lua> {
impl<'lua> IntoLua<'lua> for OwnedAnyUserData {
#[inline]
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::UserData(AnyUserData(lua.adopt_owned_ref(self.0))))
Ok(Value::UserData(AnyUserData(
lua.adopt_owned_ref(self.0),
self.1,
)))
}
}
@@ -302,6 +307,11 @@ impl<'lua> IntoLua<'lua> for StdString {
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::String(lua.create_string(&self)?))
}
#[inline]
unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> {
push_bytes_into_stack(self, lua)
}
}
impl<'lua> FromLua<'lua> for StdString {
@@ -318,6 +328,25 @@ impl<'lua> FromLua<'lua> for StdString {
.to_str()?
.to_owned())
}
#[inline]
unsafe fn from_stack(idx: c_int, lua: &'lua Lua) -> Result<Self> {
let state = lua.state();
if ffi::lua_type(state, idx) == ffi::LUA_TSTRING {
let mut size = 0;
let data = ffi::lua_tolstring(state, idx, &mut size);
let bytes = slice::from_raw_parts(data as *const u8, size);
return str::from_utf8(bytes).map(|s| s.to_owned()).map_err(|e| {
Error::FromLuaConversionError {
from: "string",
to: "String",
message: Some(e.to_string()),
}
});
}
// Fallback to default
Self::from_lua(lua.stack_value(idx), lua)
}
}
impl<'lua> IntoLua<'lua> for &str {
@@ -325,6 +354,11 @@ impl<'lua> IntoLua<'lua> for &str {
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::String(lua.create_string(self)?))
}
#[inline]
unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> {
push_bytes_into_stack(self, lua)
}
}
impl<'lua> IntoLua<'lua> for Cow<'_, str> {
@@ -433,6 +467,21 @@ impl<'lua> IntoLua<'lua> for &BStr {
}
}
#[inline]
unsafe fn push_bytes_into_stack<'lua, T>(this: T, lua: &'lua Lua) -> Result<()>
where
T: IntoLua<'lua> + AsRef<[u8]>,
{
let bytes = this.as_ref();
if lua.unlikely_memory_error() && bytes.len() < (1 << 30) {
// Fast path: push directly into the Lua stack.
ffi::lua_pushlstring(lua.state(), bytes.as_ptr() as *const _, bytes.len());
return Ok(());
}
// Fallback to default
lua.push_value(T::into_lua(this, lua)?)
}
macro_rules! lua_convert_int {
($x:ty) => {
impl<'lua> IntoLua<'lua> for $x {
+21 -29
View File
@@ -6,12 +6,11 @@ use std::slice;
use crate::error::{Error, Result};
use crate::lua::Lua;
use crate::memory::MemoryState;
use crate::table::Table;
use crate::types::{Callback, LuaRef, MaybeSend};
use crate::util::{
assert_stack, check_stack, error_traceback, linenumber_to_usize, pop_error, ptr_to_lossy_str,
ptr_to_str, StackGuard,
assert_stack, check_stack, linenumber_to_usize, pop_error, ptr_to_lossy_str, ptr_to_str,
StackGuard,
};
use crate::value::{FromLuaMulti, IntoLua, IntoLuaMulti, Value};
@@ -126,34 +125,25 @@ impl<'lua> Function<'lua> {
pub fn call<A: IntoLuaMulti<'lua>, R: FromLuaMulti<'lua>>(&self, args: A) -> Result<R> {
let lua = self.0.lua;
let state = lua.state();
let mut args = args.into_lua_multi(lua)?;
let nargs = args.len() as c_int;
let results = unsafe {
unsafe {
let _sg = StackGuard::new(state);
check_stack(state, nargs + 3)?;
check_stack(state, 2)?;
MemoryState::relax_limit_with(state, || ffi::lua_pushcfunction(state, error_traceback));
// Push error handler
lua.push_error_traceback();
let stack_start = ffi::lua_gettop(state);
// Push function and the arguments
lua.push_ref(&self.0);
for arg in args.drain_all() {
lua.push_value(arg)?;
}
let nargs = args.push_into_stack_multi(lua)?;
// Call the function
let ret = ffi::lua_pcall(state, nargs, ffi::LUA_MULTRET, stack_start);
if ret != ffi::LUA_OK {
return Err(pop_error(state, ret));
}
// Get the results
let nresults = ffi::lua_gettop(state) - stack_start;
let mut results = args; // Reuse MultiValue container
assert_stack(state, 2);
for _ in 0..nresults {
results.push_front(lua.pop_value());
}
ffi::lua_pop(state, 1);
results
};
R::from_lua_multi(results, lua)
R::from_stack_multi(nresults, lua)
}
}
/// Returns a future that, when polled, calls `self`, passing `args` as function arguments,
@@ -564,8 +554,9 @@ impl<'lua> Function<'lua> {
R: IntoLuaMulti<'lua>,
F: Fn(&'lua Lua, A) -> Result<R> + MaybeSend + 'static,
{
WrappedFunction(Box::new(move |lua, args| {
func(lua, A::from_lua_multi(args, lua)?)?.into_lua_multi(lua)
WrappedFunction(Box::new(move |lua, nargs| unsafe {
let args = A::from_stack_args(nargs, 1, None, lua)?;
func(lua, args)?.push_into_stack_multi(lua)
}))
}
@@ -578,11 +569,12 @@ impl<'lua> Function<'lua> {
F: FnMut(&'lua Lua, A) -> Result<R> + MaybeSend + 'static,
{
let func = RefCell::new(func);
WrappedFunction(Box::new(move |lua, args| {
WrappedFunction(Box::new(move |lua, nargs| unsafe {
let mut func = func
.try_borrow_mut()
.map_err(|_| Error::RecursiveMutCallback)?;
func(lua, A::from_lua_multi(args, lua)?)?.into_lua_multi(lua)
let args = A::from_stack_args(nargs, 1, None, lua)?;
func(lua, args)?.push_into_stack_multi(lua)
}))
}
@@ -596,13 +588,13 @@ impl<'lua> Function<'lua> {
F: Fn(&'lua Lua, A) -> FR + MaybeSend + 'static,
FR: Future<Output = Result<R>> + 'lua,
{
WrappedAsyncFunction(Box::new(move |lua, args| {
let args = match A::from_lua_multi(args, lua) {
WrappedAsyncFunction(Box::new(move |lua, args| unsafe {
let args = match A::from_lua_args(args, 1, None, lua) {
Ok(args) => args,
Err(e) => return Box::pin(future::err(e)),
};
let fut = func(lua, args);
Box::pin(async move { fut.await?.into_lua_multi(lua) })
Box::pin(async move { fut.await?.push_into_stack_multi(lua) })
}))
}
}
+4 -2
View File
@@ -353,8 +353,10 @@ impl HookTriggers {
// Returns the `count` parameter to pass to `lua_sethook`, if applicable. Otherwise, zero is
// returned.
pub(crate) const fn count(&self) -> c_int {
let Some(n) = self.every_nth_instruction else { return 0 };
n as c_int
match self.every_nth_instruction {
Some(n) => n as c_int,
None => 0,
}
}
}
+2 -1
View File
@@ -157,7 +157,8 @@ extern crate mlua_derive;
// Unstable features
#[cfg(feature = "unstable")]
pub use crate::{
function::OwnedFunction, string::OwnedString, table::OwnedTable, userdata::OwnedAnyUserData,
function::OwnedFunction, string::OwnedString, table::OwnedTable, thread::OwnedThread,
userdata::OwnedAnyUserData,
};
/// Create a type that implements [`AsChunk`] and can capture Rust variables.
+317 -189
View File
@@ -3,15 +3,14 @@ use std::cell::{RefCell, UnsafeCell};
use std::ffi::{CStr, CString};
use std::fmt;
use std::marker::PhantomData;
use std::mem::MaybeUninit;
use std::mem::{self, MaybeUninit};
use std::ops::Deref;
use std::os::raw::{c_char, c_int, c_void};
use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe, Location};
use std::ptr::NonNull;
use std::ptr;
use std::result::Result as StdResult;
use std::sync::atomic::{AtomicPtr, Ordering};
use std::sync::{Arc, Mutex};
use std::{mem, ptr, str};
use rustc_hash::FxHashMap;
@@ -27,15 +26,15 @@ use crate::table::Table;
use crate::thread::Thread;
use crate::types::{
AppData, AppDataRef, AppDataRefMut, Callback, CallbackUpvalue, DestructedUserdata, Integer,
LightUserData, LuaRef, MaybeSend, Number, RegistryKey,
LightUserData, LuaRef, MaybeSend, Number, RegistryKey, SubtypeId,
};
use crate::userdata::{AnyUserData, MetaMethod, UserData, UserDataCell};
use crate::userdata_impl::{UserDataProxy, UserDataRegistry};
use crate::util::{
self, assert_stack, check_stack, get_destructed_userdata_metatable, get_gc_metatable,
get_gc_userdata, get_main_state, get_userdata, init_error_registry, init_gc_metatable,
init_userdata_metatable, pop_error, push_gc_userdata, push_string, push_table, rawset_field,
safe_pcall, safe_xpcall, short_type_name, StackGuard, WrappedFailure,
self, assert_stack, check_stack, error_traceback, get_destructed_userdata_metatable,
get_gc_metatable, get_gc_userdata, get_main_state, get_userdata, init_error_registry,
init_gc_metatable, init_userdata_metatable, pop_error, push_gc_userdata, push_string,
push_table, rawset_field, safe_pcall, safe_xpcall, short_type_name, StackGuard, WrappedFailure,
};
use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, MultiValue, Nil, Value};
@@ -60,6 +59,7 @@ use {
crate::types::{AsyncCallback, AsyncCallbackUpvalue, AsyncPollUpvalue},
futures_util::future::{self, Future},
futures_util::task::{noop_waker_ref, Context, Poll, Waker},
std::ptr::NonNull,
};
#[cfg(feature = "serialize")]
@@ -94,7 +94,6 @@ pub(crate) struct ExtraData {
safe: bool,
libs: StdLib,
mem_state: Option<NonNull<MemoryState>>,
#[cfg(feature = "module")]
skip_memory_check: bool,
@@ -107,7 +106,7 @@ pub(crate) struct ExtraData {
// Pool of `WrappedFailure` enums in the ref thread (as userdata)
wrapped_failure_pool: Vec<c_int>,
// Pool of `MultiValue` containers
multivalue_pool: Vec<MultiValue<'static>>,
multivalue_pool: Vec<Vec<Value<'static>>>,
// Pool of `Thread`s (coroutines) for async execution
#[cfg(feature = "async")]
thread_pool: Vec<c_int>,
@@ -244,11 +243,14 @@ impl Drop for Lua {
impl Drop for LuaInner {
fn drop(&mut self) {
unsafe {
#[cfg(feature = "luau")]
{
(*ffi::lua_callbacks(self.state())).userdata = ptr::null_mut();
}
let mem_state = MemoryState::get(self.main_state);
ffi::lua_close(self.main_state);
// Deallocate MemoryState
if !mem_state.is_null() {
drop(Box::from_raw(mem_state));
}
}
}
}
@@ -261,9 +263,6 @@ impl Drop for ExtraData {
}
*mlua_expect!(self.registry_unref_list.lock(), "unref list poisoned") = None;
if let Some(mem_state) = self.mem_state {
drop(unsafe { Box::from_raw(mem_state.as_ptr()) });
}
}
}
@@ -282,6 +281,13 @@ impl Deref for Lua {
}
}
impl Default for Lua {
#[inline]
fn default() -> Self {
Lua::new()
}
}
impl Lua {
/// Creates a new Lua state and loads the **safe** subset of the standard libraries.
///
@@ -292,7 +298,6 @@ impl Lua {
/// See [`StdLib`] documentation for a list of unsafe modules that cannot be loaded.
///
/// [`StdLib`]: crate::StdLib
#[allow(clippy::new_without_default)]
pub fn new() -> Lua {
mlua_expect!(
Self::new_with(StdLib::ALL_SAFE, LuaOptions::default()),
@@ -335,7 +340,6 @@ impl Lua {
let lua = unsafe { Self::inner_new(libs, options) };
#[cfg(not(feature = "luau"))]
if libs.contains(StdLib::PACKAGE) {
mlua_expect!(lua.disable_c_modules(), "Error during disabling C modules");
}
@@ -353,23 +357,22 @@ impl Lua {
///
/// [`StdLib`]: crate::StdLib
pub unsafe fn unsafe_new_with(libs: StdLib, options: LuaOptions) -> Lua {
// Workaround to avoid stripping a few unused Lua symbols that could be imported
// by C modules in unsafe mode
let mut _symbols: Vec<*const extern "C-unwind" fn()> =
vec![ffi::lua_isuserdata as _, ffi::lua_tocfunction as _];
#[cfg(not(feature = "luau"))]
_symbols.extend_from_slice(&[
ffi::lua_atpanic as _,
ffi::luaL_loadstring as _,
ffi::luaL_openlibs as _,
]);
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
{
// Workaround to avoid stripping a few unused Lua symbols that could be imported
// by C modules in unsafe mode
let mut _symbols: Vec<*const extern "C-unwind" fn()> = vec![
ffi::lua_atpanic as _,
ffi::lua_isuserdata as _,
ffi::lua_tocfunction as _,
ffi::luaL_loadstring as _,
ffi::luaL_openlibs as _,
];
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
{
_symbols.push(ffi::lua_getglobal as _);
_symbols.push(ffi::lua_setglobal as _);
_symbols.push(ffi::luaL_setfuncs as _);
}
_symbols.push(ffi::lua_getglobal as _);
_symbols.push(ffi::lua_setglobal as _);
_symbols.push(ffi::luaL_setfuncs as _);
}
Self::inner_new(libs, options)
@@ -377,12 +380,11 @@ impl Lua {
/// Creates a new Lua state with required `libs` and `options`
unsafe fn inner_new(libs: StdLib, options: LuaOptions) -> Lua {
let mut mem_state: *mut MemoryState = Box::into_raw(Box::default());
let mem_state: *mut MemoryState = Box::into_raw(Box::default());
let mut state = ffi::lua_newstate(ALLOCATOR, mem_state as *mut c_void);
// If state is null then switch to Lua internal allocator
if state.is_null() {
drop(Box::from_raw(mem_state));
mem_state = ptr::null_mut();
state = ffi::luaL_newstate();
}
assert!(!state.is_null(), "Failed to instantiate Lua VM");
@@ -398,7 +400,6 @@ impl Lua {
let lua = Lua::init_from_ptr(state);
let extra = lua.extra.get();
(*extra).mem_state = NonNull::new(mem_state);
mlua_expect!(
load_from_std_lib(state, libs),
@@ -434,7 +435,7 @@ impl Lua {
}
#[cfg(feature = "luau")]
mlua_expect!(lua.prepare_luau_state(), "Error preparing Luau state");
mlua_expect!(lua.configure_luau(), "Error configuring Luau");
lua
}
@@ -498,6 +499,13 @@ impl Lua {
ptr
};
// Store `error_traceback` function on the ref stack
#[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))]
{
ffi::lua_pushcfunction(ref_thread, error_traceback);
assert_eq!(ffi::lua_gettop(ref_thread), ExtraData::ERROR_TRACEBACK_IDX);
}
// Create ExtraData
let extra = Arc::new(UnsafeCell::new(ExtraData {
inner: MaybeUninit::uninit(),
@@ -508,7 +516,6 @@ impl Lua {
app_data: AppData::default(),
safe: false,
libs: StdLib::NONE,
mem_state: None,
#[cfg(feature = "module")]
skip_memory_check: false,
ref_thread,
@@ -541,14 +548,8 @@ impl Lua {
// Store it in the registry
mlua_expect!(
(|state| {
push_gc_userdata(state, Arc::clone(&extra), true)?;
protect_lua!(state, 1, 0, fn(state) {
let extra_key = &EXTRA_REGISTRY_KEY as *const u8 as *const c_void;
ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, extra_key);
})
})(main_state),
"Error while storing extra data",
set_extra_data(main_state, &extra),
"Error while storing extra data"
);
// Register `DestructedUserdata` type
@@ -566,13 +567,6 @@ impl Lua {
);
assert_stack(main_state, ffi::LUA_MINSTACK);
// Set Luau callbacks userdata to extra data
// We can use global callbacks userdata since we don't allow C modules in Luau
#[cfg(feature = "luau")]
{
(*ffi::lua_callbacks(main_state)).userdata = extra.get() as *mut c_void;
}
let inner = Arc::new(LuaInner {
state: AtomicPtr::new(state),
main_state,
@@ -592,7 +586,6 @@ impl Lua {
///
/// [`StdLib`]: crate::StdLib
pub fn load_from_std_lib(&self, libs: StdLib) -> Result<()> {
#[cfg(not(feature = "luau"))]
let is_safe = unsafe { (*self.extra.get()).safe };
#[cfg(not(feature = "luau"))]
@@ -611,12 +604,9 @@ impl Lua {
let res = unsafe { load_from_std_lib(self.main_state, libs) };
// If `package` library loaded into a safe lua state then disable C modules
#[cfg(not(feature = "luau"))]
{
let curr_libs = unsafe { (*self.extra.get()).libs };
if is_safe && (curr_libs ^ (curr_libs | libs)).contains(StdLib::PACKAGE) {
mlua_expect!(self.disable_c_modules(), "Error during disabling C modules");
}
let curr_libs = unsafe { (*self.extra.get()).libs };
if is_safe && (curr_libs ^ (curr_libs | libs)).contains(StdLib::PACKAGE) {
mlua_expect!(self.disable_c_modules(), "Error during disabling C modules");
}
unsafe { (*self.extra.get()).libs |= libs };
@@ -718,50 +708,28 @@ impl Lua {
// The returned value then pushed onto the stack.
#[doc(hidden)]
#[cfg(not(tarpaulin_include))]
pub unsafe fn entrypoint<'lua, A, R, F>(self, func: F) -> Result<c_int>
pub unsafe fn entrypoint<'lua, A, R, F>(self, func: F) -> c_int
where
A: FromLuaMulti<'lua>,
R: IntoLua<'lua>,
F: Fn(&'lua Lua, A) -> Result<R> + MaybeSend + 'static,
{
let entrypoint_inner = |lua: &'lua Lua, func: F| {
let state = lua.state();
let nargs = ffi::lua_gettop(state);
check_stack(state, 3)?;
let (state, extra) = (self.state(), self.extra.get());
// It must be safe to drop `self` as in the module mode we keep strong reference to `Lua` in the registry
drop(self);
let mut args = MultiValue::new();
args.reserve(nargs as usize);
for _ in 0..nargs {
args.push_front(lua.pop_value());
}
// We create callback rather than call `func` directly to catch errors
// with attached stacktrace.
let callback = lua.create_callback(Box::new(move |lua, args| {
func(lua, A::from_lua_multi_args(args, 1, None, lua)?)?.into_lua_multi(lua)
}))?;
callback.call(args)
};
match entrypoint_inner(mem::transmute(&self), func) {
Ok(res) => {
self.push_value(res)?;
Ok(1)
}
Err(err) => {
self.push_value(Value::Error(err))?;
let state = self.state();
// Lua (self) must be dropped before triggering longjmp
drop(self);
ffi::lua_error(state)
}
}
callback_error_ext(state, extra, move |nargs| {
let lua: &Lua = mem::transmute((*extra).inner.assume_init_ref());
let args = A::from_stack_args(nargs, 1, None, lua)?;
func(lua, args)?.push_into_stack(lua)?;
Ok(1)
})
}
// A simple module entrypoint without arguments
#[doc(hidden)]
#[cfg(not(tarpaulin_include))]
pub unsafe fn entrypoint1<'lua, R, F>(self, func: F) -> Result<c_int>
pub unsafe fn entrypoint1<'lua, R, F>(self, func: F) -> c_int
where
R: IntoLua<'lua>,
F: Fn(&'lua Lua) -> Result<R> + MaybeSend + 'static,
@@ -1114,9 +1082,9 @@ impl Lua {
/// Returns the amount of memory (in bytes) currently used inside this Lua state.
pub fn used_memory(&self) -> usize {
unsafe {
match (*self.extra.get()).mem_state.map(|x| x.as_ref()) {
Some(mem_state) => mem_state.used_memory(),
None => {
match MemoryState::get(self.main_state) {
mem_state if !mem_state.is_null() => (*mem_state).used_memory(),
_ => {
// Get data from the Lua GC
let used_kbytes = ffi::lua_gc(self.main_state, ffi::LUA_GCCOUNT, 0);
let used_kbytes_rem = ffi::lua_gc(self.main_state, ffi::LUA_GCCOUNTB, 0);
@@ -1135,9 +1103,9 @@ impl Lua {
/// Does not work in module mode where Lua state is managed externally.
pub fn set_memory_limit(&self, limit: usize) -> Result<usize> {
unsafe {
match (*self.extra.get()).mem_state.map(|mut x| x.as_mut()) {
Some(mem_state) => Ok(mem_state.set_memory_limit(limit)),
None => Err(Error::MemoryLimitNotAvailable),
match MemoryState::get(self.main_state) {
mem_state if !mem_state.is_null() => Ok((*mem_state).set_memory_limit(limit)),
_ => Err(Error::MemoryLimitNotAvailable),
}
}
}
@@ -1537,8 +1505,9 @@ impl Lua {
R: IntoLuaMulti<'lua>,
F: Fn(&'lua Lua, A) -> Result<R> + MaybeSend + 'static,
{
self.create_callback(Box::new(move |lua, args| {
func(lua, A::from_lua_multi_args(args, 1, None, lua)?)?.into_lua_multi(lua)
self.create_callback(Box::new(move |lua, nargs| unsafe {
let args = A::from_stack_args(nargs, 1, None, lua)?;
func(lua, args)?.push_into_stack_multi(lua)
}))
}
@@ -1621,13 +1590,13 @@ impl Lua {
F: Fn(&'lua Lua, A) -> FR + MaybeSend + 'static,
FR: Future<Output = Result<R>> + 'lua,
{
self.create_async_callback(Box::new(move |lua, args| {
let args = match A::from_lua_multi_args(args, 1, None, lua) {
self.create_async_callback(Box::new(move |lua, args| unsafe {
let args = match A::from_lua_args(args, 1, None, lua) {
Ok(args) => args,
Err(e) => return Box::pin(future::err(e)),
};
let fut = func(lua, args);
Box::pin(async move { fut.await?.into_lua_multi(lua) })
Box::pin(async move { fut.await?.push_into_stack_multi(lua) })
}))
}
@@ -1655,7 +1624,7 @@ impl Lua {
self.push_ref(&func.0);
ffi::lua_xmove(state, thread_state, 1);
Ok(Thread(self.pop_ref()))
Ok(Thread::new(self.pop_ref()))
}
}
@@ -1683,7 +1652,7 @@ impl Lua {
ffi::lua_replace(thread_state, ffi::LUA_GLOBALSINDEX);
}
return Ok(Thread(LuaRef::new(self, index)));
return Ok(Thread::new(LuaRef::new(self, index)));
}
};
self.create_thread_inner(func)
@@ -1816,6 +1785,27 @@ impl Lua {
unsafe { self.make_userdata(UserDataCell::new(UserDataProxy::<T>(PhantomData))) }
}
/// Sets the metatable for a Luau builtin vector type.
#[cfg(any(all(feature = "luau", feature = "unstable"), doc))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "luau", feature = "unstable"))))]
pub fn set_vector_metatable(&self, metatable: Option<Table>) {
unsafe {
let state = self.state();
let _sg = StackGuard::new(state);
assert_stack(state, 2);
#[cfg(not(feature = "luau-vector4"))]
ffi::lua_pushvector(state, 0., 0., 0.);
#[cfg(feature = "luau-vector4")]
ffi::lua_pushvector(state, 0., 0., 0., 0.);
match metatable {
Some(metatable) => self.push_ref(&metatable.0),
None => ffi::lua_pushnil(state),
};
ffi::lua_setmetatable(state, -2);
}
}
/// Returns a handle to the global environment.
pub fn globals(&self) -> Table {
let state = self.state();
@@ -1838,7 +1828,7 @@ impl Lua {
let _sg = StackGuard::new(state);
assert_stack(state, 1);
ffi::lua_pushthread(state);
Thread(self.pop_ref())
Thread::new(self.pop_ref())
}
}
@@ -2429,17 +2419,128 @@ impl Lua {
ffi::lua_pop(state, 1);
Nil
}
_ => Value::UserData(AnyUserData(self.pop_ref())),
_ => Value::UserData(AnyUserData(self.pop_ref(), SubtypeId::None)),
}
}
ffi::LUA_TTHREAD => Value::Thread(Thread(self.pop_ref())),
ffi::LUA_TTHREAD => Value::Thread(Thread::new(self.pop_ref())),
#[cfg(feature = "luau")]
ffi::LUA_TBUFFER => {
// Buffer is represented as a userdata type
Value::UserData(AnyUserData(self.pop_ref(), SubtypeId::Buffer))
}
#[cfg(feature = "luajit")]
ffi::LUA_TCDATA => {
ffi::lua_pop(state, 1);
// TODO: Fix this in a next major release
panic!("cdata objects cannot be handled by mlua yet");
// CDATA is represented as a userdata type
Value::UserData(AnyUserData(self.pop_ref(), SubtypeId::CData))
}
_ => mlua_panic!("LUA_TNONE in pop_value"),
}
}
/// Returns value at given stack index without popping it.
///
/// Uses 2 stack spaces, does not call checkstack.
pub(crate) unsafe fn stack_value(&self, idx: c_int) -> Value {
let state = self.state();
match ffi::lua_type(state, idx) {
ffi::LUA_TNIL => Nil,
ffi::LUA_TBOOLEAN => Value::Boolean(ffi::lua_toboolean(state, idx) != 0),
ffi::LUA_TLIGHTUSERDATA => {
Value::LightUserData(LightUserData(ffi::lua_touserdata(state, idx)))
}
#[cfg(any(feature = "lua54", feature = "lua53"))]
ffi::LUA_TNUMBER => {
if ffi::lua_isinteger(state, idx) != 0 {
Value::Integer(ffi::lua_tointeger(state, idx))
} else {
Value::Number(ffi::lua_tonumber(state, idx))
}
}
#[cfg(any(
feature = "lua52",
feature = "lua51",
feature = "luajit",
feature = "luau"
))]
ffi::LUA_TNUMBER => {
let n = ffi::lua_tonumber(state, idx);
match num_traits::cast(n) {
Some(i) if (n - (i as Number)).abs() < Number::EPSILON => Value::Integer(i),
_ => Value::Number(n),
}
}
#[cfg(feature = "luau")]
ffi::LUA_TVECTOR => {
let v = ffi::lua_tovector(state, idx);
mlua_debug_assert!(!v.is_null(), "vector is null");
#[cfg(not(feature = "luau-vector4"))]
return Value::Vector(Vector([*v, *v.add(1), *v.add(2)]));
#[cfg(feature = "luau-vector4")]
return Value::Vector(Vector([*v, *v.add(1), *v.add(2), *v.add(3)]));
}
ffi::LUA_TSTRING => {
ffi::lua_xpush(state, self.ref_thread(), idx);
Value::String(String(self.pop_ref_thread()))
}
ffi::LUA_TTABLE => {
ffi::lua_xpush(state, self.ref_thread(), idx);
Value::Table(Table(self.pop_ref_thread()))
}
ffi::LUA_TFUNCTION => {
ffi::lua_xpush(state, self.ref_thread(), idx);
Value::Function(Function(self.pop_ref_thread()))
}
ffi::LUA_TUSERDATA => {
let wrapped_failure_mt_ptr = (*self.extra.get()).wrapped_failure_mt_ptr;
// We must prevent interaction with userdata types other than UserData OR a WrappedError.
// WrappedPanics are automatically resumed.
match get_gc_userdata::<WrappedFailure>(state, idx, wrapped_failure_mt_ptr).as_mut()
{
Some(WrappedFailure::Error(err)) => Value::Error(err.clone()),
Some(WrappedFailure::Panic(panic)) => {
if let Some(panic) = panic.take() {
resume_unwind(panic);
}
// Previously resumed panic?
Value::Nil
}
_ => {
ffi::lua_xpush(state, self.ref_thread(), idx);
Value::UserData(AnyUserData(self.pop_ref_thread(), SubtypeId::None))
}
}
}
ffi::LUA_TTHREAD => {
ffi::lua_xpush(state, self.ref_thread(), idx);
Value::Thread(Thread::new(self.pop_ref_thread()))
}
#[cfg(feature = "luau")]
ffi::LUA_TBUFFER => {
// Buffer is represented as a userdata type
ffi::lua_xpush(state, self.ref_thread(), idx);
Value::UserData(AnyUserData(self.pop_ref_thread(), SubtypeId::Buffer))
}
#[cfg(feature = "luajit")]
ffi::LUA_TCDATA => {
// CData is represented as a userdata type
ffi::lua_xpush(state, self.ref_thread(), idx);
Value::UserData(AnyUserData(self.pop_ref_thread(), SubtypeId::CData))
}
_ => mlua_panic!("LUA_TNONE in pop_value"),
@@ -2507,6 +2608,16 @@ impl Lua {
LuaRef::new(self, index)
}
#[inline]
pub(crate) unsafe fn push_error_traceback(&self) {
let state = self.state();
#[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))]
ffi::lua_xpush(self.ref_thread(), state, ExtraData::ERROR_TRACEBACK_IDX);
// Lua 5.2+ support light C functions that does not require extra allocations
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
ffi::lua_pushcfunction(state, error_traceback);
}
unsafe fn register_userdata_metatable<'lua, T: 'static>(
&'lua self,
mut registry: UserDataRegistry<'lua, T>,
@@ -2532,7 +2643,7 @@ impl Lua {
let mut has_name = false;
for (k, f) in registry.meta_fields {
has_name = has_name || k == MetaMethod::Type;
self.push_value(f(self, MultiValue::new())?.pop_front().unwrap())?;
mlua_assert!(f(self, 0)? == 1, "field function must return one value");
rawset_field(state, -2, MetaMethod::validate(&k)?)?;
}
// Set `__name/__type` if not provided
@@ -2557,12 +2668,13 @@ impl Lua {
push_table(state, 0, fields_nrec, true)?;
}
for (k, f) in registry.fields {
self.push_value(f(self, MultiValue::new())?.pop_front().unwrap())?;
mlua_assert!(f(self, 0)? == 1, "field function must return one value");
rawset_field(state, -2, &k)?;
}
rawset_field(state, metatable_index, "__index")?;
}
_ => {
ffi::lua_pop(state, 1);
// Propagate fields to the field getters
for (k, f) in registry.fields {
registry.field_getters.push((k, f))
@@ -2686,17 +2798,28 @@ impl Lua {
}
}
// Returns `TypeId` for the LuaRef, checking that it's a registered
// and not destructed UserData.
// Returns `TypeId` for the `lref` userdata, checking that it's registered and not destructed.
//
// Returns `None` if the userdata is registered but non-static.
pub(crate) unsafe fn get_userdata_type_id(&self, lref: &LuaRef) -> Result<Option<TypeId>> {
let ref_thread = self.ref_thread();
if ffi::lua_getmetatable(ref_thread, lref.index) == 0 {
pub(crate) unsafe fn get_userdata_ref_type_id(&self, lref: &LuaRef) -> Result<Option<TypeId>> {
self.get_userdata_type_id_inner(self.ref_thread(), lref.index)
}
// Same as `get_userdata_ref_type_id` but assumes the userdata is already on the stack.
pub(crate) unsafe fn get_userdata_type_id(&self, idx: c_int) -> Result<Option<TypeId>> {
self.get_userdata_type_id_inner(self.state(), idx)
}
unsafe fn get_userdata_type_id_inner(
&self,
state: *mut ffi::lua_State,
idx: c_int,
) -> Result<Option<TypeId>> {
if ffi::lua_getmetatable(state, idx) == 0 {
return Err(Error::UserDataTypeMismatch);
}
let mt_ptr = ffi::lua_topointer(ref_thread, -1);
ffi::lua_pop(ref_thread, 1);
let mt_ptr = ffi::lua_topointer(state, -1);
ffi::lua_pop(state, 1);
// Fast path to skip looking up the metatable in the map
let (last_mt, last_type_id) = (*self.extra.get()).last_checked_userdata_mt;
@@ -2719,7 +2842,7 @@ impl Lua {
// Pushes a LuaRef (userdata) value onto the stack, returning their `TypeId`.
// Uses 1 stack space, does not call checkstack.
pub(crate) unsafe fn push_userdata_ref(&self, lref: &LuaRef) -> Result<Option<TypeId>> {
let type_id = self.get_userdata_type_id(lref)?;
let type_id = self.get_userdata_type_id_inner(self.ref_thread(), lref.index)?;
self.push_ref(lref);
Ok(type_id)
}
@@ -2754,24 +2877,9 @@ impl Lua {
let lua: &Lua = mem::transmute((*extra).inner.assume_init_ref());
let _guard = StateGuard::new(&lua.0, state);
let mut args = MultiValue::new_or_pooled(lua);
args.reserve(nargs as usize);
for _ in 0..nargs {
args.push_front(lua.pop_value());
}
let func = &*(*upvalue).data;
let mut results = func(lua, args)?;
let nresults = results.len() as c_int;
check_stack(state, nresults)?;
for r in results.drain_all() {
lua.push_value(r)?;
}
MultiValue::return_to_pool(results, lua);
Ok(nresults)
func(lua, nargs)
})
}
@@ -2824,12 +2932,7 @@ impl Lua {
let lua: &Lua = mem::transmute((*extra).inner.assume_init_ref());
let _guard = StateGuard::new(&lua.0, state);
let mut args = MultiValue::new_or_pooled(lua);
args.reserve(nargs as usize);
for _ in 0..nargs {
args.push_front(lua.pop_value());
}
let args = MultiValue::from_stack_multi(nargs, lua)?;
let func = &*(*upvalue).data;
let fut = func(lua, args);
let extra = Arc::clone(&(*upvalue).extra);
@@ -2851,28 +2954,32 @@ impl Lua {
let upvalue = get_userdata::<AsyncPollUpvalue>(state, ffi::lua_upvalueindex(1));
let extra = (*upvalue).extra.get();
callback_error_ext(state, extra, |_| {
// Lua ensures that `LUA_MINSTACK` stack spaces are available (after pushing arguments)
let lua: &Lua = mem::transmute((*extra).inner.assume_init_ref());
let _guard = StateGuard::new(&lua.0, state);
let fut = &mut (*upvalue).data;
let mut ctx = Context::from_waker(lua.waker());
match fut.as_mut().poll(&mut ctx) {
Poll::Pending => Ok(0),
Poll::Ready(results) => {
let mut results = results?;
let nresults = results.len();
lua.push_value(Value::Integer(nresults as _))?;
match nresults {
0 => Ok(1),
1 | 2 => {
// Fast path for 1 or 2 results without creating a table
for r in results.drain_all() {
lua.push_value(r)?;
Poll::Pending => {
ffi::lua_pushnil(state);
let pending = &ASYNC_POLL_PENDING as *const u8 as *mut c_void;
ffi::lua_pushlightuserdata(state, pending);
Ok(2)
}
Poll::Ready(nresults) => {
match nresults? {
nresults @ 0..=2 => {
// Fast path for up to 2 results without creating a table
ffi::lua_pushinteger(state, nresults as _);
if nresults > 0 {
ffi::lua_insert(state, -nresults - 1);
}
MultiValue::return_to_pool(results, lua);
Ok(nresults as c_int + 1)
Ok(nresults + 1)
}
_ => {
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)?))?;
Ok(2)
}
@@ -2914,20 +3021,17 @@ impl Lua {
let coroutine = self.globals().get::<_, Table>("coroutine")?;
let env = self.create_table_with_capacity(0, 4)?;
let env = self.create_table_with_capacity(0, 3)?;
env.set("get_poll", get_poll)?;
// Cache `yield` function
env.set("yield", coroutine.get::<_, Function>("yield")?)?;
unsafe {
env.set("unpack", self.create_c_function(unpack)?)?;
}
env.set("pending", {
LightUserData(&ASYNC_POLL_PENDING as *const u8 as *mut c_void)
})?;
self.load(
r#"
local poll = get_poll(...)
local pending, yield, unpack = pending, yield, unpack
while true do
local nres, res, res2 = poll()
if nres ~= nil then
@@ -2941,7 +3045,7 @@ impl Lua {
return unpack(res, nres)
end
end
yield(pending)
yield(res) -- `res` is a "pending" value
end
"#,
)
@@ -3032,9 +3136,10 @@ impl Lua {
ffi::lua_setuservalue(state, -2);
}
Ok(AnyUserData(self.pop_ref()))
Ok(AnyUserData(self.pop_ref(), SubtypeId::None))
}
// Luau version located in `luau/mod.rs`
#[cfg(not(feature = "luau"))]
fn disable_c_modules(&self) -> Result<()> {
let package: Table = self.globals().get("package")?;
@@ -3073,16 +3178,13 @@ impl Lua {
#[inline]
pub(crate) unsafe fn unlikely_memory_error(&self) -> bool {
// MemoryInfo is empty in module mode so we cannot predict memory limits
(*self.extra.get())
.mem_state
.map(|x| x.as_ref().memory_limit() == 0)
.unwrap_or_else(|| {
// Alternatively, check the special flag (only for module mode)
#[cfg(feature = "module")]
return (*self.extra.get()).skip_memory_check;
#[cfg(not(feature = "module"))]
return false;
})
match MemoryState::get(self.main_state) {
mem_state if !mem_state.is_null() => (*mem_state).memory_limit() == 0,
#[cfg(feature = "module")]
_ => (*self.extra.get()).skip_memory_check, // Check the special flag (only for module mode)
#[cfg(not(feature = "module"))]
_ => false,
}
}
#[cfg(feature = "unstable")]
@@ -3110,13 +3212,13 @@ impl LuaInner {
}
#[inline]
pub(crate) fn new_multivalue_from_pool(&self) -> MultiValue {
pub(crate) fn pop_multivalue_from_pool(&self) -> Option<Vec<Value>> {
let extra = unsafe { &mut *self.extra.get() };
extra.multivalue_pool.pop().unwrap_or_default()
extra.multivalue_pool.pop()
}
#[inline]
pub(crate) fn return_multivalue_to_pool(&self, mut multivalue: MultiValue) {
pub(crate) fn push_multivalue_to_pool(&self, mut multivalue: Vec<Value>) {
let extra = unsafe { &mut *self.extra.get() };
if extra.multivalue_pool.len() < MULTIVALUE_POOL_SIZE {
multivalue.clear();
@@ -3128,11 +3230,9 @@ impl LuaInner {
}
impl ExtraData {
#[cfg(feature = "luau")]
#[inline]
pub(crate) fn mem_state(&self) -> NonNull<MemoryState> {
self.mem_state.unwrap()
}
// Index of `error_traceback` function in auxiliary thread stack
#[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))]
const ERROR_TRACEBACK_IDX: c_int = 1;
}
struct StateGuard<'a>(&'a LuaInner, *mut ffi::lua_State);
@@ -3150,13 +3250,13 @@ impl<'a> Drop for StateGuard<'a> {
}
}
#[cfg(feature = "luau")]
unsafe fn extra_data(state: *mut ffi::lua_State) -> *mut ExtraData {
(*ffi::lua_callbacks(state)).userdata as *mut ExtraData
}
#[cfg(feature = "luau")]
if cfg!(not(feature = "module")) {
// In the main app we can use `lua_callbacks` to access ExtraData
return (*ffi::lua_callbacks(state)).userdata as *mut _;
}
#[cfg(not(feature = "luau"))]
unsafe fn extra_data(state: *mut ffi::lua_State) -> *mut ExtraData {
let extra_key = &EXTRA_REGISTRY_KEY as *const u8 as *const c_void;
if ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, extra_key) != ffi::LUA_TUSERDATA {
// `ExtraData` can be null only when Lua state is foreign.
@@ -3169,6 +3269,23 @@ unsafe fn extra_data(state: *mut ffi::lua_State) -> *mut ExtraData {
(*extra_ptr).get()
}
unsafe fn set_extra_data(
state: *mut ffi::lua_State,
extra: &Arc<UnsafeCell<ExtraData>>,
) -> Result<()> {
#[cfg(feature = "luau")]
if cfg!(not(feature = "module")) {
(*ffi::lua_callbacks(state)).userdata = extra.get() as *mut _;
return Ok(());
}
push_gc_userdata(state, Arc::clone(extra), true)?;
protect_lua!(state, 1, 0, fn(state) {
let extra_key = &EXTRA_REGISTRY_KEY as *const u8 as *const c_void;
ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, extra_key);
})
}
// Creates required entries in the metatable cache (see `util::METATABLE_CACHE`)
pub(crate) fn init_metatable_cache(cache: &mut FxHashMap<TypeId, u8>) {
cache.insert(TypeId::of::<Arc<UnsafeCell<ExtraData>>>(), 0);
@@ -3407,6 +3524,12 @@ unsafe fn load_from_std_lib(state: *mut ffi::lua_State, libs: StdLib) -> Result<
}
}
#[cfg(feature = "luau")]
if libs.contains(StdLib::BUFFER) {
requiref(state, ffi::LUA_BUFFERLIBNAME, ffi::luaopen_buffer, 1)?;
ffi::lua_pop(state, 1);
}
if libs.contains(StdLib::MATH) {
requiref(state, ffi::LUA_MATHLIBNAME, ffi::luaopen_math, 1)?;
ffi::lua_pop(state, 1);
@@ -3422,6 +3545,11 @@ unsafe fn load_from_std_lib(state: *mut ffi::lua_State, libs: StdLib) -> Result<
requiref(state, ffi::LUA_LOADLIBNAME, ffi::luaopen_package, 1)?;
ffi::lua_pop(state, 1);
}
#[cfg(feature = "luau")]
if libs.contains(StdLib::PACKAGE) {
let lua: &Lua = mem::transmute((*extra_data(state)).inner.assume_init_ref());
crate::luau::register_package_module(lua)?;
}
#[cfg(feature = "luajit")]
{
+12 -61
View File
@@ -1,25 +1,19 @@
use std::ffi::CStr;
use std::os::raw::{c_float, c_int};
use std::string::String as StdString;
use crate::chunk::ChunkMode;
use crate::error::{Error, Result};
use crate::error::Result;
use crate::lua::Lua;
use crate::table::Table;
use crate::util::{check_stack, StackGuard};
use crate::value::Value;
// Since Luau has some missing standard function, we re-implement them here
// Since Luau has some missing standard functions, we re-implement them here
impl Lua {
pub(crate) unsafe fn prepare_luau_state(&self) -> Result<()> {
pub(crate) unsafe fn configure_luau(&self) -> Result<()> {
let globals = self.globals();
globals.raw_set(
"collectgarbage",
self.create_c_function(lua_collectgarbage)?,
)?;
globals.raw_set("require", self.create_function(lua_require)?)?;
globals.raw_set("vector", self.create_c_function(lua_vector)?)?;
// Set `_VERSION` global to include version number
@@ -30,6 +24,11 @@ impl Lua {
Ok(())
}
pub(crate) fn disable_c_modules(&self) -> Result<()> {
package::disable_dylibs(self);
Ok(())
}
}
unsafe extern "C-unwind" fn lua_collectgarbage(state: *mut ffi::lua_State) -> c_int {
@@ -69,58 +68,6 @@ unsafe extern "C-unwind" fn lua_collectgarbage(state: *mut ffi::lua_State) -> c_
}
}
fn lua_require(lua: &Lua, name: Option<StdString>) -> Result<Value> {
let name = name.ok_or_else(|| Error::runtime("invalid module name"))?;
// Find module in the cache
let state = lua.state();
let loaded = unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 2)?;
protect_lua!(state, 0, 1, fn(state) {
ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("_LOADED"));
})?;
Table(lua.pop_ref())
};
if let Some(v) = loaded.raw_get(name.clone())? {
return Ok(v);
}
// Load file from filesystem
let mut search_path = std::env::var("LUAU_PATH").unwrap_or_default();
if search_path.is_empty() {
search_path = "?.luau;?.lua".into();
}
let (mut source, mut source_name) = (None, String::new());
for path in search_path.split(';') {
let file_path = path.replacen('?', &name, 1);
if let Ok(buf) = std::fs::read(&file_path) {
source = Some(buf);
source_name = file_path;
break;
}
}
let source = source.ok_or_else(|| Error::runtime(format!("cannot find '{name}'")))?;
let value = lua
.load(&source)
.set_name(&format!("={source_name}"))
.set_mode(ChunkMode::Text)
.call::<_, Value>(())?;
// Save in the cache
loaded.raw_set(
name,
match value.clone() {
Value::Nil => Value::Boolean(true),
v => v,
},
)?;
Ok(value)
}
// Luau vector datatype constructor
unsafe extern "C-unwind" fn lua_vector(state: *mut ffi::lua_State) -> c_int {
let x = ffi::luaL_checknumber(state, 1) as c_float;
@@ -135,3 +82,7 @@ unsafe extern "C-unwind" fn lua_vector(state: *mut ffi::lua_State) -> c_int {
ffi::lua_pushvector(state, x, y, z, w);
1
}
pub(crate) use package::register_package_module;
mod package;
+268
View File
@@ -0,0 +1,268 @@
use std::ffi::CStr;
use std::fmt::Write;
use std::os::raw::c_int;
use std::path::{PathBuf, MAIN_SEPARATOR_STR};
use std::string::String as StdString;
use std::{env, fs};
use crate::chunk::ChunkMode;
use crate::error::Result;
use crate::lua::Lua;
use crate::table::Table;
use crate::types::RegistryKey;
use crate::value::{IntoLua, Value};
#[cfg(unix)]
use {libloading::Library, rustc_hash::FxHashMap};
//
// Luau package module
//
#[cfg(unix)]
const TARGET_MLUA_LUAU_ABI_VERSION: u32 = 1;
#[cfg(all(unix, feature = "module"))]
#[no_mangle]
#[used]
pub static MLUA_LUAU_ABI_VERSION: u32 = TARGET_MLUA_LUAU_ABI_VERSION;
// We keep reference to the `package` table in registry under this key
struct PackageKey(RegistryKey);
// We keep reference to the loaded dylibs in application data
#[cfg(unix)]
struct LoadedDylibs(FxHashMap<PathBuf, Library>);
#[cfg(unix)]
impl std::ops::Deref for LoadedDylibs {
type Target = FxHashMap<PathBuf, Library>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[cfg(unix)]
impl std::ops::DerefMut for LoadedDylibs {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
pub(crate) fn register_package_module(lua: &Lua) -> Result<()> {
// Create the package table and store it in app_data for later use (bypassing globals lookup)
let package = lua.create_table()?;
lua.set_app_data(PackageKey(lua.create_registry_value(package.clone())?));
// Set `package.path`
let mut search_path = env::var("LUAU_PATH")
.or_else(|_| env::var("LUA_PATH"))
.unwrap_or_default();
if search_path.is_empty() {
search_path = "?.luau;?.lua".to_string();
}
package.raw_set("path", search_path)?;
// Set `package.cpath`
#[cfg(unix)]
{
let mut search_cpath = env::var("LUAU_CPATH")
.or_else(|_| env::var("LUA_CPATH"))
.unwrap_or_default();
if search_cpath.is_empty() {
if cfg!(any(target_os = "macos", target_os = "ios")) {
search_cpath = "?.dylib".to_string();
} else {
search_cpath = "?.so".to_string();
}
}
package.raw_set("cpath", search_cpath)?;
}
// Set `package.loaded` (table with a list of loaded modules)
let loaded = lua.create_table()?;
package.raw_set("loaded", loaded.clone())?;
lua.set_named_registry_value("_LOADED", loaded)?;
// Set `package.loaders`
let loaders = lua.create_sequence_from([lua.create_function(lua_loader)?])?;
package.raw_set("loaders", loaders.clone())?;
#[cfg(unix)]
{
loaders.push(lua.create_function(dylib_loader)?)?;
lua.set_app_data(LoadedDylibs(FxHashMap::default()));
}
lua.set_named_registry_value("_LOADERS", loaders)?;
// Register the module and `require` function in globals
let globals = lua.globals();
globals.raw_set("package", package)?;
globals.raw_set("require", unsafe { lua.create_c_function(lua_require)? })?;
Ok(())
}
pub(crate) fn disable_dylibs(lua: &Lua) {
// Presence of `LoadedDylibs` in app data is used as a flag
// to check whether binary modules are enabled
#[cfg(unix)]
lua.remove_app_data::<LoadedDylibs>();
}
unsafe extern "C-unwind" fn lua_require(state: *mut ffi::lua_State) -> c_int {
ffi::lua_settop(state, 1);
let name = ffi::luaL_checkstring(state, 1);
ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("_LOADED")); // _LOADED is at index 2
if ffi::lua_rawgetfield(state, 2, name) != ffi::LUA_TNIL {
return 1; // module is already loaded
}
ffi::lua_pop(state, 1); // remove nil
// load the module
let err_buf = ffi::lua_newuserdata_t::<StdString>(state);
err_buf.write(StdString::new());
ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("_LOADERS")); // _LOADERS is at index 3
for i in 1.. {
if ffi::lua_rawgeti(state, -1, i) == ffi::LUA_TNIL {
// no more loaders?
if (*err_buf).is_empty() {
ffi::luaL_error(state, cstr!("module '%s' not found"), name);
} else {
let bytes = (*err_buf).as_bytes();
let extra = ffi::lua_pushlstring(state, bytes.as_ptr() as *const _, bytes.len());
ffi::luaL_error(state, cstr!("module '%s' not found:%s"), name, extra);
}
}
ffi::lua_pushvalue(state, 1); // name arg
ffi::lua_call(state, 1, 2); // call loader
match ffi::lua_type(state, -2) {
ffi::LUA_TFUNCTION => break, // loader found
ffi::LUA_TSTRING => {
// error message
let msg = ffi::lua_tostring(state, -2);
let msg = CStr::from_ptr(msg).to_string_lossy();
_ = write!(&mut *err_buf, "\n\t{msg}");
}
_ => {}
}
ffi::lua_pop(state, 2); // remove both results
}
ffi::lua_pushvalue(state, 1); // name is 1st argument to module loader
ffi::lua_rotate(state, -2, 1); // loader data <-> name
// stack: ...; loader function; module name; loader data
ffi::lua_call(state, 2, 1);
// stack: ...; result from loader function
if ffi::lua_isnil(state, -1) != 0 {
ffi::lua_pop(state, 1);
ffi::lua_pushboolean(state, 1); // use true as result
}
ffi::lua_pushvalue(state, -1); // make copy of entrypoint result
ffi::lua_setfield(state, 2, name); /* _LOADED[name] = returned value */
1
}
/// Searches for the given `name` in the given `path`.
///
/// `path` is a string containing a sequence of templates separated by semicolons.
fn package_searchpath(name: &str, search_path: &str, try_prefix: bool) -> Option<PathBuf> {
let mut names = vec![name.replace('.', MAIN_SEPARATOR_STR)];
if try_prefix && name.contains('.') {
let prefix = name.split_once('.').map(|(prefix, _)| prefix).unwrap();
names.push(prefix.to_string());
}
for path in search_path.split(';') {
for name in &names {
let file_path = PathBuf::from(path.replace('?', name));
if let Ok(true) = fs::metadata(&file_path).map(|m| m.is_file()) {
return Some(file_path);
}
}
}
None
}
//
// Module loaders
//
/// Tries to load a lua (text) file
fn lua_loader(lua: &Lua, modname: StdString) -> Result<Value> {
let package = {
let key = lua.app_data_ref::<PackageKey>().unwrap();
lua.registry_value::<Table>(&key.0)
}?;
let search_path = package.get::<_, StdString>("path").unwrap_or_default();
if let Some(file_path) = package_searchpath(&modname, &search_path, false) {
match fs::read(&file_path) {
Ok(buf) => {
return lua
.load(&buf)
.set_name(&format!("={}", file_path.display()))
.set_mode(ChunkMode::Text)
.into_function()
.map(Value::Function);
}
Err(err) => {
return format!("cannot open '{}': {err}", file_path.display()).into_lua(lua);
}
}
}
Ok(Value::Nil)
}
/// Tries to load a dynamic library
#[cfg(unix)]
fn dylib_loader(lua: &Lua, modname: StdString) -> Result<Value> {
let package = {
let key = lua.app_data_ref::<PackageKey>().unwrap();
lua.registry_value::<Table>(&key.0)
}?;
let search_cpath = package.get::<_, StdString>("cpath").unwrap_or_default();
let find_symbol = |lib: &Library| unsafe {
if let Ok(entry) = lib.get::<ffi::lua_CFunction>(format!("luaopen_{modname}\0").as_bytes())
{
return lua.create_c_function(*entry).map(Value::Function);
}
// Try all in one mode
if let Ok(entry) = lib.get::<ffi::lua_CFunction>(
format!("luaopen_{}\0", modname.replace('.', "_")).as_bytes(),
) {
return lua.create_c_function(*entry).map(Value::Function);
}
"cannot find module entrypoint".into_lua(lua)
};
if let Some(file_path) = package_searchpath(&modname, &search_cpath, true) {
let file_path = file_path.canonicalize()?;
// Load the library and check for symbol
unsafe {
let mut loaded_dylibs = match lua.app_data_mut::<LoadedDylibs>() {
Some(loaded_dylibs) => loaded_dylibs,
None => return "dynamic libraries are disabled in safe mode".into_lua(lua),
};
// Check if it's already loaded
if let Some(lib) = loaded_dylibs.get(&file_path) {
return find_symbol(lib);
}
if let Ok(lib) = Library::new(&file_path) {
// Check version
let mod_version = lib.get::<*const u32>(b"MLUA_LUAU_ABI_VERSION");
let mod_version = mod_version.map(|v| **v).unwrap_or_default();
if mod_version != TARGET_MLUA_LUAU_ABI_VERSION {
let err = format!("wrong module ABI version (expected {TARGET_MLUA_LUAU_ABI_VERSION}, got {mod_version})");
return err.into_lua(lua);
}
let symbol = find_symbol(&lib);
loaded_dylibs.insert(file_path, lib);
return symbol;
}
}
}
Ok(Value::Nil)
}
+24 -29
View File
@@ -2,11 +2,9 @@ use std::alloc::{self, Layout};
use std::os::raw::c_void;
use std::ptr;
#[cfg(feature = "luau")]
use crate::lua::ExtraData;
pub(crate) static ALLOCATOR: ffi::lua_Alloc = allocator;
#[repr(C)]
#[derive(Default)]
pub(crate) struct MemoryState {
used_memory: isize,
@@ -20,6 +18,21 @@ pub(crate) struct MemoryState {
}
impl MemoryState {
#[inline]
pub(crate) unsafe fn get(state: *mut ffi::lua_State) -> *mut Self {
let mut mem_state = ptr::null_mut();
#[cfg(feature = "luau")]
{
ffi::lua_getallocf(state, &mut mem_state);
mlua_assert!(!mem_state.is_null(), "Luau state has no allocator userdata");
}
#[cfg(not(feature = "luau"))]
if ffi::lua_getallocf(state, &mut mem_state) != ALLOCATOR {
mem_state = ptr::null_mut();
}
mem_state as *mut MemoryState
}
#[inline]
pub(crate) fn used_memory(&self) -> usize {
self.used_memory as usize
@@ -37,36 +50,21 @@ impl MemoryState {
prev_limit as usize
}
// This function is used primarily for calling `lua_pushcfunction` in lua5.1/jit
// This function is used primarily for calling `lua_pushcfunction` in lua5.1/jit/luau
// to bypass the memory limit (if set).
#[cfg(any(feature = "lua51", feature = "luajit"))]
#[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))]
#[inline]
pub(crate) unsafe fn relax_limit_with(state: *mut ffi::lua_State, f: impl FnOnce()) {
let mut mem_state: *mut c_void = ptr::null_mut();
if ffi::lua_getallocf(state, &mut mem_state) == ALLOCATOR {
(*(mem_state as *mut MemoryState)).ignore_limit = true;
let mem_state = Self::get(state);
if !mem_state.is_null() {
(*mem_state).ignore_limit = true;
f();
(*(mem_state as *mut MemoryState)).ignore_limit = false;
(*mem_state).ignore_limit = false;
} else {
f();
}
}
// Same as the above but for Luau
// It does not have `lua_getallocf` function, so instead we use `lua_callbacks`
#[cfg(feature = "luau")]
#[inline]
pub(crate) unsafe fn relax_limit_with(state: *mut ffi::lua_State, f: impl FnOnce()) {
let extra = (*ffi::lua_callbacks(state)).userdata as *mut ExtraData;
if extra.is_null() {
return f();
}
let mem_state = (*extra).mem_state();
(*mem_state.as_ptr()).ignore_limit = true;
f();
(*mem_state.as_ptr()).ignore_limit = false;
}
// Does nothing apart from calling `f()`, we don't need to bypass any limits
#[cfg(any(feature = "lua52", feature = "lua53", feature = "lua54"))]
#[inline]
@@ -76,12 +74,9 @@ impl MemoryState {
// Returns `true` if the memory limit was reached on the last memory operation
#[cfg(feature = "luau")]
#[inline]
pub(crate) unsafe fn limit_reached(state: *mut ffi::lua_State) -> bool {
let extra = (*ffi::lua_callbacks(state)).userdata as *mut ExtraData;
if extra.is_null() {
return false;
}
(*(*extra).mem_state().as_ptr()).limit_reached
(*Self::get(state)).limit_reached
}
}
+122 -27
View File
@@ -1,9 +1,11 @@
use std::iter::FromIterator;
use std::ops::{Deref, DerefMut};
use std::os::raw::c_int;
use std::result::Result as StdResult;
use crate::error::Result;
use crate::lua::Lua;
use crate::util::check_stack;
use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, MultiValue, Nil};
/// Result is convertible to `MultiValue` following the common Lua idiom of returning the result
@@ -11,7 +13,7 @@ use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, MultiValue, Nil
impl<'lua, T: IntoLua<'lua>, E: IntoLua<'lua>> IntoLuaMulti<'lua> for StdResult<T, E> {
#[inline]
fn into_lua_multi(self, lua: &'lua Lua) -> Result<MultiValue<'lua>> {
let mut result = MultiValue::new_or_pooled(lua);
let mut result = MultiValue::with_lua_and_capacity(lua, 2);
match self {
Ok(v) => result.push_front(v.into_lua(lua)?),
Err(e) => {
@@ -23,33 +25,71 @@ impl<'lua, T: IntoLua<'lua>, E: IntoLua<'lua>> IntoLuaMulti<'lua> for StdResult<
}
}
impl<'lua, E: IntoLua<'lua>> IntoLuaMulti<'lua> for StdResult<(), E> {
#[inline]
fn into_lua_multi(self, lua: &'lua Lua) -> Result<MultiValue<'lua>> {
match self {
Ok(_) => return Ok(MultiValue::new()),
Err(e) => {
let mut result = MultiValue::with_lua_and_capacity(lua, 2);
result.push_front(e.into_lua(lua)?);
result.push_front(Nil);
Ok(result)
}
}
}
}
impl<'lua, T: IntoLua<'lua>> IntoLuaMulti<'lua> for T {
#[inline]
fn into_lua_multi(self, lua: &'lua Lua) -> Result<MultiValue<'lua>> {
let mut v = MultiValue::new_or_pooled(lua);
let mut v = MultiValue::with_lua_and_capacity(lua, 1);
v.push_front(self.into_lua(lua)?);
Ok(v)
}
#[inline]
unsafe fn push_into_stack_multi(self, lua: &'lua Lua) -> Result<c_int> {
self.push_into_stack(lua)?;
Ok(1)
}
}
impl<'lua, T: FromLua<'lua>> FromLuaMulti<'lua> for T {
#[inline]
fn from_lua_multi(mut values: MultiValue<'lua>, lua: &'lua Lua) -> Result<Self> {
let res = T::from_lua(values.pop_front().unwrap_or(Nil), lua);
MultiValue::return_to_pool(values, lua);
res
T::from_lua(values.pop_front().unwrap_or(Nil), lua)
}
#[inline]
fn from_lua_multi_args(
mut values: MultiValue<'lua>,
fn from_lua_args(
mut args: MultiValue<'lua>,
i: usize,
to: Option<&str>,
lua: &'lua Lua,
) -> Result<Self> {
let res = T::from_lua_arg(values.pop_front().unwrap_or(Nil), i, to, lua);
MultiValue::return_to_pool(values, lua);
res
T::from_lua_arg(args.pop_front().unwrap_or(Nil), i, to, lua)
}
#[inline]
unsafe fn from_stack_multi(nvals: c_int, lua: &'lua Lua) -> Result<Self> {
if nvals == 0 {
return T::from_lua(Nil, lua);
}
T::from_stack(-nvals, lua)
}
#[inline]
unsafe fn from_stack_args(
nargs: c_int,
i: usize,
to: Option<&str>,
lua: &'lua Lua,
) -> Result<Self> {
if nargs == 0 {
return T::from_lua_arg(Nil, i, to, lua);
}
T::from_stack_arg(-nargs, i, to, lua)
}
}
@@ -141,7 +181,7 @@ impl<T> DerefMut for Variadic<T> {
impl<'lua, T: IntoLua<'lua>> IntoLuaMulti<'lua> for Variadic<T> {
#[inline]
fn into_lua_multi(self, lua: &'lua Lua) -> Result<MultiValue<'lua>> {
let mut values = MultiValue::new_or_pooled(lua);
let mut values = MultiValue::with_lua_and_capacity(lua, self.0.len());
values.refill(self.0.into_iter().map(|e| e.into_lua(lua)))?;
Ok(values)
}
@@ -150,13 +190,11 @@ impl<'lua, T: IntoLua<'lua>> IntoLuaMulti<'lua> for Variadic<T> {
impl<'lua, T: FromLua<'lua>> FromLuaMulti<'lua> for Variadic<T> {
#[inline]
fn from_lua_multi(mut values: MultiValue<'lua>, lua: &'lua Lua) -> Result<Self> {
let res = values
values
.drain_all()
.map(|e| T::from_lua(e, lua))
.collect::<Result<Vec<T>>>()
.map(Variadic);
MultiValue::return_to_pool(values, lua);
res
.map(Variadic)
}
}
@@ -165,14 +203,26 @@ macro_rules! impl_tuple {
impl<'lua> IntoLuaMulti<'lua> for () {
#[inline]
fn into_lua_multi(self, lua: &'lua Lua) -> Result<MultiValue<'lua>> {
Ok(MultiValue::new_or_pooled(lua))
Ok(MultiValue::with_lua_and_capacity(lua, 0))
}
#[inline]
unsafe fn push_into_stack_multi(self, _lua: &'lua Lua) -> Result<c_int> {
Ok(0)
}
}
impl<'lua> FromLuaMulti<'lua> for () {
#[inline]
fn from_lua_multi(values: MultiValue<'lua>, lua: &'lua Lua) -> Result<Self> {
MultiValue::return_to_pool(values, lua);
fn from_lua_multi(_values: MultiValue<'lua>, _lua: &'lua Lua) -> Result<Self> {
Ok(())
}
#[inline]
unsafe fn from_stack_multi(nvals: c_int, lua: &'lua Lua) -> Result<Self> {
if nvals > 0 {
ffi::lua_pop(lua.state(), nvals);
}
Ok(())
}
}
@@ -183,8 +233,7 @@ macro_rules! impl_tuple {
where $($name: IntoLua<'lua>,)*
$last: IntoLuaMulti<'lua>
{
#[allow(unused_mut)]
#[allow(non_snake_case)]
#[allow(unused_mut, non_snake_case)]
#[inline]
fn into_lua_multi(self, lua: &'lua Lua) -> Result<MultiValue<'lua>> {
let ($($name,)* $last,) = self;
@@ -193,14 +242,30 @@ macro_rules! impl_tuple {
push_reverse!(results, $($name.into_lua(lua)?,)*);
Ok(results)
}
#[allow(non_snake_case)]
#[inline]
unsafe fn push_into_stack_multi(self, lua: &'lua Lua) -> Result<c_int> {
let ($($name,)* $last,) = self;
let mut nresults = 0;
$(
_ = $name;
nresults += 1;
)*
check_stack(lua.state(), nresults + 1)?;
$(
$name.push_into_stack(lua)?;
)*
nresults += $last.push_into_stack_multi(lua)?;
Ok(nresults)
}
}
impl<'lua, $($name,)* $last> FromLuaMulti<'lua> for ($($name,)* $last,)
where $($name: FromLua<'lua>,)*
$last: FromLuaMulti<'lua>
{
#[allow(unused_mut)]
#[allow(non_snake_case)]
#[allow(unused_mut, non_snake_case)]
#[inline]
fn from_lua_multi(mut values: MultiValue<'lua>, lua: &'lua Lua) -> Result<Self> {
$(let $name = FromLua::from_lua(values.pop_front().unwrap_or(Nil), lua)?;)*
@@ -208,15 +273,45 @@ macro_rules! impl_tuple {
Ok(($($name,)* $last,))
}
#[allow(unused_mut)]
#[allow(non_snake_case)]
#[allow(unused_mut, non_snake_case)]
#[inline]
fn from_lua_multi_args(mut values: MultiValue<'lua>, mut i: usize, to: Option<&str>, lua: &'lua Lua) -> Result<Self> {
fn from_lua_args(mut args: MultiValue<'lua>, mut i: usize, to: Option<&str>, lua: &'lua Lua) -> Result<Self> {
$(
let $name = FromLua::from_lua_arg(values.pop_front().unwrap_or(Nil), i, to, lua)?;
let $name = FromLua::from_lua_arg(args.pop_front().unwrap_or(Nil), i, to, lua)?;
i += 1;
)*
let $last = FromLuaMulti::from_lua_multi_args(values, i, to, lua)?;
let $last = FromLuaMulti::from_lua_args(args, i, to, lua)?;
Ok(($($name,)* $last,))
}
#[allow(unused_mut, non_snake_case)]
#[inline]
unsafe fn from_stack_multi(mut nvals: c_int, lua: &'lua Lua) -> Result<Self> {
$(
let $name = if nvals > 0 {
nvals -= 1;
FromLua::from_stack(-(nvals + 1), lua)
} else {
FromLua::from_lua(Nil, lua)
}?;
)*
let $last = FromLuaMulti::from_stack_multi(nvals, lua)?;
Ok(($($name,)* $last,))
}
#[allow(unused_mut, non_snake_case)]
#[inline]
unsafe fn from_stack_args(mut nargs: c_int, mut i: usize, to: Option<&str>, lua: &'lua Lua) -> Result<Self> {
$(
let $name = if nargs > 0 {
nargs -= 1;
FromLua::from_stack_arg(-(nargs + 1), i, to, lua)
} else {
FromLua::from_lua_arg(Nil, i, to, lua)
}?;
i += 1;
)*
let $last = FromLuaMulti::from_stack_args(nargs, i, to, lua)?;
Ok(($($name,)* $last,))
}
}
+1 -1
View File
@@ -40,5 +40,5 @@ pub use crate::{
#[doc(no_inline)]
pub use crate::{
OwnedAnyUserData as LuaOwnedAnyUserData, OwnedFunction as LuaOwnedFunction,
OwnedString as LuaOwnedString, OwnedTable as LuaOwnedTable,
OwnedString as LuaOwnedString, OwnedTable as LuaOwnedTable, OwnedThread as LuaOwnedThread,
};
+253 -183
View File
@@ -2,6 +2,7 @@ use std::any::Any;
use std::cell::{Cell, RefCell};
use std::marker::PhantomData;
use std::mem;
use std::os::raw::c_int;
#[cfg(feature = "serialize")]
use serde::Serialize;
@@ -9,16 +10,16 @@ use serde::Serialize;
use crate::error::{Error, Result};
use crate::function::Function;
use crate::lua::Lua;
use crate::types::{Callback, CallbackUpvalue, LuaRef, MaybeSend};
use crate::types::{Callback, CallbackUpvalue, LuaRef, MaybeSend, SubtypeId};
use crate::userdata::{
AnyUserData, MetaMethod, UserData, UserDataCell, UserDataFields, UserDataMethods,
};
use crate::userdata_impl::UserDataRegistry;
use crate::util::{
self, assert_stack, check_stack, get_userdata, init_userdata_metatable, push_table,
rawset_field, take_userdata, StackGuard,
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, MultiValue, Value};
use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Value};
#[cfg(feature = "lua54")]
use crate::userdata::USER_VALUE_MAXSLOT;
@@ -75,8 +76,9 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
// to make sure callbacks can't capture handles with lifetime outside the scope, inside the
// scope, and owned inside the callback itself.
unsafe {
self.create_callback(Box::new(move |lua, args| {
func(lua, A::from_lua_multi(args, lua)?)?.into_lua_multi(lua)
self.create_callback(Box::new(move |lua, nargs| {
let args = A::from_stack_args(nargs, 1, None, lua)?;
func(lua, args)?.push_into_stack_multi(lua)
}))
}
}
@@ -186,6 +188,23 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
}
}
/// Creates a Lua userdata object from a custom Rust type.
///
/// This is a version of [`Lua::create_any_userdata`] that creates a userdata which expires on
/// scope drop and does not require that the userdata type be Send (but still requires that the
/// UserData be 'static). See [`Lua::scope`] for more details.
#[inline]
pub fn create_any_userdata<T>(&self, data: T) -> Result<AnyUserData<'lua>>
where
T: 'static,
{
unsafe {
let ud = self.lua.make_any_userdata(UserDataCell::new(data))?;
self.seal_userdata::<T>(&ud)?;
Ok(ud)
}
}
/// Creates a Lua userdata object from a reference to custom Rust type.
///
/// This is a version of [`Lua::create_any_userdata`] that creates a userdata which expires on
@@ -293,9 +312,10 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
// 'lua. This is safe though, because `UserData::add_methods` does not get to pick the 'lua
// lifetime, so none of the static methods UserData types can add can possibly capture
// parameters.
fn wrap_method<'scope, 'lua, 'callback: 'scope, T: 'scope>(
unsafe fn wrap_method<'scope, 'lua, 'callback: 'scope, T: 'scope>(
scope: &Scope<'lua, 'scope>,
ud_ptr: *const UserDataCell<T>,
name: &str,
method: NonStaticMethod<'callback, T>,
) -> Result<Function<'lua>> {
// On methods that actually receive the userdata, we fake a type check on the passed in
@@ -305,61 +325,56 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
// with a type mismatch, but here without this check would proceed as though you had
// called the method on the original value (since we otherwise completely ignore the
// first argument).
let check_ud_type = move |lua: &Lua, value| -> Result<&UserDataCell<T>> {
if let Some(Value::UserData(ud)) = value {
let state = lua.state();
unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 2)?;
lua.push_userdata_ref(&ud.0)?;
if get_userdata(state, -1) as *const _ == ud_ptr {
return Ok(&*ud_ptr);
}
}
};
Err(Error::UserDataTypeMismatch)
let func_name = format!("{}.{name}", short_type_name::<T>());
let check_self_type = move |lua: &Lua, nargs: c_int| -> Result<&UserDataCell<T>> {
let state = lua.state();
if nargs > 0 && ffi::lua_touserdata(state, -nargs) as *const _ == ud_ptr {
return Ok(&*ud_ptr);
}
Err(Error::bad_self_argument(
&func_name,
Error::UserDataTypeMismatch,
))
};
match method {
NonStaticMethod::Method(method) => {
let f = Box::new(move |lua, mut args: MultiValue<'callback>| {
let data = check_ud_type(lua, args.pop_front())?;
let f = Box::new(move |lua, nargs| {
let data = check_self_type(lua, nargs)?;
let data = data.try_borrow()?;
method(lua, &*data, args)
method(lua, &*data, nargs - 1)
});
unsafe { scope.create_callback(f) }
scope.create_callback(f)
}
NonStaticMethod::MethodMut(method) => {
let method = RefCell::new(method);
let f = Box::new(move |lua, mut args: MultiValue<'callback>| {
let data = check_ud_type(lua, args.pop_front())?;
let f = Box::new(move |lua, nargs| {
let data = check_self_type(lua, nargs)?;
let mut method = method
.try_borrow_mut()
.map_err(|_| Error::RecursiveMutCallback)?;
let mut data = data.try_borrow_mut()?;
(*method)(lua, &mut *data, args)
(*method)(lua, &mut *data, nargs - 1)
});
unsafe { scope.create_callback(f) }
scope.create_callback(f)
}
NonStaticMethod::Function(function) => unsafe { scope.create_callback(function) },
NonStaticMethod::Function(function) => scope.create_callback(function),
NonStaticMethod::FunctionMut(function) => {
let function = RefCell::new(function);
let f = Box::new(move |lua, args| {
(*function
let f = Box::new(move |lua, nargs| {
let mut func = function
.try_borrow_mut()
.map_err(|_| Error::RecursiveMutCallback)?)(
lua, args
)
.map_err(|_| Error::RecursiveMutCallback)?;
func(lua, nargs)
});
unsafe { scope.create_callback(f) }
scope.create_callback(f)
}
}
}
let mut ud_fields = NonStaticUserDataFields::default();
let mut ud_methods = NonStaticUserDataMethods::default();
T::add_fields(&mut ud_fields);
T::add_methods(&mut ud_methods);
let mut registry = NonStaticUserDataRegistry::new();
T::add_fields(&mut registry);
T::add_methods(&mut registry);
let lua = self.lua;
let state = lua.state();
@@ -387,48 +402,83 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
};
// Prepare metatable, add meta methods first and then meta fields
let meta_methods_nrec = ud_methods.meta_methods.len() + ud_fields.meta_fields.len() + 1;
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 ud_methods.meta_methods {
lua.push_value(Value::Function(wrap_method(self, ud_ptr, m)?))?;
for (k, m) in registry.meta_methods {
lua.push_value(Value::Function(wrap_method(self, ud_ptr, &k, m)?))?;
rawset_field(state, -2, MetaMethod::validate(&k)?)?;
}
for (k, f) in ud_fields.meta_fields {
lua.push_value(f(lua, MultiValue::new())?.pop_front().unwrap())?;
let mut has_name = false;
for (k, f) in registry.meta_fields {
has_name = has_name || k == MetaMethod::Type;
mlua_assert!(f(lua, 0)? == 1, "field function must return one value");
rawset_field(state, -2, MetaMethod::validate(&k)?)?;
}
// Set `__name/__type` if not provided
if !has_name {
let type_name = short_type_name::<T>();
push_string(state, type_name.as_bytes(), !lua.unlikely_memory_error())?;
rawset_field(state, -2, MetaMethod::Type.name())?;
}
let metatable_index = ffi::lua_absindex(state, -1);
let fields_nrec = registry.fields.len();
if fields_nrec > 0 {
// If __index is a table then update it inplace
let index_type = ffi::lua_getfield(state, metatable_index, cstr!("__index"));
match index_type {
ffi::LUA_TNIL | ffi::LUA_TTABLE => {
if index_type == ffi::LUA_TNIL {
// Create a new table
ffi::lua_pop(state, 1);
push_table(state, 0, fields_nrec, true)?;
}
for (k, f) in registry.fields {
#[rustfmt::skip]
let NonStaticMethod::Function(f) = f else { unreachable!() };
mlua_assert!(f(lua, 0)? == 1, "field function must return one value");
rawset_field(state, -2, &k)?;
}
rawset_field(state, metatable_index, "__index")?;
}
_ => {
// Propagate fields to the field getters
for (k, f) in registry.fields {
registry.field_getters.push((k, f))
}
}
}
}
let mut field_getters_index = None;
let field_getters_nrec = ud_fields.field_getters.len();
let field_getters_nrec = registry.field_getters.len();
if field_getters_nrec > 0 {
push_table(state, 0, field_getters_nrec, true)?;
for (k, m) in ud_fields.field_getters {
lua.push_value(Value::Function(wrap_method(self, ud_ptr, m)?))?;
for (k, m) in registry.field_getters {
lua.push_value(Value::Function(wrap_method(self, ud_ptr, &k, m)?))?;
rawset_field(state, -2, &k)?;
}
field_getters_index = Some(ffi::lua_absindex(state, -1));
}
let mut field_setters_index = None;
let field_setters_nrec = ud_fields.field_setters.len();
let field_setters_nrec = registry.field_setters.len();
if field_setters_nrec > 0 {
push_table(state, 0, field_setters_nrec, true)?;
for (k, m) in ud_fields.field_setters {
lua.push_value(Value::Function(wrap_method(self, ud_ptr, m)?))?;
for (k, m) in registry.field_setters {
lua.push_value(Value::Function(wrap_method(self, ud_ptr, &k, m)?))?;
rawset_field(state, -2, &k)?;
}
field_setters_index = Some(ffi::lua_absindex(state, -1));
}
let mut methods_index = None;
let methods_nrec = ud_methods.methods.len();
let methods_nrec = registry.methods.len();
if methods_nrec > 0 {
// Create table used for methods lookup
push_table(state, 0, methods_nrec, true)?;
for (k, m) in ud_methods.methods {
lua.push_value(Value::Function(wrap_method(self, ud_ptr, m)?))?;
for (k, m) in registry.methods {
lua.push_value(Value::Function(wrap_method(self, ud_ptr, &k, m)?))?;
rawset_field(state, -2, &k)?;
}
methods_index = Some(ffi::lua_absindex(state, -1));
@@ -461,7 +511,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
#[cfg(not(feature = "luau"))]
std::ptr::write(ud_ptr as _, UserDataCell::new(data));
ffi::lua_setmetatable(state, -2);
let ud = AnyUserData(lua.pop_ref());
let ud = AnyUserData(lua.pop_ref(), SubtypeId::None);
lua.register_raw_userdata_metatable(mt_ptr, None);
#[cfg(any(feature = "lua51", feature = "luajit"))]
@@ -572,35 +622,144 @@ impl<'lua, 'scope> Drop for Scope<'lua, 'scope> {
#[allow(clippy::type_complexity)]
enum NonStaticMethod<'lua, T> {
Method(Box<dyn Fn(&'lua Lua, &T, MultiValue<'lua>) -> Result<MultiValue<'lua>>>),
MethodMut(Box<dyn FnMut(&'lua Lua, &mut T, MultiValue<'lua>) -> Result<MultiValue<'lua>>>),
Function(Box<dyn Fn(&'lua Lua, MultiValue<'lua>) -> Result<MultiValue<'lua>>>),
FunctionMut(Box<dyn FnMut(&'lua Lua, MultiValue<'lua>) -> Result<MultiValue<'lua>>>),
Method(Box<dyn Fn(&'lua Lua, &T, c_int) -> Result<c_int>>),
MethodMut(Box<dyn FnMut(&'lua Lua, &mut T, c_int) -> Result<c_int>>),
Function(Box<dyn Fn(&'lua Lua, c_int) -> Result<c_int>>),
FunctionMut(Box<dyn FnMut(&'lua Lua, c_int) -> Result<c_int>>),
}
struct NonStaticUserDataMethods<'lua, T: UserData> {
struct NonStaticUserDataRegistry<'lua, T> {
// Fields
fields: Vec<(String, NonStaticMethod<'lua, T>)>,
field_getters: Vec<(String, NonStaticMethod<'lua, T>)>,
field_setters: Vec<(String, NonStaticMethod<'lua, T>)>,
meta_fields: Vec<(String, Callback<'lua, 'static>)>,
// Methods
methods: Vec<(String, NonStaticMethod<'lua, T>)>,
meta_methods: Vec<(String, NonStaticMethod<'lua, T>)>,
}
impl<'lua, T: UserData> Default for NonStaticUserDataMethods<'lua, T> {
fn default() -> NonStaticUserDataMethods<'lua, T> {
NonStaticUserDataMethods {
impl<'lua, T> NonStaticUserDataRegistry<'lua, T> {
const fn new() -> NonStaticUserDataRegistry<'lua, T> {
NonStaticUserDataRegistry {
fields: Vec::new(),
field_getters: Vec::new(),
field_setters: Vec::new(),
meta_fields: Vec::new(),
methods: Vec::new(),
meta_methods: Vec::new(),
}
}
}
impl<'lua, T: UserData> UserDataMethods<'lua, T> for NonStaticUserDataMethods<'lua, T> {
impl<'lua, T> UserDataFields<'lua, T> for NonStaticUserDataRegistry<'lua, T> {
fn add_field<V>(&mut self, name: impl AsRef<str>, value: V)
where
V: IntoLua<'lua> + Clone + 'static,
{
let name = name.as_ref().to_string();
self.fields.push((
name,
NonStaticMethod::Function(Box::new(move |lua, _| unsafe {
value.clone().push_into_stack_multi(lua)
})),
));
}
fn add_field_method_get<M, R>(&mut self, name: impl AsRef<str>, method: M)
where
M: Fn(&'lua Lua, &T) -> Result<R> + MaybeSend + 'static,
R: IntoLua<'lua>,
{
let method = NonStaticMethod::Method(Box::new(move |lua, ud, _| unsafe {
method(lua, ud)?.push_into_stack_multi(lua)
}));
self.field_getters.push((name.as_ref().into(), method));
}
fn add_field_method_set<M, A>(&mut self, name: impl AsRef<str>, mut method: M)
where
M: FnMut(&'lua Lua, &mut T, A) -> Result<()> + MaybeSend + 'static,
A: FromLua<'lua>,
{
let func_name = format!("{}.{}", short_type_name::<T>(), name.as_ref());
let method = NonStaticMethod::MethodMut(Box::new(move |lua, ud, nargs| unsafe {
let val = A::from_stack_args(nargs, 2, Some(&func_name), lua)?;
method(lua, ud, val)?.push_into_stack_multi(lua)
}));
self.field_setters.push((name.as_ref().into(), method));
}
fn add_field_function_get<F, R>(&mut self, name: impl AsRef<str>, function: F)
where
F: Fn(&'lua Lua, AnyUserData<'lua>) -> Result<R> + MaybeSend + 'static,
R: IntoLua<'lua>,
{
let func_name = format!("{}.{}", short_type_name::<T>(), name.as_ref());
let func = NonStaticMethod::Function(Box::new(move |lua, nargs| unsafe {
let ud = AnyUserData::from_stack_args(nargs, 1, Some(&func_name), lua)?;
function(lua, ud)?.push_into_stack_multi(lua)
}));
self.field_getters.push((name.as_ref().into(), func));
}
fn add_field_function_set<F, A>(&mut self, name: impl AsRef<str>, mut function: F)
where
F: FnMut(&'lua Lua, AnyUserData<'lua>, A) -> Result<()> + MaybeSend + 'static,
A: FromLua<'lua>,
{
let func_name = format!("{}.{}", short_type_name::<T>(), name.as_ref());
let func = NonStaticMethod::FunctionMut(Box::new(move |lua, nargs| unsafe {
let (ud, val) = <_>::from_stack_args(nargs, 1, Some(&func_name), lua)?;
function(lua, ud, val)?.push_into_stack_multi(lua)
}));
self.field_setters.push((name.as_ref().into(), func));
}
fn add_meta_field<V>(&mut self, name: impl AsRef<str>, value: V)
where
V: IntoLua<'lua> + Clone + 'static,
{
let name = name.as_ref().to_string();
let name2 = name.clone();
self.meta_fields.push((
name,
Box::new(move |lua, _| unsafe {
UserDataRegistry::<()>::check_meta_field(lua, &name2, value.clone())?
.push_into_stack_multi(lua)
}),
));
}
fn add_meta_field_with<F, R>(&mut self, name: impl AsRef<str>, f: F)
where
F: Fn(&'lua Lua) -> Result<R> + MaybeSend + 'static,
R: IntoLua<'lua>,
{
let name = name.as_ref().to_string();
let name2 = name.clone();
self.meta_fields.push((
name,
Box::new(move |lua, _| unsafe {
UserDataRegistry::<()>::check_meta_field(lua, &name2, f(lua)?)?
.push_into_stack_multi(lua)
}),
));
}
}
impl<'lua, T> UserDataMethods<'lua, T> for NonStaticUserDataRegistry<'lua, T> {
fn add_method<M, A, R>(&mut self, name: impl AsRef<str>, method: M)
where
M: Fn(&'lua Lua, &T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
{
let method = NonStaticMethod::Method(Box::new(move |lua, ud, args| {
method(lua, ud, A::from_lua_multi(args, lua)?)?.into_lua_multi(lua)
let func_name = format!("{}.{}", short_type_name::<T>(), name.as_ref());
let method = NonStaticMethod::Method(Box::new(move |lua, ud, nargs| unsafe {
let args = A::from_stack_args(nargs, 2, Some(&func_name), lua)?;
method(lua, ud, args)?.push_into_stack_multi(lua)
}));
self.methods.push((name.as_ref().into(), method));
}
@@ -611,8 +770,10 @@ impl<'lua, T: UserData> UserDataMethods<'lua, T> for NonStaticUserDataMethods<'l
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
{
let method = NonStaticMethod::MethodMut(Box::new(move |lua, ud, args| {
method(lua, ud, A::from_lua_multi(args, lua)?)?.into_lua_multi(lua)
let func_name = format!("{}.{}", short_type_name::<T>(), name.as_ref());
let method = NonStaticMethod::MethodMut(Box::new(move |lua, ud, nargs| unsafe {
let args = A::from_stack_args(nargs, 2, Some(&func_name), lua)?;
method(lua, ud, args)?.push_into_stack_multi(lua)
}));
self.methods.push((name.as_ref().into(), method));
}
@@ -653,8 +814,10 @@ impl<'lua, T: UserData> UserDataMethods<'lua, T> for NonStaticUserDataMethods<'l
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
{
let func = NonStaticMethod::Function(Box::new(move |lua, args| {
function(lua, A::from_lua_multi(args, lua)?)?.into_lua_multi(lua)
let func_name = format!("{}.{}", short_type_name::<T>(), name.as_ref());
let func = NonStaticMethod::Function(Box::new(move |lua, nargs| unsafe {
let args = A::from_stack_args(nargs, 1, Some(&func_name), lua)?;
function(lua, args)?.push_into_stack_multi(lua)
}));
self.methods.push((name.as_ref().into(), func));
}
@@ -665,8 +828,10 @@ impl<'lua, T: UserData> UserDataMethods<'lua, T> for NonStaticUserDataMethods<'l
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
{
let func = NonStaticMethod::FunctionMut(Box::new(move |lua, args| {
function(lua, A::from_lua_multi(args, lua)?)?.into_lua_multi(lua)
let func_name = format!("{}.{}", short_type_name::<T>(), name.as_ref());
let func = NonStaticMethod::FunctionMut(Box::new(move |lua, nargs| unsafe {
let args = A::from_stack_args(nargs, 1, Some(&func_name), lua)?;
function(lua, args)?.push_into_stack_multi(lua)
}));
self.methods.push((name.as_ref().into(), func));
}
@@ -690,8 +855,10 @@ impl<'lua, T: UserData> UserDataMethods<'lua, T> for NonStaticUserDataMethods<'l
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
{
let method = NonStaticMethod::Method(Box::new(move |lua, ud, args| {
method(lua, ud, A::from_lua_multi(args, lua)?)?.into_lua_multi(lua)
let func_name = format!("{}.{}", short_type_name::<T>(), name.as_ref());
let method = NonStaticMethod::Method(Box::new(move |lua, ud, nargs| unsafe {
let args = A::from_stack_args(nargs, 2, Some(&func_name), lua)?;
method(lua, ud, args)?.push_into_stack_multi(lua)
}));
self.meta_methods.push((name.as_ref().into(), method));
}
@@ -702,8 +869,10 @@ impl<'lua, T: UserData> UserDataMethods<'lua, T> for NonStaticUserDataMethods<'l
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
{
let method = NonStaticMethod::MethodMut(Box::new(move |lua, ud, args| {
method(lua, ud, A::from_lua_multi(args, lua)?)?.into_lua_multi(lua)
let func_name = format!("{}.{}", short_type_name::<T>(), name.as_ref());
let method = NonStaticMethod::MethodMut(Box::new(move |lua, ud, nargs| unsafe {
let args = A::from_stack_args(nargs, 2, Some(&func_name), lua)?;
method(lua, ud, args)?.push_into_stack_multi(lua)
}));
self.meta_methods.push((name.as_ref().into(), method));
}
@@ -744,8 +913,10 @@ impl<'lua, T: UserData> UserDataMethods<'lua, T> for NonStaticUserDataMethods<'l
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
{
let func = NonStaticMethod::Function(Box::new(move |lua, args| {
function(lua, A::from_lua_multi(args, lua)?)?.into_lua_multi(lua)
let func_name = format!("{}.{}", short_type_name::<T>(), name.as_ref());
let func = NonStaticMethod::Function(Box::new(move |lua, nargs| unsafe {
let args = A::from_stack_args(nargs, 1, Some(&func_name), lua)?;
function(lua, args)?.push_into_stack_multi(lua)
}));
self.meta_methods.push((name.as_ref().into(), func));
}
@@ -756,8 +927,10 @@ impl<'lua, T: UserData> UserDataMethods<'lua, T> for NonStaticUserDataMethods<'l
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
{
let func = NonStaticMethod::FunctionMut(Box::new(move |lua, args| {
function(lua, A::from_lua_multi(args, lua)?)?.into_lua_multi(lua)
let func_name = format!("{}.{}", short_type_name::<T>(), name.as_ref());
let func = NonStaticMethod::FunctionMut(Box::new(move |lua, nargs| unsafe {
let args = A::from_stack_args(nargs, 1, Some(&func_name), lua)?;
function(lua, args)?.push_into_stack_multi(lua)
}));
self.meta_methods.push((name.as_ref().into(), func));
}
@@ -775,106 +948,3 @@ impl<'lua, T: UserData> UserDataMethods<'lua, T> for NonStaticUserDataMethods<'l
panic!("asynchronous meta functions are not supported for non-static userdata")
}
}
struct NonStaticUserDataFields<'lua, T: UserData> {
fields: Vec<(String, Callback<'lua, 'static>)>,
field_getters: Vec<(String, NonStaticMethod<'lua, T>)>,
field_setters: Vec<(String, NonStaticMethod<'lua, T>)>,
meta_fields: Vec<(String, Callback<'lua, 'static>)>,
}
impl<'lua, T: UserData> Default for NonStaticUserDataFields<'lua, T> {
fn default() -> NonStaticUserDataFields<'lua, T> {
NonStaticUserDataFields {
fields: Vec::new(),
field_getters: Vec::new(),
field_setters: Vec::new(),
meta_fields: Vec::new(),
}
}
}
impl<'lua, T: UserData> UserDataFields<'lua, T> for NonStaticUserDataFields<'lua, T> {
fn add_field<V>(&mut self, name: impl AsRef<str>, value: V)
where
V: IntoLua<'lua> + Clone + 'static,
{
let name = name.as_ref().to_string();
self.fields.push((
name,
Box::new(move |lua, _| value.clone().into_lua_multi(lua)),
));
}
fn add_field_method_get<M, R>(&mut self, name: impl AsRef<str>, method: M)
where
M: Fn(&'lua Lua, &T) -> Result<R> + MaybeSend + 'static,
R: IntoLua<'lua>,
{
let method = NonStaticMethod::Method(Box::new(move |lua, ud, _| {
method(lua, ud)?.into_lua_multi(lua)
}));
self.field_getters.push((name.as_ref().into(), method));
}
fn add_field_method_set<M, A>(&mut self, name: impl AsRef<str>, mut method: M)
where
M: FnMut(&'lua Lua, &mut T, A) -> Result<()> + MaybeSend + 'static,
A: FromLua<'lua>,
{
let method = NonStaticMethod::MethodMut(Box::new(move |lua, ud, args| {
method(lua, ud, A::from_lua_multi(args, lua)?)?.into_lua_multi(lua)
}));
self.field_setters.push((name.as_ref().into(), method));
}
fn add_field_function_get<F, R>(&mut self, name: impl AsRef<str>, function: F)
where
F: Fn(&'lua Lua, AnyUserData<'lua>) -> Result<R> + MaybeSend + 'static,
R: IntoLua<'lua>,
{
let func = NonStaticMethod::Function(Box::new(move |lua, args| {
function(lua, AnyUserData::from_lua_multi(args, lua)?)?.into_lua_multi(lua)
}));
self.field_getters.push((name.as_ref().into(), func));
}
fn add_field_function_set<F, A>(&mut self, name: impl AsRef<str>, mut function: F)
where
F: FnMut(&'lua Lua, AnyUserData<'lua>, A) -> Result<()> + MaybeSend + 'static,
A: FromLua<'lua>,
{
let func = NonStaticMethod::FunctionMut(Box::new(move |lua, args| {
let (ud, val) = <_>::from_lua_multi(args, lua)?;
function(lua, ud, val)?.into_lua_multi(lua)
}));
self.field_setters.push((name.as_ref().into(), func));
}
fn add_meta_field<V>(&mut self, name: impl AsRef<str>, value: V)
where
V: IntoLua<'lua> + Clone + 'static,
{
let name = name.as_ref().to_string();
let name2 = name.clone();
self.meta_fields.push((
name,
Box::new(move |lua, _| {
UserDataRegistry::<()>::check_meta_field(lua, &name2, value.clone())
}),
));
}
fn add_meta_field_with<F, R>(&mut self, name: impl AsRef<str>, f: F)
where
F: Fn(&'lua Lua) -> Result<R> + MaybeSend + 'static,
R: IntoLua<'lua>,
{
let name = name.as_ref().to_string();
let name2 = name.clone();
self.meta_fields.push((
name,
Box::new(move |lua, _| UserDataRegistry::<()>::check_meta_field(lua, &name2, f(lua)?)),
));
}
}
+79 -17
View File
@@ -2,6 +2,7 @@ use std::cell::RefCell;
use std::convert::TryInto;
use std::os::raw::c_void;
use std::rc::Rc;
use std::result::Result as StdResult;
use std::string::String as StdString;
use rustc_hash::FxHashSet;
@@ -24,14 +25,14 @@ pub struct Deserializer<'lua> {
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub struct Options {
/// If true, an attempt to serialize types such as [`Thread`], [`UserData`], [`LightUserData`]
/// If true, an attempt to serialize types such as [`Function`], [`Thread`], [`LightUserData`]
/// and [`Error`] will cause an error.
/// Otherwise these types skipped when iterating or serialized as unit type.
///
/// Default: **true**
///
/// [`Function`]: crate::Function
/// [`Thread`]: crate::Thread
/// [`UserData`]: crate::UserData
/// [`LightUserData`]: crate::LightUserData
/// [`Error`]: crate::Error
pub deny_unsupported_types: bool,
@@ -42,6 +43,11 @@ pub struct Options {
///
/// Default: **true**
pub deny_recursive_tables: bool,
/// If true, keys in tables will be iterated in sorted order.
///
/// Default: **false**
pub sort_keys: bool,
}
impl Default for Options {
@@ -56,6 +62,7 @@ impl Options {
Options {
deny_unsupported_types: true,
deny_recursive_tables: true,
sort_keys: false,
}
}
@@ -76,6 +83,15 @@ impl Options {
self.deny_recursive_tables = enabled;
self
}
/// Sets [`sort_keys`] option.
///
/// [`sort_keys`]: #structfield.sort_keys
#[must_use]
pub const fn sort_keys(mut self, enabled: bool) -> Self {
self.sort_keys = enabled;
self
}
}
impl<'lua> Deserializer<'lua> {
@@ -141,10 +157,8 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
| Value::LightUserData(_)
| Value::Error(_) => {
if self.options.deny_unsupported_types {
Err(de::Error::custom(format!(
"unsupported value type `{}`",
self.value.type_name()
)))
let msg = format!("unsupported value type `{}`", self.value.type_name());
Err(de::Error::custom(msg))
} else {
visitor.visit_unit()
}
@@ -195,7 +209,9 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
&"map with a single key",
));
}
if check_value_if_skip(&value, self.options, &self.visited)? {
let skip = check_value_for_skip(&value, self.options, &self.visited)
.map_err(|err| Error::DeserializeError(err.to_string()))?;
if skip {
return Err(de::Error::custom("bad enum value"));
}
@@ -235,7 +251,7 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
Value::Table(t) => {
let _guard = RecursionGuard::new(&t, &self.visited);
let len = t.raw_len() as usize;
let len = t.raw_len();
let mut deserializer = SeqDeserializer {
seq: t.sequence_values(),
options: self.options,
@@ -292,7 +308,7 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
let _guard = RecursionGuard::new(&t, &self.visited);
let mut deserializer = MapDeserializer {
pairs: t.pairs(),
pairs: MapPairs::new(t, self.options.sort_keys)?,
value: None,
options: self.options,
visited: self.visited,
@@ -390,7 +406,9 @@ impl<'lua, 'de> de::SeqAccess<'de> for SeqDeserializer<'lua> {
match self.seq.next() {
Some(value) => {
let value = value?;
if check_value_if_skip(&value, self.options, &self.visited)? {
let skip = check_value_for_skip(&value, self.options, &self.visited)
.map_err(|err| Error::DeserializeError(err.to_string()))?;
if skip {
continue;
}
let visited = Rc::clone(&self.visited);
@@ -443,8 +461,50 @@ impl<'de> de::SeqAccess<'de> for VecDeserializer {
}
}
pub(crate) enum MapPairs<'lua> {
Iter(TablePairs<'lua, Value<'lua>, Value<'lua>>),
Vec(Vec<(Value<'lua>, Value<'lua>)>),
}
impl<'lua> MapPairs<'lua> {
pub(crate) fn new(t: Table<'lua>, sort_keys: bool) -> Result<Self> {
if sort_keys {
let mut pairs = t.pairs::<Value, Value>().collect::<Result<Vec<_>>>()?;
pairs.sort_by(|(a, _), (b, _)| b.cmp(a)); // reverse order as we pop values from the end
Ok(MapPairs::Vec(pairs))
} else {
Ok(MapPairs::Iter(t.pairs::<Value, Value>()))
}
}
pub(crate) fn count(self) -> usize {
match self {
MapPairs::Iter(iter) => iter.count(),
MapPairs::Vec(vec) => vec.len(),
}
}
pub(crate) fn size_hint(&self) -> (usize, Option<usize>) {
match self {
MapPairs::Iter(iter) => iter.size_hint(),
MapPairs::Vec(vec) => (vec.len(), Some(vec.len())),
}
}
}
impl<'lua> Iterator for MapPairs<'lua> {
type Item = Result<(Value<'lua>, Value<'lua>)>;
fn next(&mut self) -> Option<Self::Item> {
match self {
MapPairs::Iter(iter) => iter.next(),
MapPairs::Vec(vec) => vec.pop().map(Ok),
}
}
}
struct MapDeserializer<'lua> {
pairs: TablePairs<'lua, Value<'lua>, Value<'lua>>,
pairs: MapPairs<'lua>,
value: Option<Value<'lua>>,
options: Options,
visited: Rc<RefCell<FxHashSet<*const c_void>>>,
@@ -462,9 +522,11 @@ impl<'lua, 'de> de::MapAccess<'de> for MapDeserializer<'lua> {
match self.pairs.next() {
Some(item) => {
let (key, value) = item?;
if check_value_if_skip(&key, self.options, &self.visited)?
|| check_value_if_skip(&value, self.options, &self.visited)?
{
let skip_key = check_value_for_skip(&key, self.options, &self.visited)
.map_err(|err| Error::DeserializeError(err.to_string()))?;
let skip_value = check_value_for_skip(&value, self.options, &self.visited)
.map_err(|err| Error::DeserializeError(err.to_string()))?;
if skip_key || skip_value {
continue;
}
self.processed += 1;
@@ -615,17 +677,17 @@ impl Drop for RecursionGuard {
}
// Checks `options` and decides should we emit an error or skip next element
fn check_value_if_skip(
pub(crate) fn check_value_for_skip(
value: &Value,
options: Options,
visited: &RefCell<FxHashSet<*const c_void>>,
) -> Result<bool> {
) -> StdResult<bool, &'static str> {
match value {
Value::Table(table) => {
let ptr = table.to_pointer();
if visited.borrow().contains(&ptr) {
if options.deny_recursive_tables {
return Err(de::Error::custom("recursive table detected"));
return Err("recursive table detected");
}
return Ok(true); // skip
}
+15 -2
View File
@@ -16,32 +16,44 @@ impl StdLib {
feature = "luau"
))]
pub const COROUTINE: StdLib = StdLib(1);
/// [`table`](https://www.lua.org/manual/5.4/manual.html#6.6) library
pub const TABLE: StdLib = StdLib(1 << 1);
/// [`io`](https://www.lua.org/manual/5.4/manual.html#6.8) library
#[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub const IO: StdLib = StdLib(1 << 2);
/// [`os`](https://www.lua.org/manual/5.4/manual.html#6.9) library
pub const OS: StdLib = StdLib(1 << 3);
/// [`string`](https://www.lua.org/manual/5.4/manual.html#6.4) library
pub const STRING: StdLib = StdLib(1 << 4);
/// [`utf8`](https://www.lua.org/manual/5.4/manual.html#6.5) library
///
/// Requires `feature = "lua54/lua53/luau"`
#[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))]
pub const UTF8: StdLib = StdLib(1 << 5);
/// [`bit`](https://www.lua.org/manual/5.2/manual.html#6.7) library
///
/// Requires `feature = "lua52/luajit/luau"`
#[cfg(any(feature = "lua52", feature = "luajit", feature = "luau", doc))]
pub const BIT: StdLib = StdLib(1 << 6);
/// [`math`](https://www.lua.org/manual/5.4/manual.html#6.7) library
pub const MATH: StdLib = StdLib(1 << 7);
/// [`package`](https://www.lua.org/manual/5.4/manual.html#6.3) library
#[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub const PACKAGE: StdLib = StdLib(1 << 8);
/// [`buffer`](https://luau-lang.org/library#buffer-library) library
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub const BUFFER: StdLib = StdLib(1 << 9);
/// [`jit`](http://luajit.org/ext_jit.html) library
///
/// Requires `feature = "luajit"`
@@ -55,6 +67,7 @@ impl StdLib {
#[cfg(any(feature = "luajit", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luajit")))]
pub const FFI: StdLib = StdLib(1 << 30);
/// (**unsafe**) [`debug`](https://www.lua.org/manual/5.4/manual.html#6.10) library
pub const DEBUG: StdLib = StdLib(1 << 31);
+184 -120
View File
@@ -6,8 +6,8 @@ use std::os::raw::c_void;
#[cfg(feature = "serialize")]
use {
rustc_hash::FxHashSet,
serde::ser::{self, Serialize, SerializeMap, SerializeSeq, Serializer},
std::{cell::RefCell, result::Result as StdResult},
serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer},
std::{cell::RefCell, rc::Rc, result::Result as StdResult},
};
use crate::error::{Error, Result};
@@ -86,17 +86,14 @@ impl<'lua> Table<'lua> {
}
let lua = self.0.lua;
let key = key.into_lua(lua)?;
let value = value.into_lua(lua)?;
let state = lua.state();
unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 5)?;
lua.push_ref(&self.0);
lua.push_value(key)?;
lua.push_value(value)?;
key.push_into_stack(lua)?;
value.push_into_stack(lua)?;
protect_lua!(state, 3, 0, fn(state) ffi::lua_settable(state, -3))
}
}
@@ -133,19 +130,16 @@ impl<'lua> Table<'lua> {
let lua = self.0.lua;
let state = lua.state();
let key = key.into_lua(lua)?;
let value = unsafe {
unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 4)?;
lua.push_ref(&self.0);
lua.push_value(key)?;
key.push_into_stack(lua)?;
protect_lua!(state, 2, 1, fn(state) ffi::lua_gettable(state, -2))?;
lua.pop_value()
};
V::from_lua(value, lua)
V::from_stack(-1, lua)
}
}
/// Checks whether the table contains a non-nil value for `key`.
@@ -166,13 +160,12 @@ impl<'lua> Table<'lua> {
let lua = self.0.lua;
let state = lua.state();
let value = value.into_lua(lua)?;
unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 4)?;
lua.push_ref(&self.0);
lua.push_value(value)?;
value.push_into_stack(lua)?;
protect_lua!(state, 2, 0, fn(state) {
let len = ffi::luaL_len(state, -2) as Integer;
ffi::lua_seti(state, -2, len + 1);
@@ -192,7 +185,7 @@ impl<'lua> Table<'lua> {
let lua = self.0.lua;
let state = lua.state();
let value = unsafe {
unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 4)?;
@@ -203,9 +196,8 @@ impl<'lua> Table<'lua> {
ffi::lua_pushnil(state);
ffi::lua_seti(state, -3, len);
})?;
lua.pop_value()
};
V::from_lua(value, lua)
V::from_stack(-1, lua)
}
}
/// Compares two tables for equality.
@@ -271,16 +263,13 @@ impl<'lua> Table<'lua> {
let lua = self.0.lua;
let state = lua.state();
let key = key.into_lua(lua)?;
let value = value.into_lua(lua)?;
unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 5)?;
lua.push_ref(&self.0);
lua.push_value(key)?;
lua.push_value(value)?;
key.push_into_stack(lua)?;
value.push_into_stack(lua)?;
if lua.unlikely_memory_error() {
ffi::lua_rawset(state, -3);
@@ -296,39 +285,34 @@ impl<'lua> Table<'lua> {
pub fn raw_get<K: IntoLua<'lua>, V: FromLua<'lua>>(&self, key: K) -> Result<V> {
let lua = self.0.lua;
let state = lua.state();
let key = key.into_lua(lua)?;
let value = unsafe {
unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 3)?;
lua.push_ref(&self.0);
lua.push_value(key)?;
key.push_into_stack(lua)?;
ffi::lua_rawget(state, -2);
lua.pop_value()
};
V::from_lua(value, lua)
V::from_stack(-1, lua)
}
}
/// Inserts element value at position `idx` to the table, shifting up the elements from `table[idx]`.
/// The worst case complexity is O(n), where n is the table length.
pub fn raw_insert<V: IntoLua<'lua>>(&self, idx: Integer, value: V) -> Result<()> {
let lua = self.0.lua;
let state = lua.state();
let size = self.raw_len();
let size = self.raw_len() as Integer;
if idx < 1 || idx > size + 1 {
return Err(Error::runtime("index out of bounds"));
}
let value = value.into_lua(lua)?;
let lua = self.0.lua;
let state = lua.state();
unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 5)?;
lua.push_ref(&self.0);
lua.push_value(value)?;
value.push_into_stack(lua)?;
protect_lua!(state, 2, 0, |state| {
for i in (idx..=size).rev() {
// table[i+1] = table[i]
@@ -347,14 +331,12 @@ impl<'lua> Table<'lua> {
let lua = self.0.lua;
let state = lua.state();
let value = value.into_lua(lua)?;
unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 4)?;
lua.push_ref(&self.0);
lua.push_value(value)?;
value.push_into_stack(lua)?;
unsafe fn callback(state: *mut ffi::lua_State) {
let len = ffi::lua_rawlen(state, -2) as Integer;
@@ -377,7 +359,7 @@ impl<'lua> Table<'lua> {
let lua = self.0.lua;
let state = lua.state();
let value = unsafe {
unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 3)?;
@@ -387,9 +369,9 @@ impl<'lua> Table<'lua> {
// Set slot to nil (it must be safe to do)
ffi::lua_pushnil(state);
ffi::lua_rawseti(state, -3, len);
lua.pop_value()
};
V::from_lua(value, lua)
V::from_stack(-1, lua)
}
}
/// Removes a key from the table.
@@ -405,7 +387,7 @@ impl<'lua> Table<'lua> {
let key = key.into_lua(lua)?;
match key {
Value::Integer(idx) => {
let size = self.raw_len();
let size = self.raw_len() as Integer;
if idx < 1 || idx > size {
return Err(Error::runtime("index out of bounds"));
}
@@ -477,7 +459,7 @@ impl<'lua> Table<'lua> {
pub fn len(&self) -> Result<Integer> {
// Fast track
if !self.has_metatable() {
return Ok(self.raw_len());
return Ok(self.raw_len() as Integer);
}
let lua = self.0.lua;
@@ -492,9 +474,9 @@ impl<'lua> Table<'lua> {
}
/// Returns the result of the Lua `#` operator, without invoking the `__len` metamethod.
pub fn raw_len(&self) -> Integer {
pub fn raw_len(&self) -> usize {
let ref_thread = self.0.lua.ref_thread();
unsafe { ffi::lua_rawlen(ref_thread, self.0.index) as Integer }
unsafe { ffi::lua_rawlen(ref_thread, self.0.index) }
}
/// Returns `true` if the table is empty, without invoking metamethods.
@@ -669,6 +651,34 @@ impl<'lua> Table<'lua> {
}
}
/// Iterates over the pairs of the table, invoking the given closure on each pair.
///
/// This method is similar to [`Table::pairs`], but optimized for performance.
/// It does not invoke the `__pairs` metamethod.
pub fn for_each<K, V>(&self, mut f: impl FnMut(K, V) -> Result<()>) -> Result<()>
where
K: FromLua<'lua>,
V: FromLua<'lua>,
{
let lua = self.0.lua;
let state = lua.state();
unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 5)?;
lua.push_ref(&self.0);
ffi::lua_pushnil(state);
while ffi::lua_next(state, -2) != 0 {
let k = K::from_stack(-2, lua)?;
let v = V::from_stack(-1, lua)?;
f(k, v)?;
// Keep key for next iteration
ffi::lua_pop(state, 1);
}
}
Ok(())
}
/// Consume this table and return an iterator over all values in the sequence part of the table.
///
/// The iterator will yield all values `t[1]`, `t[2]` and so on, until a `nil` value is
@@ -710,7 +720,7 @@ impl<'lua> Table<'lua> {
pub fn sequence_values<V: FromLua<'lua>>(self) -> TableSequence<'lua, V> {
TableSequence {
table: self.0,
index: Some(1),
index: 1,
len: None,
_phantom: PhantomData,
}
@@ -725,33 +735,31 @@ impl<'lua> Table<'lua> {
#[cfg(feature = "serialize")]
pub(crate) fn sequence_values_by_len<V: FromLua<'lua>>(
self,
len: Option<Integer>,
len: Option<usize>,
) -> TableSequence<'lua, V> {
let len = len.unwrap_or_else(|| self.raw_len());
let len = len.unwrap_or_else(|| self.raw_len()) as Integer;
TableSequence {
table: self.0,
index: Some(1),
index: 1,
len: Some(len),
_phantom: PhantomData,
}
}
/// Sets element value at position `idx` without invoking metamethods.
#[allow(dead_code)]
pub(crate) fn raw_seti<V: IntoLua<'lua>>(&self, idx: usize, value: V) -> Result<()> {
#[doc(hidden)]
pub fn raw_seti<V: IntoLua<'lua>>(&self, idx: usize, value: V) -> Result<()> {
#[cfg(feature = "luau")]
self.check_readonly_write()?;
let lua = self.0.lua;
let state = lua.state();
let value = value.into_lua(lua)?;
unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 5)?;
lua.push_ref(&self.0);
lua.push_value(value)?;
value.push_into_stack(lua)?;
let idx = idx.try_into().unwrap();
if lua.unlikely_memory_error() {
@@ -759,8 +767,8 @@ impl<'lua> Table<'lua> {
} else {
protect_lua!(state, 2, 0, |state| ffi::lua_rawseti(state, -2, idx))?;
}
Ok(())
}
Ok(())
}
#[cfg(feature = "serialize")]
@@ -1036,47 +1044,113 @@ impl<'lua> TableExt<'lua> for Table<'lua> {
}
}
/// A wrapped [`Table`] with customized serialization behavior.
#[cfg(feature = "serialize")]
pub(crate) struct SerializableTable<'a, 'lua> {
table: &'a Table<'lua>,
options: crate::serde::de::Options,
visited: Rc<RefCell<FxHashSet<*const c_void>>>,
}
#[cfg(feature = "serialize")]
impl<'lua> Serialize for Table<'lua> {
#[inline]
fn serialize<S: Serializer>(&self, serializer: S) -> StdResult<S::Ok, S::Error> {
SerializableTable::new(self, Default::default(), Default::default()).serialize(serializer)
}
}
#[cfg(feature = "serialize")]
impl<'a, 'lua> SerializableTable<'a, 'lua> {
#[inline]
pub(crate) fn new(
table: &'a Table<'lua>,
options: crate::serde::de::Options,
visited: Rc<RefCell<FxHashSet<*const c_void>>>,
) -> Self {
Self {
table,
options,
visited,
}
}
}
#[cfg(feature = "serialize")]
impl<'a, 'lua> Serialize for SerializableTable<'a, 'lua> {
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
where
S: Serializer,
{
thread_local! {
static VISITED: RefCell<FxHashSet<*const c_void>> = RefCell::new(FxHashSet::default());
use crate::serde::de::{check_value_for_skip, MapPairs};
use crate::value::SerializableValue;
let options = self.options;
let visited = &self.visited;
visited.borrow_mut().insert(self.table.to_pointer());
// Array
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)?;
if skip {
continue;
}
seq.serialize_element(&SerializableValue::new(value, options, Some(visited)))?;
}
return seq.end();
}
let ptr = self.to_pointer();
let res = VISITED.with(|visited| {
{
let mut visited = visited.borrow_mut();
if visited.contains(&ptr) {
return Err(ser::Error::custom("recursive table detected"));
}
visited.insert(ptr);
// HashMap
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)
.map_err(|err| Error::SerializeError(err.to_string()))?;
let skip_value = check_value_for_skip(&value, self.options, &self.visited)
.map_err(|err| Error::SerializeError(err.to_string()))?;
if skip_key || skip_value {
// continue iteration
return Ok(());
}
map.serialize_entry(
&SerializableValue::new(&key, options, Some(visited)),
&SerializableValue::new(&value, options, Some(visited)),
)
.map_err(|err| {
serialize_err = Some(err);
Error::SerializeError(String::new())
})
};
let len = self.raw_len() as usize;
if len > 0 || self.is_array() {
let mut seq = serializer.serialize_seq(Some(len))?;
for v in self.clone().sequence_values_by_len::<Value>(None) {
let v = v.map_err(serde::ser::Error::custom)?;
seq.serialize_element(&v)?;
}
return seq.end();
let res = if !self.options.sort_keys {
// Fast track
self.table.for_each(process_pair)
} else {
MapPairs::new(self.table.clone(), self.options.sort_keys)
.map_err(serde::ser::Error::custom)?
.try_for_each(|kv| {
let (key, value) = kv?;
process_pair(key, value)
})
};
match res {
Ok(_) => {}
Err(Error::SerializeError(_)) if serialize_err.is_some() => {
return Err(serialize_err.unwrap());
}
let mut map = serializer.serialize_map(None)?;
for kv in self.clone().pairs::<Value, Value>() {
let (k, v) = kv.map_err(serde::ser::Error::custom)?;
map.serialize_entry(&k, &v)?;
Err(Error::SerializeError(msg)) => {
return Err(serde::ser::Error::custom(msg));
}
map.end()
});
VISITED.with(|visited| {
visited.borrow_mut().remove(&ptr);
});
res
Err(err) => {
return Err(serde::ser::Error::custom(err.to_string()));
}
}
map.end()
}
}
@@ -1110,16 +1184,15 @@ where
lua.push_ref(&self.table);
lua.push_value(prev_key)?;
let next = protect_lua!(state, 2, ffi::LUA_MULTRET, |state| {
ffi::lua_next(state, -2)
})?;
if next != 0 {
let value = lua.pop_value();
let key = lua.pop_value();
// It must be safe to call `lua_next` unprotected as deleting a key from a table is
// a permitted operation.
// It fails only if the key is not found (never existed) which seems impossible scenario.
if ffi::lua_next(state, -2) != 0 {
let key = lua.stack_value(-2);
Ok(Some((
key.clone(),
K::from_lua(key, lua)?,
V::from_lua(value, lua)?,
V::from_stack(-1, lua)?,
)))
} else {
Ok(None)
@@ -1147,7 +1220,7 @@ where
/// [`Table::sequence_values`]: crate::Table::sequence_values
pub struct TableSequence<'lua, V> {
table: LuaRef<'lua>,
index: Option<Integer>,
index: Integer,
len: Option<Integer>,
_phantom: PhantomData<V>,
}
@@ -1159,31 +1232,22 @@ where
type Item = Result<V>;
fn next(&mut self) -> Option<Self::Item> {
if let Some(index) = self.index.take() {
let lua = self.table.lua;
let state = lua.state();
let res = (|| unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 1)?;
lua.push_ref(&self.table);
match ffi::lua_rawgeti(state, -1, index) {
ffi::LUA_TNIL if index > self.len.unwrap_or(0) => Ok(None),
_ => Ok(Some((index, lua.pop_value()))),
}
})();
match res {
Ok(Some((index, r))) => {
self.index = Some(index + 1);
Some(V::from_lua(r, lua))
}
Ok(None) => None,
Err(err) => Some(Err(err)),
let lua = self.table.lua;
let state = lua.state();
unsafe {
let _sg = StackGuard::new(state);
if let Err(err) = check_stack(state, 1) {
return Some(Err(err));
}
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,
_ => {
self.index += 1;
Some(V::from_stack(-1, lua))
}
}
} else {
None
}
}
}
+190 -131
View File
@@ -1,4 +1,3 @@
use std::cmp;
use std::os::raw::c_int;
use crate::error::{Error, Result};
@@ -16,10 +15,7 @@ use crate::{
#[cfg(feature = "async")]
use {
crate::{
lua::ASYNC_POLL_PENDING,
value::{MultiValue, Value},
},
crate::{lua::ASYNC_POLL_PENDING, value::MultiValue},
futures_util::stream::Stream,
std::{
future::Future,
@@ -30,7 +26,7 @@ use {
},
};
/// Status of a Lua thread (or coroutine).
/// Status of a Lua thread (coroutine).
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum ThreadStatus {
/// The thread was just created, or is suspended because it has called `coroutine.yield`.
@@ -45,9 +41,33 @@ pub enum ThreadStatus {
Error,
}
/// Handle to an internal Lua thread (or coroutine).
/// Handle to an internal Lua thread (coroutine).
#[derive(Clone, Debug)]
pub struct Thread<'lua>(pub(crate) LuaRef<'lua>);
pub struct Thread<'lua>(pub(crate) LuaRef<'lua>, pub(crate) *mut ffi::lua_State);
/// Owned handle to an internal Lua thread (coroutine).
///
/// The owned handle holds a *strong* reference to the current Lua instance.
/// Be warned, if you place it into a Lua type (eg. [`UserData`] or a Rust callback), it is *very easy*
/// to accidentally cause reference cycles that would prevent destroying Lua instance.
///
/// [`UserData`]: crate::UserData
#[cfg(feature = "unstable")]
#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
#[derive(Clone, Debug)]
pub struct OwnedThread(
pub(crate) crate::types::LuaOwnedRef,
pub(crate) *mut ffi::lua_State,
);
#[cfg(feature = "unstable")]
impl OwnedThread {
/// Get borrowed handle to the underlying Lua table.
#[cfg_attr(feature = "send", allow(unused))]
pub const fn to_ref(&self) -> Thread {
Thread(self.0.to_ref(), self.1)
}
}
/// Thread (coroutine) representation as an async [`Future`] or [`Stream`].
///
@@ -66,6 +86,16 @@ pub struct AsyncThread<'lua, R> {
}
impl<'lua> Thread<'lua> {
#[inline(always)]
pub(crate) fn new(r#ref: LuaRef<'lua>) -> Self {
let state = unsafe { ffi::lua_tothread(r#ref.lua.ref_thread(), r#ref.index) };
Thread(r#ref, state)
}
const fn state(&self) -> *mut ffi::lua_State {
self.1
}
/// Resumes execution of this thread.
///
/// Equivalent to `coroutine.resume`.
@@ -114,60 +144,59 @@ impl<'lua> Thread<'lua> {
{
let lua = self.0.lua;
let state = lua.state();
let mut args = args.into_lua_multi(lua)?;
let nargs = args.len() as c_int;
let results = unsafe {
let thread_state = self.state();
unsafe {
let _sg = StackGuard::new(state);
check_stack(state, cmp::max(nargs + 1, 3))?;
let _thread_sg = StackGuard::with_top(thread_state, 0);
let thread_state = ffi::lua_tothread(lua.ref_thread(), self.0.index);
let status = ffi::lua_status(thread_state);
if status != ffi::LUA_YIELD && ffi::lua_gettop(thread_state) == 0 {
return Err(Error::CoroutineInactive);
}
check_stack(thread_state, nargs)?;
for arg in args.drain_all() {
lua.push_value(arg)?;
}
ffi::lua_xmove(state, thread_state, nargs);
let mut nresults = 0;
let ret = ffi::lua_resume(thread_state, state, nargs, &mut nresults as *mut c_int);
if ret != ffi::LUA_OK && ret != ffi::LUA_YIELD {
if ret == ffi::LUA_ERRMEM {
// Don't call error handler for memory errors
return Err(pop_error(thread_state, ret));
}
check_stack(state, 3)?;
protect_lua!(state, 0, 1, |state| error_traceback_thread(
state,
thread_state
))?;
return Err(pop_error(state, ret));
}
let mut results = args; // Reuse MultiValue container
check_stack(state, nresults + 2)?; // 2 is extra for `lua.pop_value()` below
let nresults = self.resume_inner(args)?;
check_stack(state, nresults + 1)?;
ffi::lua_xmove(thread_state, state, nresults);
for _ in 0..nresults {
results.push_front(lua.pop_value());
R::from_stack_multi(nresults, lua)
}
}
/// Resumes execution of this thread.
///
/// It's similar to `resume()` but leaves `nresults` values on the thread stack.
unsafe fn resume_inner<A: IntoLuaMulti<'lua>>(&self, args: A) -> Result<c_int> {
let lua = self.0.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)?;
ffi::lua_xmove(state, thread_state, nargs);
}
let mut nresults = 0;
let ret = ffi::lua_resume(thread_state, state, nargs, &mut nresults as *mut c_int);
if ret != ffi::LUA_OK && ret != ffi::LUA_YIELD {
if ret == ffi::LUA_ERRMEM {
// Don't call error handler for memory errors
return Err(pop_error(thread_state, ret));
}
results
};
R::from_lua_multi(results, lua)
check_stack(state, 3)?;
protect_lua!(state, 0, 1, |state| error_traceback_thread(
state,
thread_state
))?;
return Err(pop_error(state, ret));
}
Ok(nresults)
}
/// Gets the status of the thread.
pub fn status(&self) -> ThreadStatus {
let lua = self.0.lua;
let thread_state = self.state();
unsafe {
let thread_state = ffi::lua_tothread(lua.ref_thread(), self.0.index);
let status = ffi::lua_status(thread_state);
if status != ffi::LUA_OK && status != ffi::LUA_YIELD {
ThreadStatus::Error
@@ -191,8 +220,7 @@ impl<'lua> Thread<'lua> {
{
let lua = self.0.lua;
unsafe {
let thread_state = ffi::lua_tothread(lua.ref_thread(), self.0.index);
lua.set_thread_hook(thread_state, triggers, callback);
lua.set_thread_hook(self.state(), triggers, callback);
}
}
@@ -211,21 +239,15 @@ impl<'lua> Thread<'lua> {
///
/// [Lua 5.4]: https://www.lua.org/manual/5.4/manual.html#lua_closethread
#[cfg(any(feature = "lua54", feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "luau"))))]
pub fn reset(&self, func: crate::function::Function<'lua>) -> Result<()> {
let lua = self.0.lua;
let state = lua.state();
let thread_state = self.state();
unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 2)?;
lua.push_ref(&self.0);
let thread_state = ffi::lua_tothread(state, -1);
#[cfg(all(feature = "lua54", not(feature = "vendored")))]
let status = ffi::lua_resetthread(thread_state);
#[cfg(all(feature = "lua54", feature = "vendored"))]
let status = ffi::lua_closethread(thread_state, state);
let status = ffi::lua_closethread(thread_state, lua.state());
#[cfg(feature = "lua54")]
if status != ffi::LUA_OK {
return Err(pop_error(thread_state, status));
@@ -233,8 +255,8 @@ impl<'lua> Thread<'lua> {
#[cfg(feature = "luau")]
ffi::lua_resetthread(thread_state);
lua.push_ref(&func.0);
ffi::lua_xmove(state, thread_state, 1);
// Push function to the top of the thread stack
ffi::lua_xpush(lua.ref_thread(), thread_state, func.0.index);
#[cfg(feature = "luau")]
{
@@ -345,13 +367,21 @@ impl<'lua> Thread<'lua> {
pub fn sandbox(&self) -> Result<()> {
let lua = self.0.lua;
let state = lua.state();
let thread_state = self.state();
unsafe {
let thread = ffi::lua_tothread(lua.ref_thread(), self.0.index);
check_stack(thread, 3)?;
check_stack(thread_state, 3)?;
check_stack(state, 3)?;
protect_lua!(state, 0, 0, |_| ffi::luaL_sandboxthread(thread))
protect_lua!(state, 0, 0, |_| ffi::luaL_sandboxthread(thread_state))
}
}
/// 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")))))]
#[inline]
pub fn into_owned(self) -> OwnedThread {
OwnedThread(self.0.into_owned(), self.1)
}
}
impl<'lua> PartialEq for Thread<'lua> {
@@ -360,6 +390,26 @@ impl<'lua> PartialEq for Thread<'lua> {
}
}
// Additional shortcuts
#[cfg(feature = "unstable")]
impl OwnedThread {
/// Resumes execution of this thread.
///
/// See [`Thread::resume()`] for more details.
pub fn resume<'lua, A, R>(&'lua self, args: A) -> Result<R>
where
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua>,
{
self.to_ref().resume(args)
}
/// Gets the status of the thread.
pub fn status(&self) -> ThreadStatus {
self.to_ref().status()
}
}
#[cfg(feature = "async")]
impl<'lua, R> AsyncThread<'lua, R> {
#[inline]
@@ -379,11 +429,10 @@ impl<'lua, R> Drop for AsyncThread<'lua, R> {
if !lua.recycle_thread(&mut self.thread) {
#[cfg(feature = "lua54")]
if self.thread.status() == ThreadStatus::Error {
let thread_state = ffi::lua_tothread(lua.ref_thread(), self.thread.0.index);
#[cfg(not(feature = "vendored"))]
ffi::lua_resetthread(thread_state);
ffi::lua_resetthread(self.thread.state());
#[cfg(feature = "vendored")]
ffi::lua_closethread(thread_state, lua.state());
ffi::lua_closethread(self.thread.state(), lua.state());
}
}
}
@@ -399,29 +448,36 @@ where
type Item = Result<R>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let lua = self.thread.0.lua;
match self.thread.status() {
ThreadStatus::Resumable => {}
_ => return Poll::Ready(None),
};
let _wg = WakerGuard::new(lua, cx.waker());
// This is safe as we are not moving the whole struct
let this = unsafe { self.get_unchecked_mut() };
let ret: MultiValue = if let Some(args) = this.init_args.take() {
this.thread.resume(args?)?
} else {
this.thread.resume(())?
};
if is_poll_pending(&ret) {
return Poll::Pending;
if self.thread.status() != ThreadStatus::Resumable {
return Poll::Ready(None);
}
cx.waker().wake_by_ref();
Poll::Ready(Some(R::from_lua_multi(ret, lua)))
let lua = self.thread.0.lua;
let state = lua.state();
let thread_state = self.thread.state();
unsafe {
let _sg = StackGuard::new(state);
let _thread_sg = StackGuard::with_top(thread_state, 0);
let _wg = WakerGuard::new(lua, cx.waker());
// This is safe as we are not moving the whole struct
let this = self.get_unchecked_mut();
let nresults = if let Some(args) = this.init_args.take() {
this.thread.resume_inner(args?)?
} else {
this.thread.resume_inner(())?
};
if nresults == 1 && is_poll_pending(thread_state) {
return Poll::Pending;
}
check_stack(state, nresults + 1)?;
ffi::lua_xmove(thread_state, state, nresults);
cx.waker().wake_by_ref();
Poll::Ready(Some(R::from_stack_multi(nresults, lua)))
}
}
}
@@ -433,46 +489,53 @@ where
type Output = Result<R>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if self.thread.status() != ThreadStatus::Resumable {
return Poll::Ready(Err(Error::CoroutineInactive));
}
let lua = self.thread.0.lua;
let state = lua.state();
let thread_state = self.thread.state();
unsafe {
let _sg = StackGuard::new(state);
let _thread_sg = StackGuard::with_top(thread_state, 0);
let _wg = WakerGuard::new(lua, cx.waker());
match self.thread.status() {
ThreadStatus::Resumable => {}
_ => return Poll::Ready(Err(Error::CoroutineInactive)),
};
// This is safe as we are not moving the whole struct
let this = self.get_unchecked_mut();
let nresults = if let Some(args) = this.init_args.take() {
this.thread.resume_inner(args?)?
} else {
this.thread.resume_inner(())?
};
let _wg = WakerGuard::new(lua, cx.waker());
if nresults == 1 && is_poll_pending(thread_state) {
return Poll::Pending;
}
// This is safe as we are not moving the whole struct
let this = unsafe { self.get_unchecked_mut() };
let ret: MultiValue = if let Some(args) = this.init_args.take() {
this.thread.resume(args?)?
} else {
this.thread.resume(())?
};
if ffi::lua_status(thread_state) == ffi::LUA_YIELD {
// Ignore value returned via yield()
cx.waker().wake_by_ref();
return Poll::Pending;
}
if is_poll_pending(&ret) {
return Poll::Pending;
check_stack(state, nresults + 1)?;
ffi::lua_xmove(thread_state, state, nresults);
Poll::Ready(R::from_stack_multi(nresults, lua))
}
if let ThreadStatus::Resumable = this.thread.status() {
// Ignore value returned via yield()
cx.waker().wake_by_ref();
return Poll::Pending;
}
Poll::Ready(R::from_lua_multi(ret, lua))
}
}
#[cfg(feature = "async")]
#[inline(always)]
fn is_poll_pending(val: &MultiValue) -> bool {
match val.iter().enumerate().last() {
Some((0, Value::LightUserData(ud))) => {
std::ptr::eq(ud.0 as *const u8, &ASYNC_POLL_PENDING as *const u8)
}
_ => false,
unsafe fn is_poll_pending(state: *mut ffi::lua_State) -> bool {
if ffi::lua_islightuserdata(state, -1) != 0 {
let stack_ptr = ffi::lua_touserdata(state, -1) as *const u8;
let pending_ptr = &ASYNC_POLL_PENDING as *const u8;
return std::ptr::eq(stack_ptr, pending_ptr);
}
false
}
#[cfg(feature = "async")]
@@ -486,23 +549,19 @@ struct WakerGuard<'lua, 'a> {
impl<'lua, 'a> WakerGuard<'lua, 'a> {
#[inline]
pub fn new(lua: &'lua Lua, waker: &'a Waker) -> Result<WakerGuard<'lua, 'a>> {
unsafe {
let prev = lua.set_waker(NonNull::from(waker));
Ok(WakerGuard {
lua,
prev,
_phantom: PhantomData,
})
}
let prev = unsafe { lua.set_waker(NonNull::from(waker)) };
Ok(WakerGuard {
lua,
prev,
_phantom: PhantomData,
})
}
}
#[cfg(feature = "async")]
impl<'lua, 'a> Drop for WakerGuard<'lua, 'a> {
fn drop(&mut self) {
unsafe {
self.lua.set_waker(self.prev);
}
unsafe { self.lua.set_waker(self.prev) };
}
}
+16 -8
View File
@@ -10,14 +10,13 @@ use std::{fmt, mem, ptr};
use rustc_hash::FxHashMap;
#[cfg(feature = "async")]
use futures_util::future::LocalBoxFuture;
use crate::error::Result;
#[cfg(not(feature = "luau"))]
use crate::hook::Debug;
use crate::lua::{ExtraData, Lua};
use crate::value::MultiValue;
#[cfg(feature = "async")]
use {crate::value::MultiValue, futures_util::future::LocalBoxFuture};
#[cfg(feature = "unstable")]
use {crate::lua::LuaInner, std::marker::PhantomData};
@@ -30,12 +29,21 @@ pub type Integer = ffi::lua_Integer;
/// Type of Lua floating point numbers.
pub type Number = ffi::lua_Number;
// Represents different subtypes wrapped to AnyUserData
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub(crate) enum SubtypeId {
None,
#[cfg(feature = "luau")]
Buffer,
#[cfg(feature = "luajit")]
CData,
}
/// A "light" userdata value. Equivalent to an unmanaged raw pointer.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct LightUserData(pub *mut c_void);
pub(crate) type Callback<'lua, 'a> =
Box<dyn Fn(&'lua Lua, MultiValue<'lua>) -> Result<MultiValue<'lua>> + 'a>;
pub(crate) type Callback<'lua, 'a> = Box<dyn Fn(&'lua Lua, c_int) -> Result<c_int> + 'a>;
pub(crate) struct Upvalue<T> {
pub(crate) data: T,
@@ -46,13 +54,13 @@ pub(crate) type CallbackUpvalue = Upvalue<Callback<'static, 'static>>;
#[cfg(feature = "async")]
pub(crate) type AsyncCallback<'lua, 'a> =
Box<dyn Fn(&'lua Lua, MultiValue<'lua>) -> LocalBoxFuture<'lua, Result<MultiValue<'lua>>> + 'a>;
Box<dyn Fn(&'lua Lua, MultiValue<'lua>) -> LocalBoxFuture<'lua, Result<c_int>> + 'a>;
#[cfg(feature = "async")]
pub(crate) type AsyncCallbackUpvalue = Upvalue<AsyncCallback<'static, 'static>>;
#[cfg(feature = "async")]
pub(crate) type AsyncPollUpvalue = Upvalue<LocalBoxFuture<'static, Result<MultiValue<'static>>>>;
pub(crate) type AsyncPollUpvalue = Upvalue<LocalBoxFuture<'static, Result<c_int>>>;
/// Type to set next Luau VM action after executing interrupt function.
#[cfg(any(feature = "luau", doc))]
+22 -13
View File
@@ -22,7 +22,7 @@ use crate::function::Function;
use crate::lua::Lua;
use crate::string::String;
use crate::table::{Table, TablePairs};
use crate::types::{LuaRef, MaybeSend};
use crate::types::{LuaRef, MaybeSend, SubtypeId};
use crate::util::{check_stack, get_userdata, take_userdata, StackGuard};
use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Value};
use crate::UserDataRegistry;
@@ -580,12 +580,12 @@ pub trait UserDataFields<'lua, T> {
/// # use mlua::{Lua, Result, UserData};
/// # fn main() -> Result<()> {
/// # let lua = Lua::new();
/// struct MyUserData(i32);
/// struct MyUserData;
///
/// impl UserData for MyUserData {}
///
/// // `MyUserData` now implements `IntoLua`:
/// lua.globals().set("myobject", MyUserData(123))?;
/// lua.globals().set("myobject", MyUserData)?;
///
/// lua.load("assert(type(myobject) == 'userdata')").exec()?;
/// # Ok(())
@@ -791,7 +791,7 @@ impl<T> Deref for UserDataVariant<T> {
/// [`is`]: crate::AnyUserData::is
/// [`borrow`]: crate::AnyUserData::borrow
#[derive(Clone, Debug)]
pub struct AnyUserData<'lua>(pub(crate) LuaRef<'lua>);
pub struct AnyUserData<'lua>(pub(crate) LuaRef<'lua>, pub(crate) SubtypeId);
/// Owned handle to an internal Lua userdata.
///
@@ -801,14 +801,14 @@ pub struct AnyUserData<'lua>(pub(crate) LuaRef<'lua>);
#[cfg(feature = "unstable")]
#[cfg_attr(docsrs, doc(cfg(feature = "unstable")))]
#[derive(Clone, Debug)]
pub struct OwnedAnyUserData(pub(crate) crate::types::LuaOwnedRef);
pub struct OwnedAnyUserData(pub(crate) crate::types::LuaOwnedRef, pub(crate) SubtypeId);
#[cfg(feature = "unstable")]
impl OwnedAnyUserData {
/// Get borrowed handle to the underlying Lua userdata.
#[cfg_attr(feature = "send", allow(unused))]
pub const fn to_ref(&self) -> AnyUserData {
AnyUserData(self.0.to_ref())
AnyUserData(self.0.to_ref(), self.1)
}
}
@@ -1101,16 +1101,25 @@ impl<'lua> AnyUserData<'lua> {
#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))]
#[inline]
pub fn into_owned(self) -> OwnedAnyUserData {
OwnedAnyUserData(self.0.into_owned())
OwnedAnyUserData(self.0.into_owned(), self.1)
}
#[inline(always)]
#[cfg(feature = "async")]
#[inline]
pub(crate) fn type_id(&self) -> Result<Option<TypeId>> {
unsafe { self.0.lua.get_userdata_type_id(&self.0) }
unsafe { self.0.lua.get_userdata_ref_type_id(&self.0) }
}
/// Returns a type name of this `UserData` (from a metatable field).
pub(crate) fn type_name(&self) -> Result<Option<StdString>> {
match self.1 {
SubtypeId::None => {}
#[cfg(feature = "luau")]
SubtypeId::Buffer => return Ok(Some("buffer".to_owned())),
#[cfg(feature = "luajit")]
SubtypeId::CData => return Ok(Some("cdata".to_owned())),
}
let lua = self.0.lua;
let state = lua.state();
unsafe {
@@ -1154,13 +1163,13 @@ impl<'lua> AnyUserData<'lua> {
Ok(false)
}
/// Returns true if this `AnyUserData` is serializable (eg. was created using `create_ser_userdata`).
/// Returns `true` if this `AnyUserData` is serializable (eg. was created using `create_ser_userdata`).
#[cfg(feature = "serialize")]
pub(crate) fn is_serializable(&self) -> bool {
let lua = self.0.lua;
let is_serializable = || unsafe {
// Userdata can be unregistered or destructed
let _ = lua.get_userdata_type_id(&self.0)?;
let _ = lua.get_userdata_ref_type_id(&self.0)?;
let ud = &*get_userdata::<UserDataCell<()>>(lua.ref_thread(), self.0.index);
match &*ud.0.try_borrow().map_err(|_| Error::UserDataBorrowError)? {
@@ -1178,7 +1187,7 @@ impl<'lua> AnyUserData<'lua> {
{
let lua = self.0.lua;
unsafe {
let type_id = lua.get_userdata_type_id(&self.0)?;
let type_id = lua.get_userdata_ref_type_id(&self.0)?;
match type_id {
Some(type_id) if type_id == TypeId::of::<T>() => {
let ref_thread = lua.ref_thread();
@@ -1327,7 +1336,7 @@ impl<'lua> Serialize for AnyUserData<'lua> {
let lua = self.0.lua;
let data = unsafe {
let _ = lua
.get_userdata_type_id(&self.0)
.get_userdata_ref_type_id(&self.0)
.map_err(ser::Error::custom)?;
let ud = &*get_userdata::<UserDataCell<()>>(lua.ref_thread(), self.0.index);
ud.0.try_borrow()
+157 -178
View File
@@ -14,17 +14,13 @@ use crate::userdata::{
AnyUserData, MetaMethod, UserData, UserDataCell, UserDataFields, UserDataMethods,
};
use crate::util::{get_userdata, short_type_name};
use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, MultiValue, Value};
use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Value};
#[cfg(not(feature = "send"))]
use std::rc::Rc;
#[cfg(feature = "async")]
use {
crate::types::AsyncCallback,
futures_util::future::{self, TryFutureExt},
std::future::Future,
};
use {crate::types::AsyncCallback, futures_util::future, std::future::Future};
/// Handle to registry for userdata methods and metamethods.
pub struct UserDataRegistry<'lua, T: 'static> {
@@ -78,63 +74,61 @@ impl<'lua, T: 'static> UserDataRegistry<'lua, T> {
};
}
Box::new(move |lua, mut args| {
let front = args
.pop_front()
.ok_or_else(|| Error::from_lua_conversion("missing argument", "userdata", None));
let front = try_self_arg!(front);
let call = |ud| {
// Self was at index 1, so we pass 2 here
let args = A::from_lua_multi_args(args, 2, Some(&name), lua)?;
method(lua, ud, args)?.into_lua_multi(lua)
};
Box::new(move |lua, nargs| unsafe {
if nargs == 0 {
let err = Error::from_lua_conversion("missing argument", "userdata", None);
try_self_arg!(Err(err));
}
let state = lua.state();
// Find absolute "self" index before processing args
let index = ffi::lua_absindex(state, -nargs);
// Self was at position 1, so we pass 2 here
let args = A::from_stack_args(nargs - 1, 2, Some(&name), lua);
let userdata = try_self_arg!(AnyUserData::from_lua(front, lua));
let (ref_thread, index) = (lua.ref_thread(), userdata.0.index);
match try_self_arg!(userdata.type_id()) {
Some(id) if id == TypeId::of::<T>() => unsafe {
let ud = try_self_arg!(get_userdata_ref::<T>(ref_thread, index));
call(&ud)
},
match try_self_arg!(lua.get_userdata_type_id(index)) {
Some(id) if id == TypeId::of::<T>() => {
let ud = try_self_arg!(get_userdata_ref::<T>(state, index));
method(lua, &ud, args?)?.push_into_stack_multi(lua)
}
#[cfg(not(feature = "send"))]
Some(id) if id == TypeId::of::<Rc<T>>() => unsafe {
let ud = try_self_arg!(get_userdata_ref::<Rc<T>>(ref_thread, index));
call(&ud)
},
Some(id) if id == TypeId::of::<Rc<T>>() => {
let ud = try_self_arg!(get_userdata_ref::<Rc<T>>(state, index));
method(lua, &ud, args?)?.push_into_stack_multi(lua)
}
#[cfg(not(feature = "send"))]
Some(id) if id == TypeId::of::<Rc<RefCell<T>>>() => unsafe {
let ud = try_self_arg!(get_userdata_ref::<Rc<RefCell<T>>>(ref_thread, index));
Some(id) if id == TypeId::of::<Rc<RefCell<T>>>() => {
let ud = try_self_arg!(get_userdata_ref::<Rc<RefCell<T>>>(state, index));
let ud = try_self_arg!(ud.try_borrow(), Error::UserDataBorrowError);
call(&ud)
},
Some(id) if id == TypeId::of::<Arc<T>>() => unsafe {
let ud = try_self_arg!(get_userdata_ref::<Arc<T>>(ref_thread, index));
call(&ud)
},
Some(id) if id == TypeId::of::<Arc<Mutex<T>>>() => unsafe {
let ud = try_self_arg!(get_userdata_ref::<Arc<Mutex<T>>>(ref_thread, index));
method(lua, &ud, args?)?.push_into_stack_multi(lua)
}
Some(id) if id == TypeId::of::<Arc<T>>() => {
let ud = try_self_arg!(get_userdata_ref::<Arc<T>>(state, index));
method(lua, &ud, args?)?.push_into_stack_multi(lua)
}
Some(id) if id == TypeId::of::<Arc<Mutex<T>>>() => {
let ud = try_self_arg!(get_userdata_ref::<Arc<Mutex<T>>>(state, index));
let ud = try_self_arg!(ud.try_lock(), Error::UserDataBorrowError);
call(&ud)
},
method(lua, &ud, args?)?.push_into_stack_multi(lua)
}
#[cfg(feature = "parking_lot")]
Some(id) if id == TypeId::of::<Arc<parking_lot::Mutex<T>>>() => unsafe {
let ud = get_userdata_ref::<Arc<parking_lot::Mutex<T>>>(ref_thread, index);
Some(id) if id == TypeId::of::<Arc<parking_lot::Mutex<T>>>() => {
let ud = get_userdata_ref::<Arc<parking_lot::Mutex<T>>>(state, index);
let ud = try_self_arg!(ud);
let ud = try_self_arg!(ud.try_lock().ok_or(Error::UserDataBorrowError));
call(&ud)
},
Some(id) if id == TypeId::of::<Arc<RwLock<T>>>() => unsafe {
let ud = try_self_arg!(get_userdata_ref::<Arc<RwLock<T>>>(ref_thread, index));
method(lua, &ud, args?)?.push_into_stack_multi(lua)
}
Some(id) if id == TypeId::of::<Arc<RwLock<T>>>() => {
let ud = try_self_arg!(get_userdata_ref::<Arc<RwLock<T>>>(state, index));
let ud = try_self_arg!(ud.try_read(), Error::UserDataBorrowError);
call(&ud)
},
method(lua, &ud, args?)?.push_into_stack_multi(lua)
}
#[cfg(feature = "parking_lot")]
Some(id) if id == TypeId::of::<Arc<parking_lot::RwLock<T>>>() => unsafe {
let ud = get_userdata_ref::<Arc<parking_lot::RwLock<T>>>(ref_thread, index);
Some(id) if id == TypeId::of::<Arc<parking_lot::RwLock<T>>>() => {
let ud = get_userdata_ref::<Arc<parking_lot::RwLock<T>>>(state, index);
let ud = try_self_arg!(ud);
let ud = try_self_arg!(ud.try_read().ok_or(Error::UserDataBorrowError));
call(&ud)
},
method(lua, &ud, args?)?.push_into_stack_multi(lua)
}
_ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)),
}
})
@@ -157,60 +151,58 @@ impl<'lua, T: 'static> UserDataRegistry<'lua, T> {
}
let method = RefCell::new(method);
Box::new(move |lua, mut args| {
Box::new(move |lua, nargs| unsafe {
let mut method = method
.try_borrow_mut()
.map_err(|_| Error::RecursiveMutCallback)?;
let front = args
.pop_front()
.ok_or_else(|| Error::from_lua_conversion("missing argument", "userdata", None));
let front = try_self_arg!(front);
let call = |ud| {
// Self was at index 1, so we pass 2 here
let args = A::from_lua_multi_args(args, 2, Some(&name), lua)?;
method(lua, ud, args)?.into_lua_multi(lua)
};
if nargs == 0 {
let err = Error::from_lua_conversion("missing argument", "userdata", None);
try_self_arg!(Err(err));
}
let state = lua.state();
// Find absolute "self" index before processing args
let index = ffi::lua_absindex(state, -nargs);
// Self was at position 1, so we pass 2 here
let args = A::from_stack_args(nargs - 1, 2, Some(&name), lua);
let userdata = try_self_arg!(AnyUserData::from_lua(front, lua));
let (ref_thread, index) = (lua.ref_thread(), userdata.0.index);
match try_self_arg!(userdata.type_id()) {
Some(id) if id == TypeId::of::<T>() => unsafe {
let mut ud = try_self_arg!(get_userdata_mut::<T>(ref_thread, index));
call(&mut ud)
},
match try_self_arg!(lua.get_userdata_type_id(index)) {
Some(id) if id == TypeId::of::<T>() => {
let mut ud = try_self_arg!(get_userdata_mut::<T>(state, index));
method(lua, &mut ud, args?)?.push_into_stack_multi(lua)
}
#[cfg(not(feature = "send"))]
Some(id) if id == TypeId::of::<Rc<T>>() => Err(Error::UserDataBorrowMutError),
#[cfg(not(feature = "send"))]
Some(id) if id == TypeId::of::<Rc<RefCell<T>>>() => unsafe {
let ud = try_self_arg!(get_userdata_mut::<Rc<RefCell<T>>>(ref_thread, index));
Some(id) if id == TypeId::of::<Rc<RefCell<T>>>() => {
let ud = try_self_arg!(get_userdata_mut::<Rc<RefCell<T>>>(state, index));
let mut ud = try_self_arg!(ud.try_borrow_mut(), Error::UserDataBorrowMutError);
call(&mut ud)
},
method(lua, &mut ud, args?)?.push_into_stack_multi(lua)
}
Some(id) if id == TypeId::of::<Arc<T>>() => Err(Error::UserDataBorrowMutError),
Some(id) if id == TypeId::of::<Arc<Mutex<T>>>() => unsafe {
let ud = try_self_arg!(get_userdata_mut::<Arc<Mutex<T>>>(ref_thread, index));
Some(id) if id == TypeId::of::<Arc<Mutex<T>>>() => {
let ud = try_self_arg!(get_userdata_mut::<Arc<Mutex<T>>>(state, index));
let mut ud = try_self_arg!(ud.try_lock(), Error::UserDataBorrowMutError);
call(&mut ud)
},
method(lua, &mut ud, args?)?.push_into_stack_multi(lua)
}
#[cfg(feature = "parking_lot")]
Some(id) if id == TypeId::of::<Arc<parking_lot::Mutex<T>>>() => unsafe {
let ud = get_userdata_mut::<Arc<parking_lot::Mutex<T>>>(ref_thread, index);
Some(id) if id == TypeId::of::<Arc<parking_lot::Mutex<T>>>() => {
let ud = get_userdata_mut::<Arc<parking_lot::Mutex<T>>>(state, index);
let ud = try_self_arg!(ud);
let mut ud = try_self_arg!(ud.try_lock().ok_or(Error::UserDataBorrowMutError));
call(&mut ud)
},
Some(id) if id == TypeId::of::<Arc<RwLock<T>>>() => unsafe {
let ud = try_self_arg!(get_userdata_mut::<Arc<RwLock<T>>>(ref_thread, index));
method(lua, &mut ud, args?)?.push_into_stack_multi(lua)
}
Some(id) if id == TypeId::of::<Arc<RwLock<T>>>() => {
let ud = try_self_arg!(get_userdata_mut::<Arc<RwLock<T>>>(state, index));
let mut ud = try_self_arg!(ud.try_write(), Error::UserDataBorrowMutError);
call(&mut ud)
},
method(lua, &mut ud, args?)?.push_into_stack_multi(lua)
}
#[cfg(feature = "parking_lot")]
Some(id) if id == TypeId::of::<Arc<parking_lot::RwLock<T>>>() => unsafe {
let ud = get_userdata_mut::<Arc<parking_lot::RwLock<T>>>(ref_thread, index);
Some(id) if id == TypeId::of::<Arc<parking_lot::RwLock<T>>>() => {
let ud = get_userdata_mut::<Arc<parking_lot::RwLock<T>>>(state, index);
let ud = try_self_arg!(ud);
let mut ud = try_self_arg!(ud.try_write().ok_or(Error::UserDataBorrowMutError));
call(&mut ud)
},
method(lua, &mut ud, args?)?.push_into_stack_multi(lua)
}
_ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)),
}
})
@@ -226,10 +218,10 @@ impl<'lua, T: 'static> UserDataRegistry<'lua, T> {
MR: Future<Output = Result<R>> + 's,
R: IntoLuaMulti<'lua>,
{
let name = get_function_name::<T>(name);
let name = Arc::new(get_function_name::<T>(name));
let method = Arc::new(method);
Box::new(move |lua, mut args| {
Box::new(move |lua, mut args| unsafe {
let name = name.clone();
let method = method.clone();
macro_rules! try_self_arg {
@@ -242,76 +234,68 @@ impl<'lua, T: 'static> UserDataRegistry<'lua, T> {
}
Box::pin(async move {
let front = args.pop_front().ok_or_else(|| {
let this = args.pop_front().ok_or_else(|| {
Error::from_lua_conversion("missing argument", "userdata", None)
});
let front = try_self_arg!(front);
let userdata: AnyUserData = try_self_arg!(AnyUserData::from_lua(front, lua));
let (ref_thread, index) = (lua.ref_thread(), userdata.0.index);
match try_self_arg!(userdata.type_id()) {
Some(id) if id == TypeId::of::<T>() => unsafe {
let this = try_self_arg!(AnyUserData::from_lua(try_self_arg!(this), lua));
let args = A::from_lua_args(args, 2, Some(&name), lua);
let (ref_thread, index) = (lua.ref_thread(), this.0.index);
match try_self_arg!(this.type_id()) {
Some(id) if id == TypeId::of::<T>() => {
let ud = try_self_arg!(get_userdata_ref::<T>(ref_thread, index));
let ud = std::mem::transmute::<&T, &T>(&ud);
// Self was at index 1, so we pass 2 here
let args = A::from_lua_multi_args(args, 2, Some(&name), lua)?;
method(lua, ud, args).await?.into_lua_multi(lua)
},
method(lua, ud, args?).await?.push_into_stack_multi(lua)
}
#[cfg(not(feature = "send"))]
Some(id) if id == TypeId::of::<Rc<T>>() => unsafe {
Some(id) if id == TypeId::of::<Rc<T>>() => {
let ud = try_self_arg!(get_userdata_ref::<Rc<T>>(ref_thread, index));
let ud = std::mem::transmute::<&T, &T>(&ud);
let args = A::from_lua_multi_args(args, 2, Some(&name), lua)?;
method(lua, ud, args).await?.into_lua_multi(lua)
},
method(lua, ud, args?).await?.push_into_stack_multi(lua)
}
#[cfg(not(feature = "send"))]
Some(id) if id == TypeId::of::<Rc<RefCell<T>>>() => unsafe {
Some(id) if id == TypeId::of::<Rc<RefCell<T>>>() => {
let ud =
try_self_arg!(get_userdata_ref::<Rc<RefCell<T>>>(ref_thread, index));
let ud = try_self_arg!(ud.try_borrow(), Error::UserDataBorrowError);
let ud = std::mem::transmute::<&T, &T>(&ud);
let args = A::from_lua_multi_args(args, 2, Some(&name), lua)?;
method(lua, ud, args).await?.into_lua_multi(lua)
},
Some(id) if id == TypeId::of::<Arc<T>>() => unsafe {
method(lua, ud, args?).await?.push_into_stack_multi(lua)
}
Some(id) if id == TypeId::of::<Arc<T>>() => {
let ud = try_self_arg!(get_userdata_ref::<Arc<T>>(ref_thread, index));
let ud = std::mem::transmute::<&T, &T>(&ud);
let args = A::from_lua_multi_args(args, 2, Some(&name), lua)?;
method(lua, ud, args).await?.into_lua_multi(lua)
},
Some(id) if id == TypeId::of::<Arc<Mutex<T>>>() => unsafe {
method(lua, ud, args?).await?.push_into_stack_multi(lua)
}
Some(id) if id == TypeId::of::<Arc<Mutex<T>>>() => {
let ud =
try_self_arg!(get_userdata_ref::<Arc<Mutex<T>>>(ref_thread, index));
let ud = try_self_arg!(ud.try_lock(), Error::UserDataBorrowError);
let ud = std::mem::transmute::<&T, &T>(&ud);
let args = A::from_lua_multi_args(args, 2, Some(&name), lua)?;
method(lua, ud, args).await?.into_lua_multi(lua)
},
method(lua, ud, args?).await?.push_into_stack_multi(lua)
}
#[cfg(feature = "parking_lot")]
Some(id) if id == TypeId::of::<Arc<parking_lot::Mutex<T>>>() => unsafe {
Some(id) if id == TypeId::of::<Arc<parking_lot::Mutex<T>>>() => {
let ud = get_userdata_ref::<Arc<parking_lot::Mutex<T>>>(ref_thread, index);
let ud = try_self_arg!(ud);
let ud = try_self_arg!(ud.try_lock().ok_or(Error::UserDataBorrowError));
let ud = std::mem::transmute::<&T, &T>(&ud);
let args = A::from_lua_multi_args(args, 2, Some(&name), lua)?;
method(lua, ud, args).await?.into_lua_multi(lua)
},
Some(id) if id == TypeId::of::<Arc<RwLock<T>>>() => unsafe {
method(lua, ud, args?).await?.push_into_stack_multi(lua)
}
Some(id) if id == TypeId::of::<Arc<RwLock<T>>>() => {
let ud =
try_self_arg!(get_userdata_ref::<Arc<RwLock<T>>>(ref_thread, index));
let ud = try_self_arg!(ud.try_read(), Error::UserDataBorrowError);
let ud = std::mem::transmute::<&T, &T>(&ud);
let args = A::from_lua_multi_args(args, 2, Some(&name), lua)?;
method(lua, ud, args).await?.into_lua_multi(lua)
},
method(lua, ud, args?).await?.push_into_stack_multi(lua)
}
#[cfg(feature = "parking_lot")]
Some(id) if id == TypeId::of::<Arc<parking_lot::RwLock<T>>>() => unsafe {
Some(id) if id == TypeId::of::<Arc<parking_lot::RwLock<T>>>() => {
let ud = get_userdata_ref::<Arc<parking_lot::RwLock<T>>>(ref_thread, index);
let ud = try_self_arg!(ud);
let ud = try_self_arg!(ud.try_read().ok_or(Error::UserDataBorrowError));
let ud = std::mem::transmute::<&T, &T>(&ud);
let args = A::from_lua_multi_args(args, 2, Some(&name), lua)?;
method(lua, ud, args).await?.into_lua_multi(lua)
},
method(lua, ud, args?).await?.push_into_stack_multi(lua)
}
_ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)),
}
})
@@ -328,10 +312,10 @@ impl<'lua, T: 'static> UserDataRegistry<'lua, T> {
MR: Future<Output = Result<R>> + 's,
R: IntoLuaMulti<'lua>,
{
let name = get_function_name::<T>(name);
let name = Arc::new(get_function_name::<T>(name));
let method = Arc::new(method);
Box::new(move |lua, mut args| {
Box::new(move |lua, mut args| unsafe {
let name = name.clone();
let method = method.clone();
macro_rules! try_self_arg {
@@ -344,72 +328,66 @@ impl<'lua, T: 'static> UserDataRegistry<'lua, T> {
}
Box::pin(async move {
let front = args.pop_front().ok_or_else(|| {
let this = args.pop_front().ok_or_else(|| {
Error::from_lua_conversion("missing argument", "userdata", None)
});
let front = try_self_arg!(front);
let userdata: AnyUserData = try_self_arg!(AnyUserData::from_lua(front, lua));
let (ref_thread, index) = (lua.ref_thread(), userdata.0.index);
match try_self_arg!(userdata.type_id()) {
Some(id) if id == TypeId::of::<T>() => unsafe {
let this = try_self_arg!(AnyUserData::from_lua(try_self_arg!(this), lua));
let args = A::from_lua_args(args, 2, Some(&name), lua);
let (ref_thread, index) = (lua.ref_thread(), this.0.index);
match try_self_arg!(this.type_id()) {
Some(id) if id == TypeId::of::<T>() => {
let mut ud = try_self_arg!(get_userdata_mut::<T>(ref_thread, index));
let ud = std::mem::transmute::<&mut T, &mut T>(&mut ud);
// Self was at index 1, so we pass 2 here
let args = A::from_lua_multi_args(args, 2, Some(&name), lua)?;
method(lua, ud, args).await?.into_lua_multi(lua)
},
method(lua, ud, args?).await?.push_into_stack_multi(lua)
}
#[cfg(not(feature = "send"))]
Some(id) if id == TypeId::of::<Rc<RefCell<T>>>() => {
Err(Error::UserDataBorrowMutError)
}
#[cfg(not(feature = "send"))]
Some(id) if id == TypeId::of::<Rc<RefCell<T>>>() => unsafe {
Some(id) if id == TypeId::of::<Rc<RefCell<T>>>() => {
let ud =
try_self_arg!(get_userdata_mut::<Rc<RefCell<T>>>(ref_thread, index));
let mut ud =
try_self_arg!(ud.try_borrow_mut(), Error::UserDataBorrowMutError);
let ud = std::mem::transmute::<&mut T, &mut T>(&mut ud);
let args = A::from_lua_multi_args(args, 2, Some(&name), lua)?;
method(lua, ud, args).await?.into_lua_multi(lua)
},
method(lua, ud, args?).await?.push_into_stack_multi(lua)
}
#[cfg(not(feature = "send"))]
Some(id) if id == TypeId::of::<Arc<T>>() => Err(Error::UserDataBorrowMutError),
Some(id) if id == TypeId::of::<Arc<Mutex<T>>>() => unsafe {
Some(id) if id == TypeId::of::<Arc<Mutex<T>>>() => {
let ud =
try_self_arg!(get_userdata_mut::<Arc<Mutex<T>>>(ref_thread, index));
let mut ud = try_self_arg!(ud.try_lock(), Error::UserDataBorrowMutError);
let ud = std::mem::transmute::<&mut T, &mut T>(&mut ud);
let args = A::from_lua_multi_args(args, 2, Some(&name), lua)?;
method(lua, ud, args).await?.into_lua_multi(lua)
},
method(lua, ud, args?).await?.push_into_stack_multi(lua)
}
#[cfg(feature = "parking_lot")]
Some(id) if id == TypeId::of::<Arc<parking_lot::Mutex<T>>>() => unsafe {
Some(id) if id == TypeId::of::<Arc<parking_lot::Mutex<T>>>() => {
let ud = get_userdata_mut::<Arc<parking_lot::Mutex<T>>>(ref_thread, index);
let ud = try_self_arg!(ud);
let mut ud =
try_self_arg!(ud.try_lock().ok_or(Error::UserDataBorrowMutError));
let ud = std::mem::transmute::<&mut T, &mut T>(&mut ud);
let args = A::from_lua_multi_args(args, 2, Some(&name), lua)?;
method(lua, ud, args).await?.into_lua_multi(lua)
},
Some(id) if id == TypeId::of::<Arc<RwLock<T>>>() => unsafe {
method(lua, ud, args?).await?.push_into_stack_multi(lua)
}
Some(id) if id == TypeId::of::<Arc<RwLock<T>>>() => {
let ud =
try_self_arg!(get_userdata_mut::<Arc<RwLock<T>>>(ref_thread, index));
let mut ud = try_self_arg!(ud.try_write(), Error::UserDataBorrowMutError);
let ud = std::mem::transmute::<&mut T, &mut T>(&mut ud);
let args = A::from_lua_multi_args(args, 2, Some(&name), lua)?;
method(lua, ud, args).await?.into_lua_multi(lua)
},
method(lua, ud, args?).await?.push_into_stack_multi(lua)
}
#[cfg(feature = "parking_lot")]
Some(id) if id == TypeId::of::<Arc<parking_lot::RwLock<T>>>() => unsafe {
Some(id) if id == TypeId::of::<Arc<parking_lot::RwLock<T>>>() => {
let ud = get_userdata_mut::<Arc<parking_lot::RwLock<T>>>(ref_thread, index);
let ud = try_self_arg!(ud);
let mut ud =
try_self_arg!(ud.try_write().ok_or(Error::UserDataBorrowMutError));
let ud = std::mem::transmute::<&mut T, &mut T>(&mut ud);
let args = A::from_lua_multi_args(args, 2, Some(&name), lua)?;
method(lua, ud, args).await?.into_lua_multi(lua)
},
method(lua, ud, args?).await?.push_into_stack_multi(lua)
}
_ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)),
}
})
@@ -423,8 +401,9 @@ impl<'lua, T: 'static> UserDataRegistry<'lua, T> {
R: IntoLuaMulti<'lua>,
{
let name = get_function_name::<T>(name);
Box::new(move |lua, args| {
function(lua, A::from_lua_multi_args(args, 1, Some(&name), lua)?)?.into_lua_multi(lua)
Box::new(move |lua, nargs| unsafe {
let args = A::from_stack_args(nargs, 1, Some(&name), lua)?;
function(lua, args)?.push_into_stack_multi(lua)
})
}
@@ -436,11 +415,12 @@ impl<'lua, T: 'static> UserDataRegistry<'lua, T> {
{
let name = get_function_name::<T>(name);
let function = RefCell::new(function);
Box::new(move |lua, args| {
Box::new(move |lua, nargs| unsafe {
let function = &mut *function
.try_borrow_mut()
.map_err(|_| Error::RecursiveMutCallback)?;
function(lua, A::from_lua_multi_args(args, 1, Some(&name), lua)?)?.into_lua_multi(lua)
let args = A::from_stack_args(nargs, 1, Some(&name), lua)?;
function(lua, args)?.push_into_stack_multi(lua)
})
}
@@ -453,22 +433,17 @@ impl<'lua, T: 'static> UserDataRegistry<'lua, T> {
R: IntoLuaMulti<'lua>,
{
let name = get_function_name::<T>(name);
Box::new(move |lua, args| {
let args = match A::from_lua_multi_args(args, 1, Some(&name), lua) {
Box::new(move |lua, args| unsafe {
let args = match A::from_lua_args(args, 1, Some(&name), lua) {
Ok(args) => args,
Err(e) => return Box::pin(future::err(e)),
};
Box::pin(
function(lua, args).and_then(move |ret| future::ready(ret.into_lua_multi(lua))),
)
let fut = function(lua, args);
Box::pin(async move { fut.await?.push_into_stack_multi(lua) })
})
}
pub(crate) fn check_meta_field<V>(
lua: &'lua Lua,
name: &str,
value: V,
) -> Result<MultiValue<'lua>>
pub(crate) fn check_meta_field<V>(lua: &'lua Lua, name: &str, value: V) -> Result<Value<'lua>>
where
V: IntoLua<'lua>,
{
@@ -485,7 +460,7 @@ impl<'lua, T: 'static> UserDataRegistry<'lua, T> {
}
}
}
value.into_lua_multi(lua)
value.into_lua(lua)
}
}
@@ -502,7 +477,7 @@ impl<'lua, T: 'static> UserDataFields<'lua, T> for UserDataRegistry<'lua, T> {
let name = name.as_ref().to_string();
self.fields.push((
name,
Box::new(move |lua, _| value.clone().into_lua_multi(lua)),
Box::new(move |lua, _| unsafe { value.clone().push_into_stack_multi(lua) }),
));
}
@@ -554,7 +529,9 @@ impl<'lua, T: 'static> UserDataFields<'lua, T> for UserDataRegistry<'lua, T> {
let name2 = name.clone();
self.meta_fields.push((
name,
Box::new(move |lua, _| Self::check_meta_field(lua, &name2, value.clone())),
Box::new(move |lua, _| unsafe {
Self::check_meta_field(lua, &name2, value.clone())?.push_into_stack_multi(lua)
}),
));
}
@@ -567,7 +544,9 @@ impl<'lua, T: 'static> UserDataFields<'lua, T> for UserDataRegistry<'lua, T> {
let name2 = name.clone();
self.meta_fields.push((
name,
Box::new(move |lua, _| Self::check_meta_field(lua, &name2, f(lua)?)),
Box::new(move |lua, _| unsafe {
Self::check_meta_field(lua, &name2, f(lua)?)?.push_into_stack_multi(lua)
}),
));
}
+18 -32
View File
@@ -6,7 +6,7 @@ use std::mem::MaybeUninit;
use std::os::raw::{c_char, c_int, c_void};
use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe};
use std::sync::Arc;
use std::{mem, ptr, slice, str};
use std::{ptr, slice, str};
use once_cell::sync::Lazy;
use rustc_hash::FxHashMap;
@@ -63,6 +63,11 @@ impl StackGuard {
top: ffi::lua_gettop(state),
}
}
// Same as `new()`, but allows specifying the expected stack size at the end of the scope.
pub const fn with_top(state: *mut ffi::lua_State, top: c_int) -> StackGuard {
StackGuard { state, top }
}
}
impl Drop for StackGuard {
@@ -277,38 +282,23 @@ pub unsafe fn rawset_field(state: *mut ffi::lua_State, table: c_int, field: &str
}
// Internally uses 3 stack spaces, does not call checkstack.
#[cfg(not(feature = "luau"))]
#[inline]
pub unsafe fn push_userdata<T>(state: *mut ffi::lua_State, t: T, protect: bool) -> Result<()> {
#[cfg(not(feature = "luau"))]
let ud = if protect {
protect_lua!(state, 0, 1, |state| {
ffi::lua_newuserdata(state, mem::size_of::<T>()) as *mut T
ffi::lua_newuserdata(state, std::mem::size_of::<T>()) as *mut T
})?
} else {
ffi::lua_newuserdata(state, mem::size_of::<T>()) as *mut T
ffi::lua_newuserdata(state, std::mem::size_of::<T>()) as *mut T
};
ptr::write(ud, t);
Ok(())
}
// Internally uses 3 stack spaces, does not call checkstack.
#[cfg(feature = "luau")]
#[inline]
pub unsafe fn push_userdata<T>(state: *mut ffi::lua_State, t: T, protect: bool) -> Result<()> {
unsafe extern "C-unwind" fn destructor<T>(ud: *mut c_void) {
ptr::drop_in_place(ud as *mut T);
}
let size = mem::size_of::<T>();
#[cfg(feature = "luau")]
let ud = if protect {
protect_lua!(state, 0, 1, |state| {
ffi::lua_newuserdatadtor(state, size, destructor::<T>) as *mut T
})?
protect_lua!(state, 0, 1, |state| { ffi::lua_newuserdata_t::<T>(state) })?
} else {
ffi::lua_newuserdatadtor(state, size, destructor::<T>) as *mut T
ffi::lua_newuserdata_t::<T>(state)
};
ptr::write(ud, t);
Ok(())
}
@@ -323,10 +313,10 @@ pub unsafe fn push_userdata_uv<T>(
) -> Result<()> {
let ud = if protect {
protect_lua!(state, 0, 1, |state| {
ffi::lua_newuserdatauv(state, mem::size_of::<T>(), nuvalue) as *mut T
ffi::lua_newuserdatauv(state, std::mem::size_of::<T>(), nuvalue) as *mut T
})?
} else {
ffi::lua_newuserdatauv(state, mem::size_of::<T>(), nuvalue) as *mut T
ffi::lua_newuserdatauv(state, std::mem::size_of::<T>(), nuvalue) as *mut T
};
ptr::write(ud, t);
Ok(())
@@ -1004,16 +994,10 @@ pub(crate) enum WrappedFailure {
impl WrappedFailure {
pub(crate) unsafe fn new_userdata(state: *mut ffi::lua_State) -> *mut Self {
let size = mem::size_of::<WrappedFailure>();
#[cfg(feature = "luau")]
let ud = {
unsafe extern "C-unwind" fn destructor(p: *mut c_void) {
ptr::drop_in_place(p as *mut WrappedFailure);
}
ffi::lua_newuserdatadtor(state, size, destructor) as *mut Self
};
let ud = ffi::lua_newuserdata_t::<Self>(state);
#[cfg(not(feature = "luau"))]
let ud = ffi::lua_newuserdata(state, size) as *mut Self;
let ud = ffi::lua_newuserdata(state, std::mem::size_of::<Self>()) as *mut Self;
ptr::write(ud, WrappedFailure::None);
ud
}
@@ -1059,6 +1043,8 @@ pub(crate) unsafe fn to_string(state: *mut ffi::lua_State, index: c_int) -> Stri
ffi::LUA_TFUNCTION => format!("<function {:?}>", ffi::lua_topointer(state, index)),
ffi::LUA_TUSERDATA => format!("<userdata {:?}>", ffi::lua_topointer(state, index)),
ffi::LUA_TTHREAD => format!("<thread {:?}>", ffi::lua_topointer(state, index)),
#[cfg(feature = "luau")]
ffi::LUA_TBUFFER => format!("<buffer {:?}>", ffi::lua_topointer(state, index)),
_ => "<unknown>".to_string(),
}
}
+526 -61
View File
@@ -1,17 +1,21 @@
use std::borrow::Cow;
use std::cmp::Ordering;
use std::collections::HashSet;
use std::iter::{self, FromIterator};
use std::ops::Index;
use std::os::raw::c_void;
use std::os::raw::{c_int, c_void};
use std::string::String as StdString;
use std::sync::Arc;
use std::{fmt, ptr, slice, str, vec};
use std::{fmt, mem, ptr, slice, str, vec};
use num_traits::FromPrimitive;
#[cfg(feature = "serialize")]
use {
crate::table::SerializableTable,
rustc_hash::FxHashSet,
serde::ser::{self, Serialize, Serializer},
std::convert::TryInto,
std::result::Result as StdResult,
std::{cell::RefCell, convert::TryInto, rc::Rc, result::Result as StdResult},
};
use crate::error::{Error, Result};
@@ -20,7 +24,7 @@ use crate::lua::Lua;
use crate::string::String;
use crate::table::Table;
use crate::thread::Thread;
use crate::types::{Integer, LightUserData, Number};
use crate::types::{Integer, LightUserData, Number, SubtypeId};
use crate::userdata::AnyUserData;
use crate::util::{check_stack, StackGuard};
@@ -84,7 +88,11 @@ impl<'lua> Value<'lua> {
Value::Table(_) => "table",
Value::Function(_) => "function",
Value::Thread(_) => "thread",
Value::UserData(_) => "userdata",
Value::UserData(AnyUserData(_, SubtypeId::None)) => "userdata",
#[cfg(feature = "luau")]
Value::UserData(AnyUserData(_, SubtypeId::Buffer)) => "buffer",
#[cfg(feature = "luajit")]
Value::UserData(AnyUserData(_, SubtypeId::CData)) => "cdata",
Value::Error(_) => "error",
}
}
@@ -121,8 +129,8 @@ impl<'lua> Value<'lua> {
Value::String(String(r))
| Value::Table(Table(r))
| Value::Function(Function(r))
| Value::Thread(Thread(r))
| Value::UserData(AnyUserData(r)) => r.to_pointer(),
| Value::Thread(Thread(r, ..))
| Value::UserData(AnyUserData(r, ..)) => r.to_pointer(),
_ => ptr::null(),
}
}
@@ -143,8 +151,8 @@ impl<'lua> Value<'lua> {
Value::String(s) => Ok(s.to_str()?.to_string()),
Value::Table(Table(r))
| Value::Function(Function(r))
| Value::Thread(Thread(r))
| Value::UserData(AnyUserData(r)) => unsafe {
| Value::Thread(Thread(r, ..))
| Value::UserData(AnyUserData(r, ..)) => unsafe {
let state = r.lua.state();
let _guard = StackGuard::new(state);
check_stack(state, 3)?;
@@ -159,6 +167,285 @@ impl<'lua> Value<'lua> {
}
}
/// Returns `true` if the value is a [`Nil`].
#[inline]
pub fn is_nil(&self) -> bool {
self == &Nil
}
/// Returns `true` if the value is a [`NULL`].
#[inline]
pub fn is_null(&self) -> bool {
self == &Self::NULL
}
/// Returns `true` if the value is a boolean.
#[inline]
pub fn is_boolean(&self) -> bool {
self.as_boolean().is_some()
}
/// Cast the value to boolean.
///
/// If the value is a Boolean, returns it or `None` otherwise.
#[inline]
pub fn as_boolean(&self) -> Option<bool> {
match *self {
Value::Boolean(b) => Some(b),
_ => None,
}
}
/// Returns `true` if the value is a [`LightUserData`].
#[inline]
pub fn is_light_userdata(&self) -> bool {
self.as_light_userdata().is_some()
}
/// Cast the value to [`LightUserData`].
///
/// If the value is a [`LightUserData`], returns it or `None` otherwise.
#[inline]
pub fn as_light_userdata(&self) -> Option<LightUserData> {
match *self {
Value::LightUserData(l) => Some(l),
_ => None,
}
}
/// Returns `true` if the value is an [`Integer`].
#[inline]
pub fn is_integer(&self) -> bool {
self.as_integer().is_some()
}
/// Cast the value to [`Integer`].
///
/// If the value is a Lua [`Integer`], returns it or `None` otherwise.
#[inline]
pub fn as_integer(&self) -> Option<Integer> {
match *self {
Value::Integer(i) => Some(i),
_ => None,
}
}
/// Cast the value to `i32`.
///
/// If the value is a Lua [`Integer`], try to convert it to `i32` or return `None` otherwise.
#[inline]
pub fn as_i32(&self) -> Option<i32> {
#[allow(clippy::useless_conversion)]
self.as_integer().and_then(|i| i32::try_from(i).ok())
}
/// Cast the value to `u32`.
///
/// If the value is a Lua [`Integer`], try to convert it to `u32` or return `None` otherwise.
#[inline]
pub fn as_u32(&self) -> Option<u32> {
self.as_integer().and_then(|i| u32::try_from(i).ok())
}
/// Cast the value to `i64`.
///
/// If the value is a Lua [`Integer`], try to convert it to `i64` or return `None` otherwise.
#[inline]
pub fn as_i64(&self) -> Option<i64> {
#[allow(clippy::useless_conversion)]
self.as_integer().and_then(|i| i64::try_from(i).ok())
}
/// Cast the value to `u64`.
///
/// If the value is a Lua [`Integer`], try to convert it to `u64` or return `None` otherwise.
#[inline]
pub fn as_u64(&self) -> Option<u64> {
self.as_integer().and_then(|i| u64::try_from(i).ok())
}
/// Cast the value to `isize`.
///
/// If the value is a Lua [`Integer`], try to convert it to `isize` or return `None` otherwise.
#[inline]
pub fn as_isize(&self) -> Option<isize> {
self.as_integer().and_then(|i| isize::try_from(i).ok())
}
/// Cast the value to `usize`.
///
/// If the value is a Lua [`Integer`], try to convert it to `usize` or return `None` otherwise.
#[inline]
pub fn as_usize(&self) -> Option<usize> {
self.as_integer().and_then(|i| usize::try_from(i).ok())
}
/// Returns `true` if the value is a Lua [`Number`].
#[inline]
pub fn is_number(&self) -> bool {
self.as_number().is_some()
}
/// Cast the value to [`Number`].
///
/// If the value is a Lua [`Number`], returns it or `None` otherwise.
#[inline]
pub fn as_number(&self) -> Option<Number> {
match *self {
Value::Number(n) => Some(n),
_ => None,
}
}
/// Cast the value to `f32`.
///
/// If the value is a Lua [`Number`], try to convert it to `f32` or return `None` otherwise.
#[inline]
pub fn as_f32(&self) -> Option<f32> {
self.as_number().and_then(f32::from_f64)
}
/// Cast the value to `f64`.
///
/// If the value is a Lua [`Number`], try to convert it to `f64` or return `None` otherwise.
#[inline]
pub fn as_f64(&self) -> Option<f64> {
self.as_number()
}
/// Returns `true` if the value is a Lua [`String`].
#[inline]
pub fn is_string(&self) -> bool {
self.as_string().is_some()
}
/// Cast the value to Lua [`String`].
///
/// If the value is a Lua [`String`], returns it or `None` otherwise.
#[inline]
pub fn as_string(&self) -> Option<&String> {
match self {
Value::String(s) => Some(s),
_ => None,
}
}
/// Cast the value to [`str`].
///
/// If the value is a Lua [`String`], try to convert it to [`str`] or return `None` otherwise.
#[inline]
pub fn as_str(&self) -> Option<&str> {
self.as_string().and_then(|s| s.to_str().ok())
}
/// Cast the value to [`Cow<str>`].
///
/// If the value is a Lua [`String`], converts it to [`Cow<str>`] or returns `None` otherwise.
#[inline]
pub fn as_string_lossy(&self) -> Option<Cow<str>> {
self.as_string().map(|s| s.to_string_lossy())
}
/// Returns `true` if the value is a Lua [`Table`].
#[inline]
pub fn is_table(&self) -> bool {
self.as_table().is_some()
}
/// Cast the value to [`Table`].
///
/// If the value is a Lua [`Table`], returns it or `None` otherwise.
#[inline]
pub fn as_table(&self) -> Option<&Table> {
match self {
Value::Table(t) => Some(t),
_ => None,
}
}
/// Returns `true` if the value is a Lua [`Thread`].
#[inline]
pub fn is_thread(&self) -> bool {
self.as_thread().is_some()
}
/// Cast the value to [`Thread`].
///
/// If the value is a Lua [`Thread`], returns it or `None` otherwise.
#[inline]
pub fn as_thread(&self) -> Option<&Thread> {
match self {
Value::Thread(t) => Some(t),
_ => None,
}
}
/// Returns `true` if the value is a Lua [`Function`].
#[inline]
pub fn is_function(&self) -> bool {
self.as_function().is_some()
}
/// Cast the value to [`Function`].
///
/// If the value is a Lua [`Function`], returns it or `None` otherwise.
#[inline]
pub fn as_function(&self) -> Option<&Function> {
match self {
Value::Function(f) => Some(f),
_ => None,
}
}
/// Returns `true` if the value is an [`AnyUserData`].
#[inline]
pub fn is_userdata(&self) -> bool {
self.as_userdata().is_some()
}
/// Cast the value to [`AnyUserData`].
///
/// If the value is an [`AnyUserData`], returns it or `None` otherwise.
#[inline]
pub fn as_userdata(&self) -> Option<&AnyUserData> {
match self {
Value::UserData(ud) => Some(ud),
_ => None,
}
}
/// Returns `true` if the value is a Buffer wrapped in [`AnyUserData`].
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
#[doc(hidden)]
#[inline]
pub fn is_buffer(&self) -> bool {
self.as_userdata()
.map(|ud| ud.1 == SubtypeId::Buffer)
.unwrap_or_default()
}
/// Returns `true` if the value is a CData wrapped in [`AnyUserData`].
#[cfg(any(feature = "luajit", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luajit")))]
#[doc(hidden)]
#[inline]
pub fn is_cdata(&self) -> bool {
self.as_userdata()
.map(|ud| ud.1 == SubtypeId::CData)
.unwrap_or_default()
}
/// Wrap reference to this Value into [`SerializableValue`].
///
/// This allows customizing serialization behavior using serde.
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
#[doc(hidden)]
pub fn to_serializable(&self) -> SerializableValue<'_, 'lua> {
SerializableValue::new(self, Default::default(), None)
}
// Compares two values.
// Used to sort values for Debug printing.
pub(crate) fn cmp(&self, other: &Self) -> Ordering {
@@ -289,13 +576,86 @@ impl<'lua> AsRef<Value<'lua>> for Value<'lua> {
}
}
/// A wrapped [`Value`] with customized serialization behavior.
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
pub struct SerializableValue<'a, 'lua> {
value: &'a Value<'lua>,
options: crate::serde::de::Options,
// In many cases we don't need `visited` map, so don't allocate memory by default
visited: Option<Rc<RefCell<FxHashSet<*const c_void>>>>,
}
#[cfg(feature = "serialize")]
impl<'lua> Serialize for Value<'lua> {
#[inline]
fn serialize<S: Serializer>(&self, serializer: S) -> StdResult<S::Ok, S::Error> {
SerializableValue::new(self, Default::default(), None).serialize(serializer)
}
}
#[cfg(feature = "serialize")]
impl<'a, 'lua> SerializableValue<'a, 'lua> {
#[inline]
pub(crate) fn new(
value: &'a Value<'lua>,
options: crate::serde::de::Options,
visited: Option<&Rc<RefCell<FxHashSet<*const c_void>>>>,
) -> Self {
if let Value::Table(_) = value {
return Self {
value,
options,
// We need to always initialize the `visited` map for Tables
visited: visited.cloned().or_else(|| Some(Default::default())),
};
}
Self {
value,
options,
visited: None,
}
}
/// If true, an attempt to serialize types such as [`Function`], [`Thread`], [`LightUserData`]
/// and [`Error`] will cause an error.
/// Otherwise these types skipped when iterating or serialized as unit type.
///
/// Default: **true**
#[must_use]
pub const fn deny_unsupported_types(mut self, enabled: bool) -> Self {
self.options.deny_unsupported_types = enabled;
self
}
/// If true, an attempt to serialize a recursive table (table that refers to itself)
/// will cause an error.
/// Otherwise subsequent attempts to serialize the same table will be ignored.
///
/// Default: **true**
#[must_use]
pub const fn deny_recursive_tables(mut self, enabled: bool) -> Self {
self.options.deny_recursive_tables = enabled;
self
}
/// If true, keys in tables will be iterated (and serialized) in sorted order.
///
/// Default: **false**
#[must_use]
pub const fn sort_keys(mut self, enabled: bool) -> Self {
self.options.sort_keys = enabled;
self
}
}
#[cfg(feature = "serialize")]
impl<'a, 'lua> Serialize for SerializableValue<'a, 'lua> {
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
where
S: Serializer,
{
match self {
match self.value {
Value::Nil => serializer.serialize_unit(),
Value::Boolean(b) => serializer.serialize_bool(*b),
#[allow(clippy::useless_conversion)]
@@ -305,21 +665,44 @@ impl<'lua> Serialize for Value<'lua> {
#[cfg(feature = "luau")]
Value::Vector(v) => v.serialize(serializer),
Value::String(s) => s.serialize(serializer),
Value::Table(t) => t.serialize(serializer),
Value::UserData(ud) => ud.serialize(serializer),
Value::Table(t) => {
let visited = self.visited.as_ref().unwrap().clone();
SerializableTable::new(t, self.options, visited).serialize(serializer)
}
Value::LightUserData(ud) if ud.0.is_null() => serializer.serialize_none(),
Value::Error(_) | Value::LightUserData(_) | Value::Function(_) | Value::Thread(_) => {
let msg = format!("cannot serialize <{}>", self.type_name());
Err(ser::Error::custom(msg))
Value::UserData(ud) if ud.is_serializable() || self.options.deny_unsupported_types => {
ud.serialize(serializer)
}
Value::Function(_)
| Value::Thread(_)
| Value::UserData(_)
| Value::LightUserData(_)
| Value::Error(_) => {
if self.options.deny_unsupported_types {
let msg = format!("cannot serialize <{}>", self.value.type_name());
Err(ser::Error::custom(msg))
} else {
serializer.serialize_unit()
}
}
}
}
}
/// Trait for types convertible to `Value`.
pub trait IntoLua<'lua> {
pub trait IntoLua<'lua>: Sized {
/// Performs the conversion.
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>>;
/// Pushes the value into the Lua stack.
///
/// # Safety
/// This method does not check Lua stack space.
#[doc(hidden)]
#[inline]
unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> {
lua.push_value(self.into_lua(lua)?)
}
}
/// Trait for types convertible from `Value`.
@@ -332,13 +715,33 @@ pub trait FromLua<'lua>: Sized {
/// `i` is the argument index (position),
/// `to` is a function name that received the argument.
#[doc(hidden)]
fn from_lua_arg(
value: Value<'lua>,
#[inline]
fn from_lua_arg(arg: Value<'lua>, i: usize, to: Option<&str>, lua: &'lua Lua) -> Result<Self> {
Self::from_lua(arg, lua).map_err(|err| Error::BadArgument {
to: to.map(|s| s.to_string()),
pos: i,
name: None,
cause: Arc::new(err),
})
}
/// Performs the conversion for a value in the Lua stack at index `idx`.
#[doc(hidden)]
#[inline]
unsafe fn from_stack(idx: c_int, lua: &'lua Lua) -> Result<Self> {
Self::from_lua(lua.stack_value(idx), lua)
}
/// Same as `from_lua_arg` but for a value in the Lua stack at index `idx`.
#[doc(hidden)]
#[inline]
unsafe fn from_stack_arg(
idx: c_int,
i: usize,
to: Option<&str>,
lua: &'lua Lua,
) -> Result<Self> {
Self::from_lua(value, lua).map_err(|err| Error::BadArgument {
Self::from_stack(idx, lua).map_err(|err| Error::BadArgument {
to: to.map(|s| s.to_string()),
pos: i,
name: None,
@@ -349,24 +752,43 @@ pub trait FromLua<'lua>: Sized {
/// Multiple Lua values used for both argument passing and also for multiple return values.
#[derive(Debug, Clone)]
pub struct MultiValue<'lua>(Vec<Value<'lua>>);
pub struct MultiValue<'lua> {
vec: Vec<Value<'lua>>,
lua: Option<&'lua Lua>,
}
impl Drop for MultiValue<'_> {
fn drop(&mut self) {
if let Some(lua) = self.lua {
let vec = mem::take(&mut self.vec);
lua.push_multivalue_to_pool(vec);
}
}
}
impl<'lua> MultiValue<'lua> {
/// Creates an empty `MultiValue` containing no values.
pub const fn new() -> MultiValue<'lua> {
MultiValue(Vec::new())
MultiValue {
vec: Vec::new(),
lua: None,
}
}
/// Similar to `new` but can return previously used container with allocated capacity.
/// Similar to `new` but can reuse previously used container with allocated capacity.
#[inline]
pub(crate) fn new_or_pooled(lua: &'lua Lua) -> MultiValue<'lua> {
lua.new_multivalue_from_pool()
}
/// Clears and returns previously allocated multivalue container to the pool.
#[inline]
pub(crate) fn return_to_pool(multivalue: Self, lua: &Lua) {
lua.return_multivalue_to_pool(multivalue);
pub(crate) fn with_lua_and_capacity(lua: &'lua Lua, capacity: usize) -> MultiValue<'lua> {
let vec = lua
.pop_multivalue_from_pool()
.map(|mut vec| {
vec.reserve(capacity);
vec
})
.unwrap_or_else(|| Vec::with_capacity(capacity));
MultiValue {
vec,
lua: Some(lua),
}
}
}
@@ -389,8 +811,10 @@ impl<'lua> IntoIterator for MultiValue<'lua> {
type IntoIter = iter::Rev<vec::IntoIter<Value<'lua>>>;
#[inline]
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter().rev()
fn into_iter(mut self) -> Self::IntoIter {
let vec = mem::take(&mut self.vec);
mem::forget(self);
vec.into_iter().rev()
}
}
@@ -400,7 +824,7 @@ impl<'a, 'lua> IntoIterator for &'a MultiValue<'lua> {
#[inline]
fn into_iter(self) -> Self::IntoIter {
self.0.iter().rev()
self.vec.iter().rev()
}
}
@@ -423,64 +847,60 @@ impl<'lua> Index<usize> for MultiValue<'lua> {
impl<'lua> MultiValue<'lua> {
#[inline]
pub fn from_vec(mut v: Vec<Value<'lua>>) -> MultiValue<'lua> {
v.reverse();
MultiValue(v)
pub fn from_vec(mut vec: Vec<Value<'lua>>) -> MultiValue<'lua> {
vec.reverse();
MultiValue { vec, lua: None }
}
#[inline]
pub fn into_vec(self) -> Vec<Value<'lua>> {
let mut v = self.0;
v.reverse();
v
pub fn into_vec(mut self) -> Vec<Value<'lua>> {
let mut vec = mem::take(&mut self.vec);
mem::forget(self);
vec.reverse();
vec
}
#[inline]
pub fn get(&self, index: usize) -> Option<&Value<'lua>> {
if index < self.0.len() {
return self.0.get(self.0.len() - index - 1);
if index < self.vec.len() {
return self.vec.get(self.vec.len() - index - 1);
}
None
}
#[inline]
pub(crate) fn reserve(&mut self, size: usize) {
self.0.reserve(size);
}
#[inline]
pub fn pop_front(&mut self) -> Option<Value<'lua>> {
self.0.pop()
self.vec.pop()
}
#[inline]
pub fn push_front(&mut self, value: Value<'lua>) {
self.0.push(value);
self.vec.push(value);
}
#[inline]
pub fn clear(&mut self) {
self.0.clear();
self.vec.clear();
}
#[inline]
pub fn len(&self) -> usize {
self.0.len()
self.vec.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.0.is_empty()
self.vec.is_empty()
}
#[inline]
pub fn iter(&self) -> iter::Rev<slice::Iter<Value<'lua>>> {
self.0.iter().rev()
self.vec.iter().rev()
}
#[inline]
pub(crate) fn drain_all(&mut self) -> iter::Rev<vec::Drain<Value<'lua>>> {
self.0.drain(..).rev()
self.vec.drain(..).rev()
}
#[inline]
@@ -488,11 +908,11 @@ impl<'lua> MultiValue<'lua> {
&mut self,
iter: impl IntoIterator<Item = Result<Value<'lua>>>,
) -> Result<()> {
self.0.clear();
self.vec.clear();
for value in iter {
self.0.push(value?);
self.vec.push(value?);
}
self.0.reverse();
self.vec.reverse();
Ok(())
}
}
@@ -501,9 +921,26 @@ impl<'lua> MultiValue<'lua> {
///
/// This is a generalization of `IntoLua`, allowing any number of resulting Lua values instead of just
/// one. Any type that implements `IntoLua` will automatically implement this trait.
pub trait IntoLuaMulti<'lua> {
pub trait IntoLuaMulti<'lua>: Sized {
/// Performs the conversion.
fn into_lua_multi(self, lua: &'lua Lua) -> Result<MultiValue<'lua>>;
/// Pushes the values into the Lua stack.
///
/// Returns number of pushed values.
#[doc(hidden)]
#[inline]
unsafe fn push_into_stack_multi(self, lua: &'lua Lua) -> Result<c_int> {
let mut values = self.into_lua_multi(lua)?;
let len: c_int = values.len().try_into().unwrap();
unsafe {
check_stack(lua.state(), len + 1)?;
for v in values.drain_all() {
lua.push_value(v)?;
}
}
Ok(len)
}
}
/// Trait for types that can be created from an arbitrary number of Lua values.
@@ -525,15 +962,43 @@ pub trait FromLuaMulti<'lua>: Sized {
/// `to` is a function name that received the arguments.
#[doc(hidden)]
#[inline]
fn from_lua_multi_args(
values: MultiValue<'lua>,
fn from_lua_args(
args: MultiValue<'lua>,
i: usize,
to: Option<&str>,
lua: &'lua Lua,
) -> Result<Self> {
let _ = (i, to);
Self::from_lua_multi(args, lua)
}
/// Performs the conversion for a number of values in the Lua stack.
#[doc(hidden)]
#[inline]
unsafe fn from_stack_multi(nvals: c_int, lua: &'lua Lua) -> Result<Self> {
let mut values = MultiValue::with_lua_and_capacity(lua, nvals as usize);
for idx in 1..=nvals {
values.push_front(lua.stack_value(-idx));
}
if nvals > 0 {
// It's safe to clear the stack as all references moved to ref thread
ffi::lua_pop(lua.state(), nvals);
}
Self::from_lua_multi(values, lua)
}
/// Same as `from_lua_args` but for a number of values in the Lua stack.
#[doc(hidden)]
#[inline]
unsafe fn from_stack_args(
nargs: c_int,
i: usize,
to: Option<&str>,
lua: &'lua Lua,
) -> Result<Self> {
let _ = (i, to);
Self::from_stack_multi(nargs, lua)
}
}
#[cfg(test)]
+28 -2
View File
@@ -6,14 +6,21 @@ use std::time::Duration;
use futures_util::stream::TryStreamExt;
use mlua::{
AnyUserDataExt, Error, Function, Lua, LuaOptions, Result, StdLib, Table, TableExt, UserData,
UserDataMethods, Value,
AnyUserDataExt, Error, Function, Lua, LuaOptions, MultiValue, Result, StdLib, Table, TableExt,
UserData, UserDataMethods, Value,
};
#[cfg(not(target_arch = "wasm32"))]
async fn sleep_ms(ms: u64) {
tokio::time::sleep(Duration::from_millis(ms)).await;
}
#[cfg(target_arch = "wasm32")]
async fn sleep_ms(_ms: u64) {
// I was unable to make sleep() work in wasm32-emscripten target
tokio::task::yield_now().await;
}
#[tokio::test]
async fn test_async_function() -> Result<()> {
let lua = Lua::new();
@@ -82,6 +89,25 @@ async fn test_async_call() -> Result<()> {
Ok(())
}
#[tokio::test]
async fn test_async_call_many_returns() -> Result<()> {
let lua = Lua::new();
let hello = lua.create_async_function(|_lua, ()| async move {
sleep_ms(10).await;
Ok(("a", "b", "c", 1))
})?;
let vals = hello.call_async::<_, MultiValue>(()).await?;
assert_eq!(vals.len(), 4);
assert_eq!(vals[0].to_string()?, "a");
assert_eq!(vals[1].to_string()?, "b");
assert_eq!(vals[2].to_string()?, "c");
assert_eq!(vals[3], Value::Integer(1));
Ok(())
}
#[tokio::test]
async fn test_async_bind_call() -> Result<()> {
let lua = Lua::new();
+9 -3
View File
@@ -1,12 +1,18 @@
use std::fs;
use std::io;
use mlua::{AnyUserData, Function, Lua, Result};
use mlua::{Lua, Result};
#[test]
fn test_chunk_path() -> Result<()> {
let lua = Lua::new();
if cfg!(target_arch = "wasm32") {
// TODO: figure out why emscripten fails on file operations
// Also see https://github.com/rust-lang/rust/issues/119250
return Ok(());
}
let temp_dir = tempfile::tempdir().unwrap();
fs::write(
temp_dir.path().join("module.lua"),
@@ -36,8 +42,8 @@ fn test_chunk_macro() -> Result<()> {
let data = lua.create_table()?;
data.raw_set("num", 1)?;
let ud = AnyUserData::wrap("hello");
let f = Function::wrap(|_lua, ()| Ok(()));
let ud = mlua::AnyUserData::wrap("hello");
let f = mlua::Function::wrap(|_lua, ()| Ok(()));
lua.globals().set("g", 123)?;
+3 -1
View File
@@ -41,7 +41,9 @@ fn test_error_context() -> Result<()> {
.context("some new context")
})?;
let res = func3.call::<_, ()>(()).err().unwrap();
let Error::CallbackError { cause, .. } = &res else { unreachable!() };
let Error::CallbackError { cause, .. } = &res else {
unreachable!()
};
assert!(!res.to_string().contains("some context"));
assert!(res.to_string().contains("some new context"));
assert!(cause.downcast_ref::<io::Error>().is_some());
+4 -2
View File
@@ -1,6 +1,5 @@
#![cfg(not(feature = "luau"))]
use std::cell::RefCell;
use std::ops::Deref;
use std::sync::atomic::{AtomicI64, Ordering};
use std::sync::{Arc, Mutex};
@@ -170,10 +169,13 @@ fn test_hook_removal() -> Result<()> {
Ok(())
}
// Having the code compiled (even not run) on macos and luajit causes a memory reference issue
// See https://github.com/LuaJIT/LuaJIT/issues/1099
#[cfg(not(all(feature = "luajit", target_os = "macos")))]
#[test]
fn test_hook_swap_within_hook() -> Result<()> {
thread_local! {
static TL_LUA: RefCell<Option<Lua>> = RefCell::new(None);
static TL_LUA: std::cell::RefCell<Option<Lua>> = Default::default();
}
TL_LUA.with(|tl| {
+110 -7
View File
@@ -1,6 +1,5 @@
#![cfg(feature = "luau")]
use std::env;
use std::fmt::Debug;
use std::fs;
use std::panic::{catch_unwind, AssertUnwindSafe};
@@ -8,7 +7,8 @@ use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use mlua::{
Compiler, CoverageInfo, Error, Lua, Result, Table, ThreadStatus, Value, Vector, VmState,
Compiler, CoverageInfo, Error, Lua, LuaOptions, Result, StdLib, Table, ThreadStatus, Value,
Vector, VmState,
};
#[test]
@@ -23,7 +23,18 @@ fn test_version() -> Result<()> {
#[test]
fn test_require() -> Result<()> {
let lua = Lua::new();
// Ensure that require() is not available if package module is not loaded
let mut lua = Lua::new_with(StdLib::NONE, LuaOptions::default())?;
assert!(lua.globals().get::<_, Option<Value>>("require")?.is_none());
assert!(lua.globals().get::<_, Option<Value>>("package")?.is_none());
if cfg!(target_arch = "wasm32") {
// TODO: figure out why emscripten fails on file operations
// Also see https://github.com/rust-lang/rust/issues/119250
return Ok(());
}
lua = Lua::new();
let temp_dir = tempfile::tempdir().unwrap();
fs::write(
@@ -37,7 +48,10 @@ fn test_require() -> Result<()> {
"#,
)?;
env::set_var("LUAU_PATH", temp_dir.path().join("?.luau"));
lua.globals()
.get::<_, Table>("package")?
.set("path", temp_dir.path().join("?.luau").to_string_lossy())?;
lua.load(
r#"
local module = require("module")
@@ -49,7 +63,28 @@ fn test_require() -> Result<()> {
assert(not ok and string.find(err, "module.luau") ~= nil)
"#,
)
.exec()
.exec()?;
// Require non-existent module
match lua.load("require('non-existent')").exec() {
Err(Error::RuntimeError(e)) if e.contains("module 'non-existent' not found") => {}
r => panic!("expected RuntimeError(...) with a specific message, got {r:?}"),
}
// Require binary module in safe mode
lua.globals()
.get::<_, Table>("package")?
.set("cpath", temp_dir.path().join("?.so").to_string_lossy())?;
fs::write(temp_dir.path().join("dylib.so"), "")?;
match lua.load("require('dylib')").exec() {
Err(Error::RuntimeError(e)) if cfg!(unix) && e.contains("module 'dylib' not found") => {
assert!(e.contains("dynamic libraries are disabled in safe mode"))
}
Err(Error::RuntimeError(e)) if e.contains("module 'dylib' not found") => {}
r => panic!("expected RuntimeError(...) with a specific message, got {r:?}"),
}
Ok(())
}
#[cfg(not(feature = "luau-vector4"))]
@@ -84,7 +119,7 @@ fn test_vectors() -> Result<()> {
assert(v.z == 3)
"#,
)
.set_compiler(Compiler::new().set_vector_ctor(Some("vector".to_string())))
.set_compiler(Compiler::new().set_vector_ctor("vector"))
.exec()?;
Ok(())
@@ -124,7 +159,49 @@ fn test_vectors() -> Result<()> {
assert(v.w == 4)
"#,
)
.set_compiler(Compiler::new().set_vector_ctor(Some("vector".to_string())))
.set_compiler(Compiler::new().set_vector_ctor("vector"))
.exec()?;
Ok(())
}
#[cfg(all(not(feature = "luau-vector4"), feature = "unstable"))]
#[test]
fn test_vector_metatable() -> Result<()> {
let lua = Lua::new();
let vector_mt = lua
.load(
r#"
{
__index = {
new = vector,
product = function(a, b)
return vector(a.x * b.x, a.y * b.y, a.z * b.z)
end
}
}
"#,
)
.eval::<Table>()?;
vector_mt.set_metatable(Some(vector_mt.clone()));
lua.set_vector_metatable(Some(vector_mt.clone()));
lua.globals().set("Vector3", vector_mt)?;
let compiler = Compiler::new()
.set_vector_lib("Vector3")
.set_vector_ctor("new");
// Test vector methods (fastcall)
lua.load(
r#"
local v = Vector3.new(1, 2, 3)
local v2 = v:product(Vector3.new(2, 3, 4))
assert(v2.x == 2 and v2.y == 6 and v2.z == 12)
"#,
)
.set_compiler(compiler)
.exec()?;
Ok(())
@@ -365,3 +442,29 @@ fn test_coverage() -> Result<()> {
Ok(())
}
#[test]
fn test_buffer() -> Result<()> {
let lua = Lua::new();
let buf1 = lua
.load(
r#"
local buf = buffer.fromstring("hello")
assert(buffer.len(buf) == 5)
return buf
"#,
)
.eval::<Value>()?;
assert!(buf1.is_userdata() && buf1.is_buffer());
assert_eq!(buf1.type_name(), "buffer");
let buf2 = lua.load("buffer.fromstring('hello')").eval::<Value>()?;
assert_ne!(buf1, buf2);
// Check that we can pass buffer type to Lua
let func = lua.create_function(|_, buf: Value| return buf.to_string())?;
assert!(func.call::<_, String>(buf1)?.starts_with("buffer:"));
Ok(())
}
+1
View File
@@ -18,6 +18,7 @@ lua53 = ["mlua/lua53"]
lua52 = ["mlua/lua52"]
lua51 = ["mlua/lua51"]
luajit = ["mlua/luajit"]
luau = ["mlua/luau"]
[dependencies]
mlua = { path = "../..", features = ["module"] }
+1
View File
@@ -10,6 +10,7 @@ lua53 = ["mlua/lua53"]
lua52 = ["mlua/lua52"]
lua51 = ["mlua/lua51"]
luajit = ["mlua/luajit"]
luau = ["mlua/luau"]
vendored = ["mlua/vendored"]
[dependencies]
+1 -1
View File
@@ -4,7 +4,7 @@ use std::path::PathBuf;
use mlua::{Lua, Result};
#[test]
fn test_module() -> Result<()> {
fn test_module_simple() -> Result<()> {
let lua = make_lua()?;
lua.load(
r#"
+42 -3
View File
@@ -1,5 +1,6 @@
use std::cell::Cell;
use std::rc::Rc;
use std::string::String as StdString;
use std::sync::Arc;
use mlua::{
@@ -76,6 +77,7 @@ fn test_scope_userdata_fields() -> Result<()> {
impl<'a> UserData for MyUserData<'a> {
fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_field("field", "hello");
fields.add_field_method_get("val", |_, data| Ok(data.0.get()));
fields.add_field_method_set("val", |_, data, val| {
data.0.set(val);
@@ -91,6 +93,7 @@ fn test_scope_userdata_fields() -> Result<()> {
.load(
r#"
function(u)
assert(u.field == "hello")
assert(u.val == 42)
u.val = 44
end
@@ -227,9 +230,19 @@ fn test_scope_userdata_mismatch() -> Result<()> {
let bu = scope.create_nonstatic_userdata(MyUserData(&b))?;
assert!(okay.call::<_, ()>((au.clone(), bu.clone())).is_ok());
match bad.call::<_, ()>((au, bu)) {
Err(Error::CallbackError { ref cause, .. }) => match *cause.as_ref() {
Error::UserDataTypeMismatch => {}
ref other => panic!("wrong error type {:?}", other),
Err(Error::CallbackError { ref cause, .. }) => match cause.as_ref() {
Error::BadArgument {
to,
pos,
name,
cause,
} => {
assert_eq!(to.as_deref(), Some("MyUserData.inc"));
assert_eq!(*pos, 1);
assert_eq!(name.as_deref(), Some("self"));
assert!(matches!(*cause.as_ref(), Error::UserDataTypeMismatch));
}
other => panic!("wrong error type {:?}", other),
},
Err(other) => panic!("wrong error type {:?}", other),
Ok(_) => panic!("incorrectly returned Ok"),
@@ -417,6 +430,32 @@ fn test_scope_userdata_ref_mut() -> Result<()> {
Ok(())
}
#[test]
fn test_scope_any_userdata() -> Result<()> {
let lua = Lua::new();
lua.register_userdata_type::<StdString>(|reg| {
reg.add_meta_method("__tostring", |_, data, ()| Ok(data.clone()));
})?;
lua.scope(|scope| {
let ud = scope.create_any_userdata(StdString::from("foo"))?;
lua.globals().set("ud", ud)?;
lua.load("assert(tostring(ud) == 'foo')").exec()
})?;
// Check that userdata is destructed
match lua.load("tostring(ud)").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(())
}
#[test]
fn test_scope_any_userdata_ref() -> Result<()> {
let lua = Lua::new();
+88 -2
View File
@@ -4,8 +4,8 @@ use std::collections::HashMap;
use std::error::Error as StdError;
use mlua::{
DeserializeOptions, Error, Lua, LuaSerdeExt, Result as LuaResult, SerializeOptions, UserData,
Value,
DeserializeOptions, Error, ExternalResult, Lua, LuaSerdeExt, Result as LuaResult,
SerializeOptions, UserData, Value,
};
use serde::{Deserialize, Serialize};
@@ -191,6 +191,69 @@ fn test_serialize_vector() -> Result<(), Box<dyn StdError>> {
Ok(())
}
#[test]
fn test_serialize_sorted() -> LuaResult<()> {
let lua = Lua::new();
let globals = lua.globals();
globals.set("null", lua.null())?;
let empty_array = lua.create_table()?;
empty_array.set_metatable(Some(lua.array_metatable()));
globals.set("empty_array", empty_array)?;
let value = lua
.load(
r#"
{
_bool = true,
_integer = 123,
_number = 321.99,
_string = "test string serialization",
_table_arr = {nil, "value 1", nil, "value 2", {}},
_table_map = {["table"] = "map", ["null"] = null},
_bytes = "\240\040\140\040",
_null = null,
_empty_map = {},
_empty_array = empty_array,
}
"#,
)
.eval::<Value>()?;
let json = serde_json::to_string(&value.to_serializable().sort_keys(true)).unwrap();
assert_eq!(
json,
r#"{"_bool":true,"_bytes":[240,40,140,40],"_empty_array":[],"_empty_map":{},"_integer":123,"_null":null,"_number":321.99,"_string":"test string serialization","_table_arr":[null,"value 1",null,"value 2",{}],"_table_map":{"null":null,"table":"map"}}"#
);
Ok(())
}
#[test]
fn test_serialize_globals() -> LuaResult<()> {
let lua = Lua::new();
let globals = Value::Table(lua.globals());
// By default it should not work
if let Ok(v) = serde_json::to_value(&globals) {
panic!("expected serialization error, got {v:?}");
}
// It should work with `deny_recursive_tables` and `deny_unsupported_types` disabled
if let Err(err) = serde_json::to_value(
globals
.to_serializable()
.deny_recursive_tables(false)
.deny_unsupported_types(false),
) {
panic!("expected no errors, got {err:?}");
}
Ok(())
}
#[test]
fn test_to_value_struct() -> LuaResult<()> {
let lua = Lua::new();
@@ -611,3 +674,26 @@ fn test_from_value_userdata() -> Result<(), Box<dyn StdError>> {
Ok(())
}
#[test]
fn test_from_value_sorted() -> Result<(), Box<dyn StdError>> {
let lua = Lua::new();
let to_json = lua.create_function(|lua, value| {
let json_value: serde_json::Value =
lua.from_value_with(value, DeserializeOptions::new().sort_keys(true))?;
serde_json::to_string(&json_value).into_lua_err()
})?;
lua.globals().set("to_json", to_json)?;
lua.load(
r#"
local json = to_json({c = 3, b = 2, hello = "world", x = {1}, ["0a"] = {z = "z", d = "d"}})
assert(json == '{"0a":{"d":"d","z":"z"},"b":2,"c":3,"hello":"world","x":[1]}', "invalid json")
"#,
)
.exec()
.unwrap();
Ok(())
}
+11 -2
View File
@@ -73,13 +73,22 @@ fn test_static_lua_coroutine() -> Result<()> {
async fn test_static_async() -> Result<()> {
let lua = Lua::new().into_static();
#[cfg(not(target_arch = "wasm32"))]
async fn sleep_ms(ms: u64) {
tokio::time::sleep(std::time::Duration::from_millis(ms)).await;
}
#[cfg(target_arch = "wasm32")]
async fn sleep_ms(_ms: u64) {
tokio::task::yield_now().await;
}
let timer =
lua.create_async_function(|_, (i, n, f): (u64, u64, mlua::Function)| async move {
tokio::task::spawn_local(async move {
let dur = std::time::Duration::from_millis(i);
for _ in 0..n {
tokio::task::spawn_local(f.call_async::<(), ()>(()));
tokio::time::sleep(dur).await;
sleep_ms(i).await;
}
});
Ok(())
+68
View File
@@ -217,6 +217,74 @@ fn test_table_sequence_from() -> Result<()> {
Ok(())
}
#[test]
fn test_table_pairs() -> Result<()> {
let lua = Lua::new();
let table = lua
.load(
r#"
{
foo = "bar",
baz = "baf",
[123] = 456,
[789] = 101112,
5,
}
"#,
)
.eval::<Table>()?;
let table2 = table.clone();
for (i, kv) in table.pairs::<String, Value>().enumerate() {
let (k, _v) = kv.unwrap();
match i {
// Try to add a new key
0 => table2.set("new_key", "new_value")?,
// Try to delete the 2nd key
1 => {
table2.set(k, Value::Nil)?;
lua.gc_collect()?;
}
_ => {}
}
}
Ok(())
}
#[test]
fn test_table_for_each() -> Result<()> {
let lua = Lua::new();
let table = lua
.load(
r#"
{
foo = "bar",
baz = "baf",
[123] = 456,
[789] = 101112,
5,
}
"#,
)
.eval::<Table>()?;
let mut i = 0;
table.for_each::<String, Value>(|k, _| {
if i == 0 {
// Delete first key
table.set(k, Value::Nil)?;
lua.gc_collect()?;
}
Ok(i += 1)
})?;
assert_eq!(i, 5);
Ok(())
}
#[test]
fn test_table_scope() -> Result<()> {
let lua = Lua::new();
+46 -22
View File
@@ -1,4 +1,5 @@
use std::collections::HashMap;
#[cfg(not(target_arch = "wasm32"))]
use std::iter::FromIterator;
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::string::String as StdString;
@@ -312,30 +313,29 @@ fn test_error() -> Result<()> {
globals.set("rust_error_function", rust_error_function)?;
let no_error = globals.get::<_, Function>("no_error")?;
let lua_error = globals.get::<_, Function>("lua_error")?;
let rust_error = globals.get::<_, Function>("rust_error")?;
let return_error = globals.get::<_, Function>("return_error")?;
let return_string_error = globals.get::<_, Function>("return_string_error")?;
let test_pcall = globals.get::<_, Function>("test_pcall")?;
let understand_recursion = globals.get::<_, Function>("understand_recursion")?;
assert!(no_error.call::<_, ()>(()).is_ok());
let lua_error = globals.get::<_, Function>("lua_error")?;
match lua_error.call::<_, ()>(()) {
Err(Error::RuntimeError(_)) => {}
Err(e) => panic!("error is not RuntimeError kind, got {:?}", e),
_ => panic!("error not returned"),
}
let rust_error = globals.get::<_, Function>("rust_error")?;
match rust_error.call::<_, ()>(()) {
Err(Error::CallbackError { .. }) => {}
Err(e) => panic!("error is not CallbackError kind, got {:?}", e),
_ => panic!("error not returned"),
}
let return_error = globals.get::<_, Function>("return_error")?;
match return_error.call::<_, Value>(()) {
Ok(Value::Error(_)) => {}
_ => panic!("Value::Error not returned"),
}
let return_string_error = globals.get::<_, Function>("return_string_error")?;
assert!(return_string_error.call::<_, Error>(()).is_ok());
match lua
@@ -358,9 +358,14 @@ fn test_error() -> Result<()> {
_ => panic!("error not returned"),
}
let test_pcall = globals.get::<_, Function>("test_pcall")?;
test_pcall.call::<_, ()>(())?;
assert!(understand_recursion.call::<_, ()>(()).is_err());
#[cfg(not(target_arch = "wasm32"))]
{
let understand_recursion = globals.get::<_, Function>("understand_recursion")?;
assert!(understand_recursion.call::<_, ()>(()).is_err());
}
Ok(())
}
@@ -504,25 +509,32 @@ fn test_result_conversions() -> Result<()> {
let lua = Lua::new();
let globals = lua.globals();
let err = lua.create_function(|_, ()| {
Ok(Err::<String, _>(
"only through failure can we succeed".into_lua_err(),
))
})?;
let ok = lua.create_function(|_, ()| Ok(Ok::<_, Error>("!".to_owned())))?;
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("err", 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("only through failure can we succeed") ~= nil)
assert(tostring(e):find("failure1") ~= nil)
local r, e = ok()
local r, e = ok2()
assert(r == "!")
assert(e == nil)
local r, e = err2()
assert(r == nil)
assert(tostring(e):find("failure2") ~= nil)
"#,
)
.exec()?;
@@ -940,6 +952,7 @@ fn test_application_data() -> Result<()> {
}
#[test]
#[cfg(not(target_arch = "wasm32"))]
fn test_recursion() -> Result<()> {
let lua = Lua::new();
@@ -959,14 +972,16 @@ fn test_recursion() -> Result<()> {
}
#[test]
#[cfg(not(target_arch = "wasm32"))]
fn test_too_many_returns() -> Result<()> {
let lua = Lua::new();
let f = lua.create_function(|_, ()| Ok(Variadic::from_iter(1..1000000)))?;
assert!(f.call::<_, Vec<u32>>(()).is_err());
assert!(f.call::<_, Variadic<u32>>(()).is_err());
Ok(())
}
#[test]
#[cfg(not(target_arch = "wasm32"))]
fn test_too_many_arguments() -> Result<()> {
let lua = Lua::new();
lua.load("function test(...) end").exec()?;
@@ -981,6 +996,7 @@ fn test_too_many_arguments() -> Result<()> {
#[test]
#[cfg(not(feature = "luajit"))]
#[cfg(not(target_arch = "wasm32"))]
fn test_too_many_recursions() -> Result<()> {
let lua = Lua::new();
@@ -994,6 +1010,7 @@ fn test_too_many_recursions() -> Result<()> {
}
#[test]
#[cfg(not(target_arch = "wasm32"))]
fn test_too_many_binds() -> Result<()> {
let lua = Lua::new();
let globals = lua.globals();
@@ -1015,6 +1032,7 @@ fn test_too_many_binds() -> Result<()> {
}
#[test]
#[cfg(not(target_arch = "wasm32"))]
fn test_ref_stack_exhaustion() {
match catch_unwind(AssertUnwindSafe(|| -> Result<()> {
let lua = Lua::new();
@@ -1318,10 +1336,10 @@ fn test_warnings() -> Result<()> {
#[test]
#[cfg(feature = "luajit")]
#[should_panic]
fn test_luajit_cdata() {
fn test_luajit_cdata() -> Result<()> {
let lua = unsafe { Lua::unsafe_new() };
let _v: Result<Value> = lua
let cdata = lua
.load(
r#"
local ffi = require("ffi")
@@ -1334,11 +1352,17 @@ fn test_luajit_cdata() {
return ptr
"#,
)
.eval();
.eval::<Value>()?;
assert!(cdata.is_userdata() && cdata.is_cdata());
assert_eq!(cdata.type_name(), "cdata");
assert!(cdata.to_string()?.starts_with("cdata<void *>:"));
Ok(())
}
#[test]
#[cfg(feature = "send")]
#[cfg(not(target_arch = "wasm32"))]
fn test_send() {
let lua = Lua::new();
std::thread::spawn(move || {
+31
View File
@@ -190,3 +190,34 @@ fn test_coroutine_panic() {
Err(p) => assert!(*p.downcast::<&str>().unwrap() == "test_panic"),
}
}
#[cfg(all(feature = "unstable", not(feature = "send")))]
#[test]
fn test_owned_thread() -> Result<()> {
let lua = Lua::new();
let accumulate = lua
.create_thread(
lua.load(
r#"
function (sum)
while true do
sum = sum + coroutine.yield(sum)
end
end
"#,
)
.eval::<Function>()?,
)?
.into_owned();
for i in 0..4 {
accumulate.resume::<_, ()>(i)?;
}
assert_eq!(accumulate.resume::<_, i64>(4)?, 10);
assert_eq!(accumulate.status(), ThreadStatus::Resumable);
assert!(accumulate.resume::<_, ()>("error").is_err());
assert_eq!(accumulate.status(), ThreadStatus::Error);
Ok(())
}
+55 -3
View File
@@ -15,7 +15,7 @@ use std::sync::atomic::{AtomicI64, Ordering};
use mlua::{
AnyUserData, AnyUserDataExt, Error, ExternalError, Function, Lua, MetaMethod, Nil, Result,
String, UserData, UserDataFields, UserDataMethods, UserDataRef, Value,
String, UserData, UserDataFields, UserDataMethods, UserDataRef, Value, Variadic,
};
#[test]
@@ -92,6 +92,30 @@ fn test_methods() -> Result<()> {
Ok(())
}
#[test]
fn test_method_variadic() -> Result<()> {
struct MyUserData(i64);
impl UserData for MyUserData {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_method("get", |_, data, ()| Ok(data.0));
methods.add_method_mut("add", |_, data, vals: Variadic<i64>| {
data.0 += vals.into_iter().sum::<i64>();
Ok(())
});
}
}
let lua = Lua::new();
let globals = lua.globals();
globals.set("userdata", MyUserData(0))?;
lua.load("userdata:add(1, 5, -10)").exec()?;
let ud: UserDataRef<MyUserData> = globals.get("userdata")?;
assert_eq!(ud.0, -4);
Ok(())
}
#[test]
fn test_metamethods() -> Result<()> {
#[derive(Copy, Clone)]
@@ -482,6 +506,9 @@ fn test_functions() -> Result<()> {
#[test]
fn test_fields() -> Result<()> {
let lua = Lua::new();
let globals = lua.globals();
#[derive(Copy, Clone)]
struct MyUserData(i64);
@@ -509,8 +536,6 @@ fn test_fields() -> Result<()> {
}
}
let lua = Lua::new();
let globals = lua.globals();
globals.set("ud", MyUserData(7))?;
lua.load(
r#"
@@ -531,6 +556,33 @@ fn test_fields() -> Result<()> {
)
.exec()?;
// Case: fields + __index metamethod (function)
struct MyUserData2(i64);
impl UserData for MyUserData2 {
fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_field("z", 0);
fields.add_field_method_get("x", |_, data| Ok(data.0));
}
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_method(MetaMethod::Index, |_, _, name: StdString| match &*name {
"y" => Ok(Some(-1)),
_ => Ok(None),
});
}
}
globals.set("ud", MyUserData2(1))?;
lua.load(
r#"
assert(ud.x == 1)
assert(ud.y == -1)
assert(ud.z == 0)
"#,
)
.exec()?;
Ok(())
}
+71
View File
@@ -158,3 +158,74 @@ fn test_debug_format() -> Result<()> {
Ok(())
}
#[test]
fn test_value_conversions() -> Result<()> {
let lua = Lua::new();
assert!(Value::Nil.is_nil());
assert!(!Value::NULL.is_nil());
assert!(Value::NULL.is_null());
assert!(Value::NULL.is_light_userdata());
assert!(Value::NULL.as_light_userdata() == Some(LightUserData(ptr::null_mut())));
assert!(Value::Boolean(true).is_boolean());
assert_eq!(Value::Boolean(false).as_boolean(), Some(false));
assert!(Value::Integer(1).is_integer());
assert_eq!(Value::Integer(1).as_integer(), Some(1));
assert_eq!(Value::Integer(1).as_i32(), Some(1i32));
assert_eq!(Value::Integer(1).as_u32(), Some(1u32));
assert_eq!(Value::Integer(1).as_i64(), Some(1i64));
assert_eq!(Value::Integer(1).as_u64(), Some(1u64));
#[cfg(any(feature = "lua54", feature = "lua53"))]
{
assert_eq!(Value::Integer(mlua::Integer::MAX).as_i32(), None);
assert_eq!(Value::Integer(mlua::Integer::MAX).as_u32(), None);
}
assert_eq!(Value::Integer(1).as_isize(), Some(1isize));
assert_eq!(Value::Integer(1).as_usize(), Some(1usize));
assert!(Value::Number(1.23).is_number());
assert_eq!(Value::Number(1.23).as_number(), Some(1.23));
assert_eq!(Value::Number(1.23).as_f32(), Some(1.23f32));
assert_eq!(Value::Number(1.23).as_f64(), Some(1.23f64));
assert!(Value::String(lua.create_string("hello")?).is_string());
assert_eq!(
Value::String(lua.create_string("hello")?)
.as_string()
.unwrap(),
"hello"
);
assert_eq!(
Value::String(lua.create_string("hello")?).as_str().unwrap(),
"hello"
);
assert_eq!(
Value::String(lua.create_string("hello")?)
.as_string_lossy()
.unwrap(),
"hello"
);
assert!(Value::Table(lua.create_table()?).is_table());
assert!(Value::Table(lua.create_table()?).as_table().is_some());
assert!(Value::Function(lua.create_function(|_, ()| Ok(())).unwrap()).is_function());
assert!(
Value::Function(lua.create_function(|_, ()| Ok(())).unwrap())
.as_function()
.is_some()
);
assert!(Value::Thread(lua.create_thread(lua.load("function() end").eval()?)?).is_thread());
assert!(
Value::Thread(lua.create_thread(lua.load("function() end").eval()?)?)
.as_thread()
.is_some()
);
assert!(Value::UserData(lua.create_any_userdata("hello")?).is_userdata());
assert_eq!(
Value::UserData(lua.create_any_userdata("hello")?)
.as_userdata()
.and_then(|ud| ud.borrow::<&str>().ok())
.as_deref(),
Some(&"hello")
);
Ok(())
}