Compare commits

...

146 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
Alex Orlenko cdc1c8415e v0.9.0-rc.2 2023-07-28 00:24:37 +01:00
Alex Orlenko f2142731fd mlua-sys: v0.3.0 2023-07-28 00:23:49 +01:00
Alex Orlenko 1dd62c5ffc mlua_derive: v0.9.0-rc.2 2023-07-28 00:23:04 +01:00
Alex Orlenko 95d8b2fd0f Update v0.9 release notes 2023-07-28 00:22:28 +01:00
Alex Orlenko 51a2959312 Add v0.9 release notes
Prepare v0.9-rc.2 release
2023-07-28 00:07:31 +01:00
Alex Orlenko 28bcc73f2c Move impl IntoLua for WrappedFunction to the function.rs file 2023-07-27 15:19:34 +01:00
Alex Orlenko 8b71f94141 Fix AsChunk trait to support capturing wrapped Lua types (AnyUserData::wrap() and Function::wrap()).
This is a breaking change, the `'lua` lifetime used to in environment moved to the trait declaration.
2023-07-27 13:56:48 +01:00
Alex Orlenko 3bda1351c3 Fix lua51/52 case in userdata tests 2023-07-26 23:18:30 +01:00
Alex Orlenko 4daa631178 Set __type metatable field for Luau instead of __name.
Other Lua versions continue to use `__name` field.
Also make `MetaMethod` enum non-exhaustive (and add `Type` variant)
Closes #295
2023-07-26 21:43:26 +01:00
Alex Orlenko 9f5325ef2f Use c-unwind ABI (Rust 1.71+) 2023-07-22 23:54:30 +01:00
Alex Orlenko 6fb65a8fbe Fix loading luau code starting with \t 2023-07-22 18:44:44 +01:00
Alex Orlenko f6dff65d9b Do not rely on vendored feature when testing luajit memory limit 2023-07-21 23:25:28 +01:00
Alex Orlenko 4fa9aa1e5b Pin lua-src and luajit-src versions 2023-07-21 23:22:47 +01:00
Alex Orlenko e1c075fa51 Use giraffate/clippy-action 2023-07-21 10:34:29 +01:00
Alex Orlenko df9251fb52 Support LUA_LIB_NAME in module mode for windows.
In this mode we will link with the particular lua library.
2023-07-21 10:34:26 +01:00
Alex Orlenko ead6be4a52 Make module and vendored features mutually exclusive 2023-07-20 23:23:03 +01:00
Alex Orlenko 4f48c7e5dc Update README 2023-07-20 21:58:01 +01:00
Alex Orlenko fac39a2f46 Drop openresty specific luajit extensions 2023-07-20 01:02:23 +01:00
Alex Orlenko 483e6be207 Support vendored module mode for windows (raw-dylib linking)
This is requires Rust 1.71+
2023-07-20 00:43:57 +01:00
Alex Orlenko 0e030d21b0 Add #[derive(FromLua)] macro to opt-in into FromLua<T> where T: 'static + Clone (userdata type).
Future macro implementations will allow making T from Lua tables/other values.
Relates to #291.
2023-07-16 01:57:28 +01:00
Alex Orlenko 355a0606c3 v0.9.0-rc.1 2023-07-11 22:15:31 +01:00
Alex Orlenko 2277ee4860 Rename UserDataRegistrar to LuaUserDataRegistry 2023-07-11 22:15:15 +01:00
Alex Orlenko 8a4977e8e7 Use pretty format in the repl example 2023-07-11 21:39:05 +01:00
Alex Orlenko 128c357e07 Use __tostring if __name is not available when pretty printing userdata values 2023-07-11 21:36:02 +01:00
Alex Orlenko 9f0fc27c52 Make Lua::push_value() and Lua::pop_value() public (but hidden from the docs).
Can be useful for low-level intergration with mlua values.
Also closes #215.
2023-07-11 20:03:41 +01:00
Alex Orlenko bfd1c29c0a Add back Table::raw_sequence_values() with deprecation notice 2023-07-11 16:02:28 +01:00
Alex Orlenko 5fca2db6d3 Revert Add error-send feature 2023-07-11 15:50:15 +01:00
Alex Orlenko 8ecbf5b76a v0.9.0-rc.1 2023-07-11 01:09:45 +01:00
Alex Orlenko d037c31b4d mlua-sys: v0.2.1 2023-07-11 01:07:37 +01:00
Alex Orlenko 4fe89c9d45 mlua_derive: v0.9.0-rc.1 2023-07-11 01:07:07 +01:00
Alex Orlenko 389526bb80 Some doc improvements 2023-07-11 00:28:24 +01:00
Alex Orlenko 4adc3116f9 Add error-send feature 2023-07-11 00:20:52 +01:00
Alex Orlenko 8e0bdc9934 Remove clippy::let_and_return ignore 2023-07-10 22:58:15 +01:00
Alex Orlenko 7dc6e4c132 Add Error::runtime() helper 2023-07-10 22:41:03 +01:00
Alex Orlenko 44b8c8b7a6 Add AnyUserData::wrap() to more easy way of creating _any_ userdata in Lua.
This is similar to `Function::wrap()`.
2023-07-10 16:36:38 +01:00
Alex Orlenko 6b8b79266f Drop futures-timer dev-dependency 2023-07-10 01:16:27 +01:00
Alex Orlenko 08ab685d8d Update itertools dev-dependency 2023-07-10 01:16:20 +01:00
Alex Orlenko 057deb0169 Update rustyline dependency to 12.0 2023-07-10 01:05:40 +01:00
Alex Orlenko 44533d2c9d Add new module attribule skip_memory_check to improve performance
in module mode (by skipping memory allocations check) with extra risks.
2023-07-10 00:43:19 +01:00
Alex Orlenko 54c7a2d191 Update compile tests 2023-07-10 00:10:20 +01:00
Alex Orlenko c38a1f060b Various cosmetic changes 2023-07-10 00:04:40 +01:00
Alex Orlenko 5dca743b0c Improve performance AnyUserData::{is_serializable/inspect/serialize} 2023-07-10 00:01:26 +01:00
Alex Orlenko 01c1952c9f Rename AnyUserData::get_*_user_value to AnyUserData::*_user_value.
To be more consistent with function like `Lua::named_registry_value`, `Lua::add_data_ref` and os on.
2023-07-09 23:50:44 +01:00
Alex Orlenko dbc3dd95d4 Fix Luau vector4 formatting when throwing it in exception 2023-07-09 23:14:49 +01:00
Alex Orlenko a9b0cdfc03 Take &str as function name in TableExt and AnyUserDataExt traits 2023-07-09 22:49:16 +01:00
Alex Orlenko 1c20494158 Use usize instead of c_int for table capacity (Lua::create_table_with_capacity) 2023-07-09 22:11:26 +01:00
Alex Orlenko c9294ad642 Add #[must_use] hint and const to some Luau Compiler functions 2023-07-09 14:13:06 +01:00
Alex Orlenko 3a71bfb8a0 Refactor Lua 5.4 warnings to use &str instead of CStr 2023-07-09 14:13:05 +01:00
Alex Orlenko 24e14c4874 Always set hook on a current Lua context (state) and remove MainThreadNotAvailable as no longer needed 2023-07-09 12:33:21 +01:00
Alex Orlenko 20826a69ae Re-export ffi crate (mlua-sys) 2023-07-09 11:31:59 +01:00
Alex Orlenko 5127903c38 Remove generic type parameter from init_userdata_metatable 2023-07-08 17:00:46 +01:00
Alex Orlenko 541139b944 Change Lua ref types Debug print from Ref{index} to Ref{pointer}
The index is always uniq per instance, but pointer can exactly tell when
several values reference to the same Lua internal value.
2023-07-08 12:52:14 +01:00
Alex Orlenko 925a2816cc clippy 2023-07-06 00:59:46 +01:00
Alex Orlenko b3b8d79446 Make Debug interface more user friendly
- use String instead of Vec<u8>
- update docs
- unify fields between lua5.x/luau
- line numbers are `usize`
2023-07-06 00:38:33 +01:00
Alex Orlenko 85f17a269d Add Table:is_empty() function 2023-06-26 10:50:18 +01:00
Alex Orlenko b169031d4e Don't use any metamethods in Table::sequence_values() iterator.
This is matches with `Table::pair()` iterator.
Remove `Table::raw_sequence_values()` iterator.
2023-06-26 10:46:59 +01:00
Alex Orlenko 399e469328 Update "async userdata method" benchmark 2023-06-21 22:23:43 +01:00
Alex Orlenko 1367a033d7 Don't clone function when doing call_async() 2023-06-21 13:14:17 +01:00
Alex Orlenko c1168d3ec1 Refactor call_async() functions to use static dispatch outside of traits 2023-06-21 12:44:24 +01:00
Alex Orlenko b05698d55b impl UserData for Rc<T> and Arc<T> where T: UserData 2023-06-21 01:30:09 +01:00
Alex Orlenko aeacf6cacc Remove allow(dead_code) from mlua-sys build scripts 2023-06-20 23:49:48 +01:00
Alex Orlenko 1f0e81c9a1 Add a dedicated type for Luau vector.
Refactor existing implementation and add 4-dimensional vectors support.
2023-06-20 13:30:42 +01:00
Alex Orlenko c2bfc9ec52 Implement PartialEq<[T]> for tables 2023-06-19 23:28:08 +01:00
Alex Orlenko 9fdba541e9 Update UserDataMethods::add_async_method() functions to take &T as second argument instead of cloning T.
New functions: `UserDataMethods::add_async_method_mut()`, `UserDataMethods::add_async_meta_method_mut()`.
2023-06-15 00:34:41 +01:00
Alex Orlenko cf0524aa23 Use lua_iscfunction instead of lua_getinfo in Function::environment() 2023-06-08 09:33:48 +01:00
92 changed files with 5648 additions and 2375 deletions
+42 -19
View File
@@ -9,7 +9,7 @@ jobs:
matrix:
os: [ubuntu-22.04, macos-latest, windows-latest]
rust: [stable]
lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit]
lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4]
include:
- os: ubuntu-22.04
target: x86_64-unknown-linux-gnu
@@ -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
@@ -104,7 +104,7 @@ jobs:
matrix:
os: [ubuntu-22.04, macos-latest, windows-latest]
rust: [stable, nightly]
lua: [lua54, lua53, lua52, lua51, luajit, luajit52, luau, luau-jit]
lua: [lua54, lua53, lua52, lua51, luajit, luajit52, luau, luau-jit, luau-vector4]
include:
- os: ubuntu-22.04
target: x86_64-unknown-linux-gnu
@@ -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 }}
@@ -140,12 +140,12 @@ jobs:
matrix:
os: [ubuntu-22.04]
rust: [nightly]
lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit]
lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4]
include:
- 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
@@ -218,18 +241,18 @@ jobs:
- run: cargo fmt -- --check
clippy:
name: Clippy check
name: Clippy
runs-on: ubuntu-22.04
strategy:
matrix:
lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit]
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
components: clippy
- uses: actions-rs/clippy-check@v1
- uses: giraffate/clippy-action@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot,unstable"
reporter: 'github-pr-review'
clippy_flags: --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot,unstable"
+74
View File
@@ -1,3 +1,65 @@
## 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).
- Support vendored module mode for windows (raw-dylib linking, Rust 1.71+)
- `module` and `vendored` features are now mutually exclusive
- Use `C-unwind` ABI (Rust 1.71+)
- Changed `AsChunk` trait to support capturing wrapped Lua types
## v0.9.0-rc.1
- `UserDataMethods::add_async_method()` takes `&T` instead of cloning `T`
- Implemented `PartialEq<[T]>` for tables
- Added Luau 4-dimensional vectors support (`luau-vector4` feature)
- `Table::sequence_values()` iterator no longer uses any metamethods (`Table::raw_sequence_values()` is deprecated)
- Added `Table:is_empty()` function that checks both hash and array parts
- Refactored Debug interface
- Re-exported `ffi` (`mlua-sys`) crate for easier writing of unsafe code
- Refactored Lua 5.4 warnings interface
- Take `&str` as function name in `TableExt` and `AnyUserDataExt` traits
- Added module attribule `skip_memory_check` to improve performance
- Added `AnyUserData::wrap()` to provide more easy way of creating _any_ userdata in Lua
## v0.9.0-beta.3
- Added `OwnedAnyUserData::take()`
@@ -59,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.
+21 -9
View File
@@ -1,7 +1,8 @@
[package]
name = "mlua"
version = "0.9.0-beta.3" # 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"
repository = "https://github.com/khvzak/mlua"
documentation = "https://docs.rs/mlua"
@@ -31,8 +32,9 @@ 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"]
module = ["mlua_derive", "ffi/module"]
async = ["futures-util"]
@@ -42,39 +44,49 @@ macros = ["mlua_derive/macros"]
unstable = []
[dependencies]
mlua_derive = { version = "=0.9.0-beta.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.2.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 = "11.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"] }
futures-timer = "3.0"
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"]
+13 -12
View File
@@ -9,7 +9,7 @@
[docs.rs]: https://docs.rs/mlua
[Coverage Status]: https://codecov.io/gh/khvzak/mlua/branch/master/graph/badge.svg?token=99339FS1CG
[codecov.io]: https://codecov.io/gh/khvzak/mlua
[MSRV]: https://img.shields.io/badge/rust-1.63+-brightgreen.svg?&logo=rust
[MSRV]: https://img.shields.io/badge/rust-1.71+-brightgreen.svg?&logo=rust
[Guided Tour] | [Benchmarks] | [FAQ]
@@ -19,7 +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.
> 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.
@@ -28,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
@@ -45,7 +47,8 @@ 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)
* `async`: enable async/await support (any executor can be used, eg. [tokio] or [async-std])
@@ -97,12 +100,12 @@ You have to enable one of the features: `lua54`, `lua53`, `lua52`, `lua51`, `lua
By default `mlua` uses `pkg-config` tool to find lua includes and libraries for the chosen Lua version.
In most cases it works as desired, although sometimes could be more preferable to use a custom lua library.
To achieve this, mlua supports `LUA_INC`, `LUA_LIB`, `LUA_LIB_NAME` and `LUA_LINK` environment variables.
To achieve this, mlua supports `LUA_LIB`, `LUA_LIB_NAME` and `LUA_LINK` environment variables.
`LUA_LINK` is optional and may be `dylib` (a dynamic library) or `static` (a static library, `.a` archive).
An example how to use them:
``` sh
my_project $ LUA_INC=$HOME/tmp/lua-5.2.4/src LUA_LIB=$HOME/tmp/lua-5.2.4/src LUA_LIB_NAME=lua LUA_LINK=static cargo build
my_project $ LUA_LIB=$HOME/tmp/lua-5.2.4/src LUA_LIB_NAME=lua LUA_LINK=static cargo build
```
`mlua` also supports vendored lua/luajit using the auxiliary crates [lua-src](https://crates.io/crates/lua-src) and
@@ -116,7 +119,7 @@ Add to `Cargo.toml` :
``` toml
[dependencies]
mlua = { version = "0.8", features = ["lua54", "vendored"] }
mlua = { version = "0.9.1", features = ["lua54", "vendored"] }
```
`main.rs`
@@ -151,7 +154,7 @@ Add to `Cargo.toml` :
crate-type = ["cdylib"]
[dependencies]
mlua = { version = "0.8", features = ["lua54", "vendored", "module"] }
mlua = { version = "0.9.1", features = ["lua54", "module"] }
```
`lib.rs` :
@@ -196,13 +199,11 @@ rustflags = [
]
```
On Linux you can build modules normally with `cargo build --release`.
Vendored and non-vendored builds are supported for these OS.
On Windows `vendored` mode for modules is not supported since you need to link to a Lua dll.
Easiest way is to use either MinGW64 (as part of [MSYS2](https://github.com/msys2/msys2) package) with `pkg-config` or
MSVC with `LUA_INC` / `LUA_LIB` / `LUA_LIB_NAME` environment variables.
On Windows the target module will be linked with `lua5x.dll` library (depending on your feature flags).
Your main application should provide this library.
More details about compiling and linking Lua modules can be found on the [Building Modules](http://lua-users.org/wiki/BuildingModules) page.
Module builds don't require Lua lib or headers to be installed on the system.
### Publishing to luarocks.org
+81 -4
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();
@@ -264,17 +335,19 @@ fn call_userdata_method(c: &mut Criterion) {
}
fn call_async_userdata_method(c: &mut Criterion) {
#[derive(Clone, Copy)]
struct UserData(i64);
struct UserData(String);
impl LuaUserData for UserData {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_async_method("method", |_, this, ()| async move { Ok(this.0) });
methods.add_async_method("method", |_, this, ()| async move { Ok(this.0.clone()) });
}
}
let options = LuaOptions::new().thread_pool_size(1024);
let lua = Lua::new_with(LuaStdLib::ALL_SAFE, options).unwrap();
lua.globals().set("userdata", UserData(10)).unwrap();
lua.globals()
.set("userdata", UserData("hello".to_string()))
.unwrap();
c.bench_function("call async [userdata method] 10", |b| {
let rt = Runtime::new().unwrap();
@@ -303,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);
+361
View File
@@ -0,0 +1,361 @@
## mlua v0.9 release notes
The v0.9 version of mlua is a major release that includes a number of API changes and improvements. This release is a stepping stone towards the v1.0.
This document highlights the most important changes. For a full list of changes, see the [CHANGELOG].
[CHANGELOG]: https://github.com/khvzak/mlua/blob/master/CHANGELOG.md
### New features
#### 1. New Any UserData API
This is a long awaited feature that allows to register in Lua foreign types that cannot implement `UserData` trait because of the Rust orphan rules.
Now you can register any type that implements [`Any`] trait as a userdata type.
Consider the following example:
```rust
lua.register_userdata_type::<std::string::String>(|reg| {
reg.add_method("len", |_, this, ()| Ok(this.len()));
reg.add_method_mut("push", |_, this, s: String| {
this.push_str(&s);
Ok(())
});
reg.add_meta_method(MetaMethod::ToString, |lua, this, ()| lua.create_string(this));
})?;
let s = lua.create_any_userdata("hello".to_string())?;
lua.load(chunk! {
print("s:len() is " .. $s:len())
$s:push(" world")
// Prints: hello, world
print($s)
})
.exec()?;
```
In this example we registered [`std::string::String`] as a userdata type with a set of methods and then created an instance of this type in Lua.
It's _not_ required to register a type before using the `Lua::create_any_userdata()` method, instead an empty metatable will be created for you.
You can also register the same type multiple times with different methods. Any previously created instances will share the old metatable, while new instances will have the new one.
The new set of API is called `any_userdata` because it allows to register types that implements [`Any`] trait.
[`std::string::String`]: https://doc.rust-lang.org/stable/std/string/struct.String.html
[`Any`]: https://doc.rust-lang.org/stable/std/any/trait.Any.html
#### 2. Scope support for the new any userdata types
When you need to create non-static userdata instances in Lua, the usual way is use `Lua::scope()` helper to make them scoped. When out of scope, any scoped objects will be automatically
dropped. The only downside of this approach is that every new instance will have a new metatable. This is not very fast if you need to create a lot of instances.
With the new Any UserData API, you can place non-static references `&T` where `T: 'static` into a scope and they will share a single static metatable.
```rust
lua.register_userdata_type::<std::string::String>(|reg| {
reg.add_method_mut("replace", |_, this, (pat, to): (String, String)| {
*this = this.replace(&pat, &to);
Ok(())
});
reg.add_meta_method(MetaMethod::ToString, |lua, this, ()| lua.create_string(this));
})?;
let mut s = "hello, world".to_string();
lua.scope(|scope| {
// This userdata instance holds only a mutable reference to our string
let ud = scope.create_any_userdata_ref_mut(&mut s)?;
lua.load(chunk! {
$ud:replace("world", "user")
})
.exec()
})?;
// Prints: hello, user!
println!("{s}!");
```
#### 3. Owned types (`unstable`)
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`/ `OwnedThread`that are `'static` (no lifetime attached).
```rust
let lua = Lua::new();
struct MyStruct {
table: OwnedTable,
func: OwnedFunction,
}
let my_struct = MyStruct {
table: lua.globals().into_owned(),
func: lua
.create_function(|_, t: Table| Ok(format!("{t:#?}")))?
.into_owned(),
};
// It's safe to drop Lua!
drop(lua);
let result = my_struct.func.call::<_, String>(my_struct.table)?;
println!("{result}");
```
Prior to v0.9, it was possible to do by creating a reference to the Lua value in registry using `Lua::create_registry_value()`
and retrieving value later using `Lua::registry_value()` method.
All owned handles hold a *strong* reference to the current Lua instance.
Be warned, if you place them 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.
Please note this functionality is available under the `unstable` feature flag and not available when the `send` feature is enabled.
#### New ffi module
In v0.9 release the internal `ffi` module has been moved into the new [`mlua-sys`] crate and became available for public use.
This crate provides unified Lua FFI API (targeting Lua 5.4) using a (limited) compatibility layer for older versions.
mlua re-exports the `ffi` module aliasing the `mlua-sys` crate and provides (unsafe) functionality to work with raw Lua state:
```rust
unsafe {
unsafe extern "C-unwind" fn lua_add(state: *mut mlua::lua_State) -> i32 {
let a = mlua::ffi::luaL_checkinteger(state, 1);
let b = mlua::ffi::luaL_checkinteger(state, 2);
mlua::ffi::lua_pushinteger(state, a + b);
1
}
let add = lua.create_c_function(lua_add)?;
assert_eq!(add.call::<_, i32>((2, 3))?, 5);
}
```
[`mlua-sys`]: https://crates.io/crates/mlua-sys
#### Luau JIT support
mlua brings support for the new [Luau] JIT backend under the `luau-jit` feature flag.
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
### Improvements
#### 1. Better error reporting
When calling a Rust function from Lua and passing wrong arguments, previous mlua versions reported a error message without any context or reference to the particular argument.
In v0.9 it reports a error message with the argument index and expected type:
```rust
let func = lua.create_function(|_, _a: i32| Ok(()))?;
lua.load(chunk! {
local ok, err = pcall($func, "not a number")
// Prints: bad argument #1: error converting Lua string to i32 (expected number or string coercible to number)
print(err)
})
.exec()?;
```
Similar changes have been made for userdata functions and methods:
```rust
lua.register_userdata_type::<&'static str>(|reg| {
reg.add_method("len", |_, this, ()| Ok(this.len()));
})?;
let s = lua.create_any_userdata("hello")?;
lua.load(chunk! {
local ok, err = pcall($s.len, 123)
// Prints: bad argument `self` to `&str.len`: error converting Lua integer to userdata
print(err)
})
.exec()?;
```
#### 2. Error context
Similar to the [`anyhow`] Error type, now it's possible to attach context to Lua errors:
```rust
let read = lua.create_function(|lua, path: String| {
let bytes = std::fs::read(&path)
.into_lua_err()
.context(format!("Failed to open `{path}`"))?;
Ok(lua.create_string(bytes))
})?;
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()?;
```
[`anyhow`]: https://crates.io/crates/anyhow
#### 4. New methods `Function::wrap`/`AnyUserData::wrap`
Sometimes it's useful to have `IntoLua` trait implementation for a Rust function or type `T: Any` without needing to call `Lua::create_function()`/`Lua::create_any_userdata()` methods.
Since v0.9 you can call the new methods `Function::wrap()`/`AnyUserData::wrap()` that allows to do this. They return an abstract type that `impl IntoLua`:
```rust
lua.globals().set("print_rust", Function::wrap(|_, s: String| Ok(println!("{}", s))))?;
lua.globals().set("rust_ud", AnyUserData::wrap("hello"))?;
```
In addition there are also `Function::wrap_mut()`/`Function::wrap_async()` methods that allow to wrap mutable and async functions respectively.
For a `T: 'UserData + 'static` the `IntoLua` trait is still always implemented.
#### `UserDataRef` and `UserDataRefMut` type wrappers
The new wrappers `UserDataRef` and `UserDataRefMut` are receivers for userdata type `T` and borrow underlying instance for the lifetime of the wrapper.
```rust
lua.globals()
.set("ud", AnyUserData::wrap("hello".to_string()))?;
let mut ud_mut: UserDataRefMut<String> = lua.globals().get("ud")?;
ud_mut.push_str(", Rust");
drop(ud_mut);
let ud_ref: UserDataRef<String> = lua.globals().get("ud")?;
// Prints: hello, Rust
println!("{}", *ud_ref);
```
In the previous mlua versions the same functionality can be achieved by receiving `AnyUserData` and calling `AnyUserData::borrow()`/`AnyUserData::borrow_mut()` methods.
The new wrappers are identical to Rust [`Ref`]/[`RefMut`] types.
[`Ref`]: https://doc.rust-lang.org/std/cell/struct.Ref.html
[`RefMut`]: https://doc.rust-lang.org/std/cell/struct.RefMut.html
#### New `AnyUserDataExt` trait
Similar to the `TableExt` trait, the `AnyUserDataExt` provides a set of extra methods for the `AnyUserData` type.
1) `AnyUserDataExt::get()/set()` to get/set a value by key from the userdata, assuming it has `__index` metamethod.
2) `AnyUserDataExt::call()` to call the userdata as a function assuming it has `__call` metamethod.
3) `AnyUserData::call_method(name, ...)` to call the userdata method, assuming it has `__index` metamethod and the associated function.
#### Pretty formatting Lua values
`mlua::Value` implements a new format `:#?` that allows to (recursively) pretty print Lua values:
```rust
println!("{:#?}", lua.globals());
```
Prints:
```
{
["_G"] = table: 0x7fa2d0706260,
["_VERSION"] = "Lua 5.4",
["assert"] = function: 0x10451d11d,
["collectgarbage"] = function: 0x10451d198,
["coroutine"] = {
["close"] = function: 0x10451e28f,
...
},
["dofile"] = function: 0x10451d37c,
...
}
```
In addition a new method `Value::to_string()` has been added to convert `Value` to a string (using `__tostring` metamethod if available).
#### Environment for Lua functions
Any Lua functions have an associated environment table that is used to resolve global variables. By default it sets to a Lua globals table.
In the new release it's possible to get or update a function environment using `Function::environment()` or `Function::set_environment()` methods respectively.
```rust
let f = lua.load("return a").into_function()?;
assert_eq!(f.environment(), Some(lua.globals()));
lua.globals().set("a", 1)?;
assert_eq!(f.call::<_, i32>(())?, 1);
f.set_environment(lua.create_table_from([("a", "hello")])?)?;
assert_eq!(f.call::<_, mlua::String>(())?, "hello");
```
#### Performance optimizations
The new mlua version has a number of performance improvements. Please check the [benchmarks results] to see how mlua compares to rlua and rhai.
[benchmarks results]: https://github.com/khvzak/script-bench-rs
### Changes in `module` mode
#### New attributes
The `lua_module` macro now support the following attributes:
- `name=...` - sets name of the module (defaults to the name of the function).
Eg.:
```rust
#[mlua::lua_module(name = "alt_module")]
fn my_module(lua: &Lua) -> LuaResult<LuaTable> {
lua.create_table()
}
```
Under the hood a new function `luaopen_alt_module` will be created for the Lua module loader.
- `skip_memory_check` - skip memory allocation checks for some operations.
In module mode, mlua runs in unknown environment and cannot say are there any memory limits or not. As result, some operations that require memory allocation runs in
protected mode. Setting this attribute will improve performance of such operations with risk of having uncaught exceptions and memory leaks.
#### Improved Windows target
In previous mlua versions, building a Lua module for Windows requires having Lua development libraries installed on the system.
In contrast, on Linux and macOS, modules can be built without any external dependencies using the `-undefined=dynamic_lookup` linker flag.
With Rust 1.71+ it's now possible to lift this restriction for Windows as well. You can build modules normally and they will be linked with
`lua54.dll`/`lua53.dll`/`lua52.dll`/`lua51.dll` depending on the enabled Lua version.
You still need to have the dll although, linked to application where the module will be loaded.
### Breaking changes
1) `ToLua`/`ToLuaMulti` traits have been renamed to `IntoLua`/`IntoLuaMulti` respectively (with the methods called `into_lua`/`into_lua_multi`).
The main reason for this change is following the Rust self [convention](https://rust-lang.github.io/rust-clippy/master/index.html#/wrong_self_convention).
2) Removed `FromLua` implementation for `T: UserData + Clone`.
During the usage of mlua, it was found that this implementation is not very useful and prevents custom `FromLua` implementations for `T: UserData`.
It should be a developer decision to opt-in `FromLua` for their `T` if needed rather than having enabled it unconditionally.
To opt-in `FromLua` for `T: Clone` you can use a simple `#[derive(FromLua)]` macro (requires `feature = "macros"`):
```rust
#[derive(Clone, Copy, mlua::FromLua)]
struct MyUserData(i32);
```
`T` is not required to implement `UserData` because of the new relaxed restrictions on userdata types.
+3 -4
View File
@@ -3,14 +3,13 @@ use std::collections::HashMap;
use hyper::body::{Body as HyperBody, HttpBody as _};
use hyper::Client as HyperClient;
use mlua::{chunk, AnyUserData, ExternalResult, Lua, Result, UserData, UserDataMethods};
use mlua::{chunk, ExternalResult, Lua, Result, UserData, UserDataMethods};
struct BodyReader(HyperBody);
impl UserData for BodyReader {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_async_function("read", |lua, reader: AnyUserData| async move {
let mut reader = reader.borrow_mut::<Self>()?;
methods.add_async_method_mut("read", |lua, reader, ()| async move {
if let Some(bytes) = reader.0.data().await {
let bytes = bytes.into_lua_err()?;
return Some(lua.create_string(&bytes)).transpose();
@@ -20,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();
+12 -23
View File
@@ -6,9 +6,7 @@ use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
use tokio::task;
use mlua::{
chunk, AnyUserData, Function, Lua, RegistryKey, String as LuaString, UserData, UserDataMethods,
};
use mlua::{chunk, Function, Lua, RegistryKey, String as LuaString, UserData, UserDataMethods};
struct LuaTcpStream(TcpStream);
@@ -18,28 +16,19 @@ impl UserData for LuaTcpStream {
Ok(this.0.peer_addr()?.to_string())
});
methods.add_async_function(
"read",
|lua, (this, size): (AnyUserData, usize)| async move {
let mut this = this.borrow_mut::<Self>()?;
let mut buf = vec![0; size];
let n = this.0.read(&mut buf).await?;
buf.truncate(n);
lua.create_string(&buf)
},
);
methods.add_async_method_mut("read", |lua, this, size| async move {
let mut buf = vec![0; size];
let n = this.0.read(&mut buf).await?;
buf.truncate(n);
lua.create_string(&buf)
});
methods.add_async_function(
"write",
|_, (this, data): (AnyUserData, LuaString)| async move {
let mut this = this.borrow_mut::<Self>()?;
let n = this.0.write(&data.as_bytes()).await?;
Ok(n)
},
);
methods.add_async_method_mut("write", |_, this, data: LuaString| async move {
let n = this.0.write(&data.as_bytes()).await?;
Ok(n)
});
methods.add_async_function("close", |_, this: AnyUserData| async move {
let mut this = this.borrow_mut::<Self>()?;
methods.add_async_method_mut("close", |_, this, ()| async move {
this.0.shutdown().await?;
Ok(())
});
+3 -3
View File
@@ -1,11 +1,11 @@
//! This example shows a simple read-evaluate-print-loop (REPL).
use mlua::{Error, Lua, MultiValue};
use rustyline::Editor;
use rustyline::DefaultEditor;
fn main() {
let lua = Lua::new();
let mut editor = Editor::<(), _>::new().expect("Failed to make rustyline editor");
let mut editor = DefaultEditor::new().expect("Failed to create editor");
loop {
let mut prompt = "> ";
@@ -24,7 +24,7 @@ fn main() {
"{}",
values
.iter()
.map(|value| format!("{:?}", value))
.map(|value| format!("{:#?}", value))
.collect::<Vec<_>>()
.join("\t")
);
+6 -4
View File
@@ -1,7 +1,8 @@
[package]
name = "mlua-sys"
version = "0.2.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"
@@ -27,6 +28,7 @@ luajit = []
luajit52 = ["luajit"]
luau = ["luau0-src"]
luau-codegen = ["luau"]
luau-vector4 = ["luau"]
vendored = ["lua-src", "luajit-src"]
module = []
@@ -36,6 +38,6 @@ module = []
cc = "1.0"
cfg-if = "1.0"
pkg-config = "0.3.17"
lua-src = { version = ">= 546.0.0, < 550.0.0", optional = true }
luajit-src = { version = ">= 210.4.0, < 220.0.0", optional = true }
luau0-src = { version = "0.5.8", 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 }
+32 -67
View File
@@ -2,92 +2,57 @@
use std::env;
use std::ops::Bound;
use std::path::PathBuf;
fn get_env_var(name: &str) -> String {
match env::var(name) {
Ok(val) => val,
Err(env::VarError::NotPresent) => String::new(),
Err(err) => panic!("cannot get {name}: {err}"),
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");
}
}
pub fn probe_lua() -> Option<PathBuf> {
let include_dir = get_env_var("LUA_INC");
let lib_dir = get_env_var("LUA_LIB");
let lua_lib = get_env_var("LUA_LIB_NAME");
let lib_dir = env::var("LUA_LIB").unwrap_or_default();
let lua_lib = env::var("LUA_LIB_NAME").unwrap_or_default();
println!("cargo:rerun-if-env-changed=LUA_INC");
println!("cargo:rerun-if-env-changed=LUA_LIB");
println!("cargo:rerun-if-env-changed=LUA_LIB_NAME");
println!("cargo:rerun-if-env-changed=LUA_LINK");
let need_lua_lib = cfg!(any(not(feature = "module"), target_os = "windows"));
if !include_dir.is_empty() {
if need_lua_lib {
if lib_dir.is_empty() {
panic!("LUA_LIB is not set");
}
if lua_lib.is_empty() {
panic!("LUA_LIB_NAME is not set");
}
let mut link_lib = "";
if get_env_var("LUA_LINK") == "static" {
link_lib = "static=";
};
if !lua_lib.is_empty() {
if !lib_dir.is_empty() {
println!("cargo:rustc-link-search=native={lib_dir}");
println!("cargo:rustc-link-lib={link_lib}{lua_lib}");
}
return Some(PathBuf::from(include_dir));
let mut link_lib = "";
if env::var("LUA_LINK").as_deref() == Ok("static") {
link_lib = "static=";
};
println!("cargo:rustc-link-lib={link_lib}{lua_lib}");
return;
}
// Find using `pkg-config`
#[cfg(feature = "lua54")]
let (incl_bound, excl_bound, alt_probe, ver) = ("5.4", "5.5", "lua5.4", "5.4");
let (incl_bound, excl_bound, alt_probe, ver) = ("5.4", "5.5", Some("lua5.4"), "5.4");
#[cfg(feature = "lua53")]
let (incl_bound, excl_bound, alt_probe, ver) = ("5.3", "5.4", "lua5.3", "5.3");
let (incl_bound, excl_bound, alt_probe, ver) = ("5.3", "5.4", Some("lua5.3"), "5.3");
#[cfg(feature = "lua52")]
let (incl_bound, excl_bound, alt_probe, ver) = ("5.2", "5.3", "lua5.2", "5.2");
let (incl_bound, excl_bound, alt_probe, ver) = ("5.2", "5.3", Some("lua5.2"), "5.2");
#[cfg(feature = "lua51")]
let (incl_bound, excl_bound, alt_probe, ver) = ("5.1", "5.2", "lua5.1", "5.1");
#[cfg(any(
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "lua51"
))]
{
let mut lua = pkg_config::Config::new()
.range_version((Bound::Included(incl_bound), Bound::Excluded(excl_bound)))
.cargo_metadata(need_lua_lib)
.probe("lua");
if lua.is_err() {
lua = pkg_config::Config::new()
.cargo_metadata(need_lua_lib)
.probe(alt_probe);
}
lua.unwrap_or_else(|_| panic!("cannot find Lua {ver} using `pkg-config`"))
.include_paths
.get(0)
.cloned()
}
let (incl_bound, excl_bound, alt_probe, ver) = ("5.1", "5.2", Some("lua5.1"), "5.1");
#[cfg(feature = "luajit")]
{
let lua = pkg_config::Config::new()
.range_version((Bound::Included("2.0.4"), Bound::Unbounded))
.cargo_metadata(need_lua_lib)
.probe("luajit");
let (incl_bound, excl_bound, alt_probe, ver) = ("2.0.4", "2.2", None, "JIT");
lua.expect("cannot find LuaJIT using `pkg-config`")
.include_paths
.get(0)
.cloned()
#[rustfmt::skip]
let mut lua = pkg_config::Config::new()
.range_version((Bound::Included(incl_bound), Bound::Excluded(excl_bound)))
.cargo_metadata(true)
.probe(if cfg!(feature = "luajit") { "luajit" } else { "lua" });
if lua.is_err() && alt_probe.is_some() {
lua = pkg_config::Config::new()
.cargo_metadata(true)
.probe(alt_probe.unwrap());
}
lua.unwrap_or_else(|err| panic!("cannot find Lua{ver} using `pkg-config`: {err}"));
}
+2 -5
View File
@@ -1,8 +1,6 @@
#![allow(dead_code)]
use std::path::PathBuf;
pub fn probe_lua() -> Option<PathBuf> {
pub fn probe_lua() {
#[cfg(feature = "lua54")]
let artifacts = lua_src::Build::new().build(lua_src::Lua54);
@@ -23,9 +21,8 @@ pub fn probe_lua() -> Option<PathBuf> {
#[cfg(feature = "luau")]
let artifacts = luau0_src::Build::new()
.enable_codegen(cfg!(feature = "luau-codegen"))
.set_vector_size(if cfg!(feature = "luau-vector4") { 4 } else { 3 })
.build();
artifacts.print_cargo_metadata();
Some(artifacts.include_dir().to_owned())
}
+18 -11
View File
@@ -9,18 +9,25 @@ cfg_if::cfg_if! {
}
fn main() {
// We don't support "vendored module" mode on windows
#[cfg(all(feature = "vendored", feature = "module", target_os = "windows"))]
compile_error!(
"Vendored (static) builds are not supported for modules on Windows.\n"
+ "Please, use `pkg-config` or custom mode to link to a Lua dll."
);
#[cfg(all(feature = "luau", feature = "module", windows))]
compile_error!("Luau does not support `module` mode on Windows");
#[cfg(all(feature = "luau", feature = "module"))]
compile_error!("Luau does not support module mode");
#[cfg(any(not(feature = "module"), target_os = "windows"))]
find::probe_lua();
#[cfg(all(feature = "module", feature = "vendored"))]
compile_error!("`vendored` and `module` features are mutually exclusive");
println!("cargo:rerun-if-changed=build");
#[cfg(windows)]
if cfg!(feature = "module") {
if !std::env::var("LUA_LIB_NAME").unwrap_or_default().is_empty() {
// Don't use raw-dylib linking
find::probe_lua();
return;
}
println!("cargo:rustc-cfg=raw_dylib");
}
#[cfg(not(feature = "module"))]
find::probe_lua();
}
+2 -5
View File
@@ -26,14 +26,10 @@ pub use luau::*;
#[doc(hidden)]
pub const LUA_MAX_UPVALUES: c_int = 255;
#[cfg(any(feature = "lua51", all(feature = "luajit", not(feature = "vendored"))))]
#[cfg(any(feature = "lua51", feature = "luajit"))]
#[doc(hidden)]
pub const LUA_MAX_UPVALUES: c_int = 60;
#[cfg(all(feature = "luajit", feature = "vendored"))]
#[doc(hidden)]
pub const LUA_MAX_UPVALUES: c_int = 120;
#[cfg(feature = "luau")]
#[doc(hidden)]
pub const LUA_MAX_UPVALUES: c_int = 200;
@@ -69,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;
+5 -3
View File
@@ -14,7 +14,8 @@ pub struct luaL_Reg {
pub func: lua_CFunction,
}
extern "C" {
#[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))]
extern "C-unwind" {
pub fn luaL_register(L: *mut lua_State, libname: *const c_char, l: *const luaL_Reg);
#[link_name = "luaL_getmetafield"]
pub fn luaL_getmetafield_(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int;
@@ -42,7 +43,7 @@ extern "C" {
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,
@@ -56,7 +57,8 @@ extern "C" {
pub const LUA_NOREF: c_int = -2;
pub const LUA_REFNIL: c_int = -1;
extern "C" {
#[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))]
extern "C-unwind" {
pub fn luaL_ref(L: *mut lua_State, t: c_int) -> c_int;
pub fn luaL_unref(L: *mut lua_State, t: c_int, r#ref: c_int);
+28 -18
View File
@@ -73,23 +73,23 @@ pub type lua_Integer = i32;
pub type lua_Integer = i64;
/// Type for native C functions that can be passed to Lua.
pub type lua_CFunction = unsafe extern "C" fn(L: *mut lua_State) -> c_int;
pub type lua_CFunction = unsafe extern "C-unwind" fn(L: *mut lua_State) -> c_int;
// Type for functions that read/write blocks when loading/dumping Lua chunks
#[rustfmt::skip]
pub type lua_Reader =
unsafe extern "C" fn(L: *mut lua_State, ud: *mut c_void, sz: *mut usize) -> *const c_char;
unsafe extern "C-unwind" fn(L: *mut lua_State, ud: *mut c_void, sz: *mut usize) -> *const c_char;
#[rustfmt::skip]
pub type lua_Writer =
unsafe extern "C" fn(L: *mut lua_State, p: *const c_void, sz: usize, ud: *mut c_void) -> c_int;
unsafe extern "C-unwind" fn(L: *mut lua_State, p: *const c_void, sz: usize, ud: *mut c_void) -> c_int;
/// Type for memory-allocation functions
pub type lua_Alloc = unsafe extern "C" fn(
ud: *mut c_void,
ptr: *mut c_void,
osize: usize,
nsize: usize,
) -> *mut c_void;
#[rustfmt::skip]
pub type lua_Alloc =
unsafe extern "C-unwind" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void;
extern "C" {
#[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))]
extern "C-unwind" {
//
// State manipulation
//
@@ -97,9 +97,6 @@ extern "C" {
pub fn lua_close(L: *mut lua_State);
pub fn lua_newthread(L: *mut lua_State) -> *mut lua_State;
#[cfg(all(feature = "luajit", feature = "vendored"))]
pub fn lua_resetthread(L: *mut lua_State, th: *mut lua_State);
pub fn lua_atpanic(L: *mut lua_State, panicf: lua_CFunction) -> lua_CFunction;
//
@@ -221,21 +218,33 @@ pub const LUA_GCSTEP: c_int = 5;
pub const LUA_GCSETPAUSE: c_int = 6;
pub const LUA_GCSETSTEPMUL: c_int = 7;
extern "C" {
#[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))]
extern "C-unwind" {
pub fn lua_gc(L: *mut lua_State, what: c_int, data: c_int) -> c_int;
}
//
// Miscellaneous functions
//
extern "C" {
pub fn lua_error(L: *mut lua_State) -> !;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))]
extern "C-unwind" {
#[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)
//
@@ -351,9 +360,10 @@ pub const LUA_MASKLINE: c_int = 1 << (LUA_HOOKLINE as usize);
pub const LUA_MASKCOUNT: c_int = 1 << (LUA_HOOKCOUNT as usize);
/// Type for functions to be called on debug events.
pub type lua_Hook = unsafe extern "C" fn(L: *mut lua_State, ar: *mut lua_Debug);
pub type lua_Hook = unsafe extern "C-unwind" fn(L: *mut lua_State, ar: *mut lua_Debug);
extern "C" {
#[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))]
extern "C-unwind" {
pub fn lua_getstack(L: *mut lua_State, level: c_int, ar: *mut lua_Debug) -> c_int;
pub fn lua_getinfo(L: *mut lua_State, what: *const c_char, ar: *mut lua_Debug) -> c_int;
pub fn lua_getlocal(L: *mut lua_State, ar: *const lua_Debug, n: c_int) -> *const c_char;
+2 -1
View File
@@ -20,7 +20,8 @@ pub const LUA_JITLIBNAME: &str = "jit";
#[cfg(feature = "luajit")]
pub const LUA_FFILIBNAME: &str = "ffi";
extern "C" {
#[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))]
extern "C-unwind" {
pub fn luaopen_base(L: *mut lua_State) -> c_int;
pub fn luaopen_table(L: *mut lua_State) -> c_int;
pub fn luaopen_io(L: *mut lua_State) -> c_int;
+7 -4
View File
@@ -14,7 +14,8 @@ pub struct luaL_Reg {
pub func: lua_CFunction,
}
extern "C" {
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
extern "C-unwind" {
pub fn luaL_checkversion_(L: *mut lua_State, ver: lua_Number);
#[link_name = "luaL_getmetafield"]
@@ -48,7 +49,7 @@ extern "C" {
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,
@@ -65,7 +66,8 @@ extern "C" {
pub const LUA_NOREF: c_int = -2;
pub const LUA_REFNIL: c_int = -1;
extern "C" {
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
extern "C-unwind" {
pub fn luaL_ref(L: *mut lua_State, t: c_int) -> c_int;
pub fn luaL_unref(L: *mut lua_State, t: c_int, r#ref: c_int);
@@ -78,7 +80,8 @@ pub unsafe fn luaL_loadfile(L: *mut lua_State, f: *const c_char) -> c_int {
luaL_loadfilex(L, f, ptr::null())
}
extern "C" {
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
extern "C-unwind" {
pub fn luaL_loadbufferx(
L: *mut lua_State,
buff: *const c_char,
+41 -23
View File
@@ -78,23 +78,23 @@ pub type lua_Integer = i64;
pub type lua_Unsigned = c_uint;
/// Type for native C functions that can be passed to Lua
pub type lua_CFunction = unsafe extern "C" fn(L: *mut lua_State) -> c_int;
pub type lua_CFunction = unsafe extern "C-unwind" fn(L: *mut lua_State) -> c_int;
// Type for functions that read/write blocks when loading/dumping Lua chunks
#[rustfmt::skip]
pub type lua_Reader =
unsafe extern "C" fn(L: *mut lua_State, ud: *mut c_void, sz: *mut usize) -> *const c_char;
unsafe extern "C-unwind" fn(L: *mut lua_State, ud: *mut c_void, sz: *mut usize) -> *const c_char;
#[rustfmt::skip]
pub type lua_Writer =
unsafe extern "C" fn(L: *mut lua_State, p: *const c_void, sz: usize, ud: *mut c_void) -> c_int;
unsafe extern "C-unwind" fn(L: *mut lua_State, p: *const c_void, sz: usize, ud: *mut c_void) -> c_int;
/// Type for memory-allocation functions
pub type lua_Alloc = unsafe extern "C" fn(
ud: *mut c_void,
ptr: *mut c_void,
osize: usize,
nsize: usize,
) -> *mut c_void;
#[rustfmt::skip]
pub type lua_Alloc =
unsafe extern "C-unwind" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void;
extern "C" {
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
extern "C-unwind" {
//
// State manipulation
//
@@ -155,20 +155,19 @@ pub const LUA_OPMOD: c_int = 4;
pub const LUA_OPPOW: c_int = 5;
pub const LUA_OPUNM: c_int = 6;
extern "C" {
pub fn lua_arith(L: *mut lua_State, op: c_int);
}
pub const LUA_OPEQ: c_int = 0;
pub const LUA_OPLT: c_int = 1;
pub const LUA_OPLE: c_int = 2;
extern "C" {
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
extern "C-unwind" {
pub fn lua_arith(L: *mut lua_State, op: c_int);
pub fn lua_rawequal(L: *mut lua_State, idx1: c_int, idx2: c_int) -> c_int;
pub fn lua_compare(L: *mut lua_State, idx1: c_int, idx2: c_int, op: c_int) -> c_int;
}
extern "C" {
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
extern "C-unwind" {
//
// Push functions (C -> stack)
//
@@ -262,7 +261,8 @@ pub unsafe fn lua_pcall(L: *mut lua_State, n: c_int, r: c_int, f: c_int) -> c_in
lua_pcallk(L, n, r, f, 0, None)
}
extern "C" {
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
extern "C-unwind" {
//
// Coroutine functions
//
@@ -298,15 +298,18 @@ pub const LUA_GCISRUNNING: c_int = 9;
pub const LUA_GCGEN: c_int = 10;
pub const LUA_GCINC: c_int = 11;
extern "C" {
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
extern "C-unwind" {
pub fn lua_gc(L: *mut lua_State, what: c_int, data: c_int) -> c_int;
}
extern "C" {
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
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);
@@ -314,6 +317,15 @@ extern "C" {
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)
//
@@ -437,9 +449,10 @@ pub const LUA_MASKLINE: c_int = 1 << (LUA_HOOKLINE as usize);
pub const LUA_MASKCOUNT: c_int = 1 << (LUA_HOOKCOUNT as usize);
/// Type for functions to be called on debug events.
pub type lua_Hook = unsafe extern "C" fn(L: *mut lua_State, ar: *mut lua_Debug);
pub type lua_Hook = unsafe extern "C-unwind" fn(L: *mut lua_State, ar: *mut lua_Debug);
extern "C" {
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
extern "C-unwind" {
pub fn lua_getstack(L: *mut lua_State, level: c_int, ar: *mut lua_Debug) -> c_int;
pub fn lua_getinfo(L: *mut lua_State, what: *const c_char, ar: *mut lua_Debug) -> c_int;
pub fn lua_getlocal(L: *mut lua_State, ar: *const lua_Debug, n: c_int) -> *const c_char;
@@ -450,7 +463,12 @@ extern "C" {
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 -1
View File
@@ -14,7 +14,8 @@ pub const LUA_MATHLIBNAME: &str = "math";
pub const LUA_DBLIBNAME: &str = "debug";
pub const LUA_LOADLIBNAME: &str = "package";
extern "C" {
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
extern "C-unwind" {
pub fn luaopen_base(L: *mut lua_State) -> c_int;
pub fn luaopen_coroutine(L: *mut lua_State) -> c_int;
pub fn luaopen_table(L: *mut lua_State) -> c_int;
+7 -4
View File
@@ -20,7 +20,8 @@ pub struct luaL_Reg {
pub func: lua_CFunction,
}
extern "C" {
#[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);
pub fn luaL_getmetafield(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int;
@@ -50,7 +51,7 @@ extern "C" {
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,
@@ -67,7 +68,8 @@ extern "C" {
pub const LUA_NOREF: c_int = -2;
pub const LUA_REFNIL: c_int = -1;
extern "C" {
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
extern "C-unwind" {
pub fn luaL_ref(L: *mut lua_State, t: c_int) -> c_int;
pub fn luaL_unref(L: *mut lua_State, t: c_int, r#ref: c_int);
@@ -80,7 +82,8 @@ pub unsafe fn luaL_loadfile(L: *mut lua_State, f: *const c_char) -> c_int {
luaL_loadfilex(L, f, ptr::null())
}
extern "C" {
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
extern "C-unwind" {
pub fn luaL_loadbufferx(
L: *mut lua_State,
buff: *const c_char,
+36 -23
View File
@@ -82,27 +82,27 @@ pub type lua_Unsigned = u64;
pub type lua_KContext = isize;
/// Type for native C functions that can be passed to Lua
pub type lua_CFunction = unsafe extern "C" fn(L: *mut lua_State) -> c_int;
pub type lua_CFunction = unsafe extern "C-unwind" fn(L: *mut lua_State) -> c_int;
/// Type for continuation functions
pub type lua_KFunction =
unsafe extern "C" fn(L: *mut lua_State, status: c_int, ctx: lua_KContext) -> c_int;
unsafe extern "C-unwind" fn(L: *mut lua_State, status: c_int, ctx: lua_KContext) -> c_int;
// Type for functions that read/write blocks when loading/dumping Lua chunks
#[rustfmt::skip]
pub type lua_Reader =
unsafe extern "C" fn(L: *mut lua_State, ud: *mut c_void, sz: *mut usize) -> *const c_char;
unsafe extern "C-unwind" fn(L: *mut lua_State, ud: *mut c_void, sz: *mut usize) -> *const c_char;
#[rustfmt::skip]
pub type lua_Writer =
unsafe extern "C" fn(L: *mut lua_State, p: *const c_void, sz: usize, ud: *mut c_void) -> c_int;
unsafe extern "C-unwind" fn(L: *mut lua_State, p: *const c_void, sz: usize, ud: *mut c_void) -> c_int;
/// Type for memory-allocation functions
pub type lua_Alloc = unsafe extern "C" fn(
ud: *mut c_void,
ptr: *mut c_void,
osize: usize,
nsize: usize,
) -> *mut c_void;
#[rustfmt::skip]
pub type lua_Alloc =
unsafe extern "C-unwind" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void;
extern "C" {
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
extern "C-unwind" {
//
// State manipulation
//
@@ -167,20 +167,19 @@ pub const LUA_OPSHR: c_int = 11;
pub const LUA_OPUNM: c_int = 12;
pub const LUA_OPBNOT: c_int = 13;
extern "C" {
pub fn lua_arith(L: *mut lua_State, op: c_int);
}
pub const LUA_OPEQ: c_int = 0;
pub const LUA_OPLT: c_int = 1;
pub const LUA_OPLE: c_int = 2;
extern "C" {
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
extern "C-unwind" {
pub fn lua_arith(L: *mut lua_State, op: c_int);
pub fn lua_rawequal(L: *mut lua_State, idx1: c_int, idx2: c_int) -> c_int;
pub fn lua_compare(L: *mut lua_State, idx1: c_int, idx2: c_int, op: c_int) -> c_int;
}
extern "C" {
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
extern "C-unwind" {
//
// Push functions (C -> stack)
//
@@ -270,7 +269,8 @@ pub unsafe fn lua_pcall(L: *mut lua_State, n: c_int, r: c_int, f: c_int) -> c_in
lua_pcallk(L, n, r, f, 0, None)
}
extern "C" {
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
extern "C-unwind" {
//
// Coroutine functions
//
@@ -304,15 +304,18 @@ pub const LUA_GCSETPAUSE: c_int = 6;
pub const LUA_GCSETSTEPMUL: c_int = 7;
pub const LUA_GCISRUNNING: c_int = 9;
extern "C" {
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
extern "C-unwind" {
pub fn lua_gc(L: *mut lua_State, what: c_int, data: c_int) -> c_int;
}
extern "C" {
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
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);
@@ -321,6 +324,15 @@ extern "C" {
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)
//
@@ -461,9 +473,10 @@ pub const LUA_MASKLINE: c_int = 1 << (LUA_HOOKLINE as usize);
pub const LUA_MASKCOUNT: c_int = 1 << (LUA_HOOKCOUNT as usize);
/// Type for functions to be called on debug events.
pub type lua_Hook = unsafe extern "C" fn(L: *mut lua_State, ar: *mut lua_Debug);
pub type lua_Hook = unsafe extern "C-unwind" fn(L: *mut lua_State, ar: *mut lua_Debug);
extern "C" {
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
extern "C-unwind" {
pub fn lua_getstack(L: *mut lua_State, level: c_int, ar: *mut lua_Debug) -> c_int;
pub fn lua_getinfo(L: *mut lua_State, what: *const c_char, ar: *mut lua_Debug) -> c_int;
pub fn lua_getlocal(L: *mut lua_State, ar: *const lua_Debug, n: c_int) -> *const c_char;
+2 -1
View File
@@ -15,7 +15,8 @@ pub const LUA_MATHLIBNAME: &str = "math";
pub const LUA_DBLIBNAME: &str = "debug";
pub const LUA_LOADLIBNAME: &str = "package";
extern "C" {
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
extern "C-unwind" {
pub fn luaopen_base(L: *mut lua_State) -> c_int;
pub fn luaopen_coroutine(L: *mut lua_State) -> c_int;
pub fn luaopen_table(L: *mut lua_State) -> c_int;
+7 -4
View File
@@ -20,7 +20,8 @@ pub struct luaL_Reg {
pub func: lua_CFunction,
}
extern "C" {
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
extern "C-unwind" {
pub fn luaL_checkversion_(L: *mut lua_State, ver: lua_Number, sz: usize);
pub fn luaL_getmetafield(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int;
@@ -49,7 +50,7 @@ extern "C" {
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,
@@ -66,7 +67,8 @@ extern "C" {
pub const LUA_NOREF: c_int = -2;
pub const LUA_REFNIL: c_int = -1;
extern "C" {
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
extern "C-unwind" {
pub fn luaL_ref(L: *mut lua_State, t: c_int) -> c_int;
pub fn luaL_unref(L: *mut lua_State, t: c_int, r#ref: c_int);
@@ -79,7 +81,8 @@ pub unsafe fn luaL_loadfile(L: *mut lua_State, f: *const c_char) -> c_int {
luaL_loadfilex(L, f, ptr::null())
}
extern "C" {
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
extern "C-unwind" {
pub fn luaL_loadbufferx(
L: *mut lua_State,
buff: *const c_char,
+48 -26
View File
@@ -81,31 +81,31 @@ pub type lua_Unsigned = u64;
pub type lua_KContext = isize;
/// Type for native C functions that can be passed to Lua
pub type lua_CFunction = unsafe extern "C" fn(L: *mut lua_State) -> c_int;
pub type lua_CFunction = unsafe extern "C-unwind" fn(L: *mut lua_State) -> c_int;
/// Type for continuation functions
pub type lua_KFunction =
unsafe extern "C" fn(L: *mut lua_State, status: c_int, ctx: lua_KContext) -> c_int;
unsafe extern "C-unwind" fn(L: *mut lua_State, status: c_int, ctx: lua_KContext) -> c_int;
// Type for functions that read/write blocks when loading/dumping Lua chunks
#[rustfmt::skip]
pub type lua_Reader =
unsafe extern "C" fn(L: *mut lua_State, ud: *mut c_void, sz: *mut usize) -> *const c_char;
unsafe extern "C-unwind" fn(L: *mut lua_State, ud: *mut c_void, sz: *mut usize) -> *const c_char;
#[rustfmt::skip]
pub type lua_Writer =
unsafe extern "C" fn(L: *mut lua_State, p: *const c_void, sz: usize, ud: *mut c_void) -> c_int;
unsafe extern "C-unwind" fn(L: *mut lua_State, p: *const c_void, sz: usize, ud: *mut c_void) -> c_int;
/// Type for memory-allocation functions
pub type lua_Alloc = unsafe extern "C" fn(
ud: *mut c_void,
ptr: *mut c_void,
osize: usize,
nsize: usize,
) -> *mut c_void;
#[rustfmt::skip]
pub type lua_Alloc =
unsafe extern "C-unwind" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void;
/// Type for warning functions
pub type lua_WarnFunction =
unsafe extern "C" fn(ud: *mut c_void, msg: *const c_char, tocont: c_int);
unsafe extern "C-unwind" fn(ud: *mut c_void, msg: *const c_char, tocont: c_int);
extern "C" {
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
extern "C-unwind" {
//
// State manipulation
//
@@ -149,13 +149,21 @@ extern "C" {
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
//
@@ -174,20 +182,19 @@ pub const LUA_OPSHR: c_int = 11;
pub const LUA_OPUNM: c_int = 12;
pub const LUA_OPBNOT: c_int = 13;
extern "C" {
pub fn lua_arith(L: *mut lua_State, op: c_int);
}
pub const LUA_OPEQ: c_int = 0;
pub const LUA_OPLT: c_int = 1;
pub const LUA_OPLE: c_int = 2;
extern "C" {
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
extern "C-unwind" {
pub fn lua_arith(L: *mut lua_State, op: c_int);
pub fn lua_rawequal(L: *mut lua_State, idx1: c_int, idx2: c_int) -> c_int;
pub fn lua_compare(L: *mut lua_State, idx1: c_int, idx2: c_int, op: c_int) -> c_int;
}
extern "C" {
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
extern "C-unwind" {
//
// Push functions (C -> stack)
//
@@ -277,7 +284,8 @@ pub unsafe fn lua_pcall(L: *mut lua_State, n: c_int, r: c_int, f: c_int) -> c_in
lua_pcallk(L, n, r, f, 0, None)
}
extern "C" {
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
extern "C-unwind" {
//
// Coroutine functions
//
@@ -305,7 +313,8 @@ pub unsafe fn lua_yield(L: *mut lua_State, n: c_int) -> c_int {
//
// Warning-related functions
//
extern "C" {
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
extern "C-unwind" {
pub fn lua_setwarnf(L: *mut lua_State, f: Option<lua_WarnFunction>, ud: *mut c_void);
pub fn lua_warning(L: *mut lua_State, msg: *const c_char, tocont: c_int);
}
@@ -325,15 +334,18 @@ pub const LUA_GCISRUNNING: c_int = 9;
pub const LUA_GCGEN: c_int = 10;
pub const LUA_GCINC: c_int = 11;
extern "C" {
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
extern "C-unwind" {
pub fn lua_gc(L: *mut lua_State, what: c_int, ...) -> c_int;
}
extern "C" {
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
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);
@@ -345,6 +357,15 @@ extern "C" {
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)
//
@@ -500,9 +521,10 @@ pub const LUA_MASKLINE: c_int = 1 << (LUA_HOOKLINE as usize);
pub const LUA_MASKCOUNT: c_int = 1 << (LUA_HOOKCOUNT as usize);
/// Type for functions to be called on debug events.
pub type lua_Hook = unsafe extern "C" fn(L: *mut lua_State, ar: *mut lua_Debug);
pub type lua_Hook = unsafe extern "C-unwind" fn(L: *mut lua_State, ar: *mut lua_Debug);
extern "C" {
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
extern "C-unwind" {
pub fn lua_getstack(L: *mut lua_State, level: c_int, ar: *mut lua_Debug) -> c_int;
pub fn lua_getinfo(L: *mut lua_State, what: *const c_char, ar: *mut lua_Debug) -> c_int;
pub fn lua_getlocal(L: *mut lua_State, ar: *const lua_Debug, n: c_int) -> *const c_char;
+2 -1
View File
@@ -14,7 +14,8 @@ pub const LUA_MATHLIBNAME: &str = "math";
pub const LUA_DBLIBNAME: &str = "debug";
pub const LUA_LOADLIBNAME: &str = "package";
extern "C" {
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
extern "C-unwind" {
pub fn luaopen_base(L: *mut lua_State) -> c_int;
pub fn luaopen_coroutine(L: *mut lua_State) -> c_int;
pub fn luaopen_table(L: *mut lua_State) -> c_int;
+4 -4
View File
@@ -323,7 +323,7 @@ pub unsafe fn luaL_getmetafield(L: *mut lua_State, obj: c_int, e: *const c_char)
pub unsafe fn luaL_newmetatable(L: *mut lua_State, tname: *const c_char) -> c_int {
if luaL_newmetatable_(L, tname) != 0 {
lua_pushstring(L, tname);
lua_setfield(L, -2, cstr!("__name"));
lua_setfield(L, -2, cstr!("__type"));
1
} else {
0
@@ -341,7 +341,7 @@ pub unsafe fn luaL_loadbufferx(
fn free(p: *mut c_void);
}
let chunk_is_text = size == 0 || (*data as u8) >= b'\n';
let chunk_is_text = size == 0 || (*data as u8) >= b'\t';
if !mode.is_null() {
let modeb = CStr::from_ptr(mode).to_bytes();
if !chunk_is_text && !modeb.contains(&b'b') {
@@ -454,7 +454,7 @@ pub unsafe fn luaL_tolstring(L: *mut lua_State, mut idx: c_int, len: *mut usize)
}
}
t => {
let tt = luaL_getmetafield(L, idx, cstr!("__name"));
let tt = luaL_getmetafield(L, idx, cstr!("__type"));
let name = if tt == LUA_TSTRING {
lua_tostring(L, -1)
} else {
@@ -462,7 +462,7 @@ pub unsafe fn luaL_tolstring(L: *mut lua_State, mut idx: c_int, len: *mut usize)
};
lua_pushfstring(L, cstr!("%s: %p"), name, lua_topointer(L, idx));
if tt != LUA_TNIL {
lua_replace(L, -2); // remove '__name'
lua_replace(L, -2); // remove '__type'
}
}
};
+47 -2
View File
@@ -13,7 +13,7 @@ pub struct luaL_Reg {
pub func: lua_CFunction,
}
extern "C" {
extern "C-unwind" {
pub fn luaL_register(L: *mut lua_State, libname: *const c_char, l: *const luaL_Reg);
#[link_name = "luaL_getmetafield"]
pub fn luaL_getmetafield_(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int;
@@ -52,6 +52,8 @@ extern "C" {
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);
}
+44 -22
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;
@@ -69,15 +70,15 @@ pub type lua_Integer = c_int;
pub type lua_Unsigned = c_uint;
/// Type for native C functions that can be passed to Lua.
pub type lua_CFunction = unsafe extern "C" fn(L: *mut lua_State) -> c_int;
pub type lua_Continuation = unsafe extern "C" fn(L: *mut lua_State, status: c_int) -> c_int;
pub type lua_CFunction = unsafe extern "C-unwind" fn(L: *mut lua_State) -> c_int;
pub type lua_Continuation = unsafe extern "C-unwind" fn(L: *mut lua_State, status: c_int) -> c_int;
/// Type for userdata destructor functions.
pub type lua_Udestructor = unsafe extern "C" fn(*mut c_void);
pub type lua_Destructor = unsafe extern "C" fn(L: *mut lua_State, *mut c_void);
pub type lua_Udestructor = unsafe extern "C-unwind" fn(*mut c_void);
pub type lua_Destructor = unsafe extern "C-unwind" fn(L: *mut lua_State, *mut c_void);
/// Type for memory-allocation functions.
pub type lua_Alloc = unsafe extern "C" fn(
pub type lua_Alloc = unsafe extern "C-unwind" fn(
ud: *mut c_void,
ptr: *mut c_void,
osize: usize,
@@ -89,7 +90,7 @@ pub const fn luau_version() -> Option<&'static str> {
option_env!("LUAU_VERSION")
}
extern "C" {
extern "C-unwind" {
//
// State manipulation
//
@@ -147,6 +148,7 @@ extern "C" {
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;
//
@@ -156,7 +158,10 @@ extern "C" {
pub fn lua_pushnumber(L: *mut lua_State, n: lua_Number);
pub fn lua_pushinteger(L: *mut lua_State, n: lua_Integer);
pub fn lua_pushunsigned(L: *mut lua_State, n: lua_Unsigned);
#[cfg(not(feature = "luau-vector4"))]
pub fn lua_pushvector(L: *mut lua_State, x: c_float, y: c_float, z: c_float);
#[cfg(feature = "luau-vector4")]
pub fn lua_pushvector(L: *mut lua_State, x: c_float, y: c_float, z: c_float, w: c_float);
#[link_name = "lua_pushlstring"]
pub fn lua_pushlstring_(L: *mut lua_State, s: *const c_char, l: usize);
#[link_name = "lua_pushstring"]
@@ -178,6 +183,8 @@ extern "C" {
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)
//
@@ -248,14 +255,14 @@ pub const LUA_GCSETGOAL: c_int = 7;
pub const LUA_GCSETSTEPMUL: c_int = 8;
pub const LUA_GCSETSTEPSIZE: c_int = 9;
extern "C" {
extern "C-unwind" {
pub fn lua_gc(L: *mut lua_State, what: c_int, data: c_int) -> c_int;
}
//
// Memory statistics
//
extern "C" {
extern "C-unwind" {
pub fn lua_setmemcat(L: *mut lua_State, category: c_int);
pub fn lua_totalbytes(L: *mut lua_State, category: c_int) -> usize;
}
@@ -263,7 +270,7 @@ extern "C" {
//
// Miscellaneous functions
//
extern "C" {
extern "C-unwind" {
pub fn lua_error(L: *mut lua_State) -> !;
pub fn lua_next(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_rawiter(L: *mut lua_State, idx: c_int, iter: c_int) -> c_int;
@@ -275,6 +282,7 @@ extern "C" {
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;
}
//
@@ -283,7 +291,7 @@ extern "C" {
pub const LUA_NOREF: c_int = -1;
pub const LUA_REFNIL: c_int = 0;
extern "C" {
extern "C-unwind" {
pub fn lua_ref(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_unref(L: *mut lua_State, r#ref: c_int);
}
@@ -322,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)]
@@ -359,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
@@ -420,9 +442,9 @@ pub unsafe fn lua_tostring(L: *mut lua_State, i: c_int) -> *const c_char {
const LUA_IDSIZE: usize = 256;
/// Type for functions to be called on debug events.
pub type lua_Hook = unsafe extern "C" fn(L: *mut lua_State, ar: *mut lua_Debug);
pub type lua_Hook = unsafe extern "C-unwind" fn(L: *mut lua_State, ar: *mut lua_Debug);
pub type lua_Coverage = unsafe extern "C" fn(
pub type lua_Coverage = unsafe extern "C-unwind" fn(
context: *mut c_void,
function: *const c_char,
linedefined: c_int,
@@ -431,7 +453,7 @@ pub type lua_Coverage = unsafe extern "C" fn(
size: usize,
);
extern "C" {
extern "C-unwind" {
pub fn lua_stackdepth(L: *mut lua_State) -> c_int;
pub fn lua_getinfo(
L: *mut lua_State,
@@ -489,23 +511,23 @@ pub struct lua_Callbacks {
pub userdata: *mut c_void,
/// gets called at safepoints (loop back edges, call/ret, gc) if set
pub interrupt: Option<unsafe extern "C" fn(L: *mut lua_State, gc: c_int)>,
pub interrupt: Option<unsafe extern "C-unwind" fn(L: *mut lua_State, gc: c_int)>,
/// gets called when an unprotected error is raised (if longjmp is used)
pub panic: Option<unsafe extern "C" fn(L: *mut lua_State, errcode: c_int)>,
pub panic: Option<unsafe extern "C-unwind" fn(L: *mut lua_State, errcode: c_int)>,
/// gets called when L is created (LP == parent) or destroyed (LP == NULL)
pub userthread: Option<unsafe extern "C" fn(LP: *mut lua_State, L: *mut lua_State)>,
pub userthread: Option<unsafe extern "C-unwind" fn(LP: *mut lua_State, L: *mut lua_State)>,
/// gets called when a string is created; returned atom can be retrieved via tostringatom
pub useratom: Option<unsafe extern "C" fn(s: *const c_char, l: usize) -> i16>,
pub useratom: Option<unsafe extern "C-unwind" fn(s: *const c_char, l: usize) -> i16>,
/// gets called when BREAK instruction is encountered
pub debugbreak: Option<unsafe extern "C" fn(L: *mut lua_State, ar: *mut lua_Debug)>,
pub debugbreak: Option<unsafe extern "C-unwind" fn(L: *mut lua_State, ar: *mut lua_Debug)>,
/// gets called after each instruction in single step mode
pub debugstep: Option<unsafe extern "C" fn(L: *mut lua_State, ar: *mut lua_Debug)>,
pub debugstep: Option<unsafe extern "C-unwind" fn(L: *mut lua_State, ar: *mut lua_Debug)>,
/// gets called when thread execution is interrupted by break in another thread
pub debuginterrupt: Option<unsafe extern "C" fn(L: *mut lua_State, ar: *mut lua_Debug)>,
pub debuginterrupt: Option<unsafe extern "C-unwind" fn(L: *mut lua_State, ar: *mut lua_Debug)>,
/// gets called when protected call results in an error
pub debugprotectederror: Option<unsafe extern "C" fn(L: *mut lua_State)>,
pub debugprotectederror: Option<unsafe extern "C-unwind" fn(L: *mut lua_State)>,
}
extern "C" {
+5 -2
View File
@@ -10,10 +10,11 @@ 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" {
extern "C-unwind" {
#[link_name = "luau_compile"]
pub fn luau_compile_(
source: *const c_char,
@@ -21,7 +22,9 @@ extern "C" {
options: *mut lua_CompileOptions,
outsize: *mut usize,
) -> *mut c_char;
}
extern "C" {
fn free(p: *mut c_void);
}
+1 -1
View File
@@ -4,7 +4,7 @@ use std::os::raw::c_int;
use super::lua::lua_State;
extern "C" {
extern "C-unwind" {
pub fn luau_codegen_supported() -> c_int;
pub fn luau_codegen_create(state: *mut lua_State);
pub fn luau_codegen_compile(state: *mut lua_State, idx: c_int);
+3 -1
View File
@@ -9,17 +9,19 @@ 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";
extern "C" {
extern "C-unwind" {
pub fn luaopen_base(L: *mut lua_State) -> c_int;
pub fn luaopen_coroutine(L: *mut lua_State) -> c_int;
pub fn luaopen_table(L: *mut lua_State) -> c_int;
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;
+2 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "mlua_derive"
version = "0.9.0-beta.2"
version = "0.9.0"
authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
edition = "2021"
description = "Procedural macros for the mlua crate."
@@ -19,6 +19,6 @@ quote = "1.0"
proc-macro2 = { version = "1.0", features = ["span-locations"] }
proc-macro-error = { version = "1.0", optional = true }
syn = { version = "2.0", features = ["full"] }
itertools = { version = "0.10", optional = true }
itertools = { version = "0.11", optional = true }
regex = { version = "1.4", optional = true }
once_cell = { version = "1.0", optional = true }
+32
View File
@@ -0,0 +1,32 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
pub fn from_lua(input: TokenStream) -> TokenStream {
let DeriveInput {
ident, generics, ..
} = parse_macro_input!(input as DeriveInput);
let where_clause = match &generics.where_clause {
Some(where_clause) => quote! { #where_clause, Self: 'static + Clone },
None => quote! { where Self: 'static + Clone },
};
let ident_str = ident.to_string();
quote! {
impl #generics ::mlua::FromLua<'_> for #ident #generics #where_clause {
#[inline]
fn from_lua(value: ::mlua::Value<'_>, lua: &'_ ::mlua::Lua) -> ::mlua::Result<Self> {
match value {
::mlua::Value::UserData(ud) => Ok(ud.borrow::<Self>()?.clone()),
_ => Err(::mlua::Error::FromLuaConversionError {
from: value.type_name(),
to: #ident_str,
message: None,
}),
}
}
}
}
.into()
}
+29 -15
View File
@@ -13,6 +13,7 @@ use {
#[derive(Default)]
struct ModuleAttributes {
name: Option<Ident>,
skip_memory_check: bool,
}
impl ModuleAttributes {
@@ -26,6 +27,11 @@ impl ModuleAttributes {
return Err(meta.error("`name` attribute must have a value"));
}
}
} else if meta.path.is_ident("skip_memory_check") {
if meta.value().is_ok() {
return Err(meta.error("`skip_memory_check` attribute have no values"));
}
self.skip_memory_check = true;
} else {
return Err(meta.error("unsupported module attribute"));
}
@@ -45,6 +51,7 @@ pub fn lua_module(attr: TokenStream, item: TokenStream) -> TokenStream {
let func_name = &func.sig.ident;
let module_name = args.name.unwrap_or_else(|| func_name.clone());
let ext_entrypoint_name = Ident::new(&format!("luaopen_{module_name}"), Span::call_site());
let skip_memory_check = args.skip_memory_check;
let wrapped = quote! {
::mlua::require_module_feature!();
@@ -52,10 +59,10 @@ pub fn lua_module(attr: TokenStream, item: TokenStream) -> TokenStream {
#func
#[no_mangle]
unsafe extern "C" fn #ext_entrypoint_name(state: *mut ::mlua::lua_State) -> ::std::os::raw::c_int {
::mlua::Lua::init_from_ptr(state)
.entrypoint1(#func_name)
.expect("cannot initialize module")
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);
lua.skip_memory_check(#skip_memory_check);
lua.entrypoint1(#func_name)
}
};
@@ -86,21 +93,20 @@ pub fn chunk(input: TokenStream) -> TokenStream {
let wrapped_code = quote! {{
use ::mlua::{AsChunk, ChunkMode, Lua, Result, Table};
use ::std::borrow::Cow;
use ::std::cell::Cell;
use ::std::io::Result as IoResult;
use ::std::sync::Mutex;
use ::std::marker::PhantomData;
struct InnerChunk<F: for <'a> FnOnce(&'a Lua) -> Result<Table<'a>>>(Mutex<Option<F>>);
struct InnerChunk<'lua, F: FnOnce(&'lua Lua) -> Result<Table<'lua>>>(Cell<Option<F>>, PhantomData<&'lua ()>);
impl<F> AsChunk<'static> for InnerChunk<F>
impl<'lua, F> AsChunk<'lua, 'static> for InnerChunk<'lua, F>
where
F: for <'a> FnOnce(&'a Lua) -> Result<Table<'a>>,
F: FnOnce(&'lua Lua) -> Result<Table<'lua>>,
{
fn environment<'lua>(&self, lua: &'lua Lua) -> Result<Option<Table<'lua>>> {
fn environment(&self, lua: &'lua Lua) -> Result<Option<Table<'lua>>> {
if #caps_len > 0 {
if let Ok(mut make_env) = self.0.lock() {
if let Some(make_env) = make_env.take() {
return make_env(lua).map(Some);
}
if let Some(make_env) = self.0.take() {
return make_env(lua).map(Some);
}
}
Ok(None)
@@ -115,7 +121,7 @@ pub fn chunk(input: TokenStream) -> TokenStream {
}
}
fn annotate<F: for<'a> FnOnce(&'a Lua) -> Result<Table<'a>>>(f: F) -> F { f }
fn annotate<'a, F: FnOnce(&'a Lua) -> Result<Table<'a>>>(f: F) -> F { f }
let make_env = annotate(move |lua: &Lua| -> Result<Table> {
let globals = lua.globals();
@@ -131,13 +137,21 @@ pub fn chunk(input: TokenStream) -> TokenStream {
Ok(env)
});
InnerChunk(Mutex::new(Some(make_env)))
InnerChunk(Cell::new(Some(make_env)), PhantomData)
}};
wrapped_code.into()
}
#[cfg(feature = "macros")]
#[proc_macro_derive(FromLua)]
pub fn from_lua(input: TokenStream) -> TokenStream {
from_lua::from_lua(input)
}
#[cfg(feature = "macros")]
mod chunk;
#[cfg(feature = "macros")]
mod from_lua;
#[cfg(feature = "macros")]
mod token;
+58 -47
View File
@@ -11,14 +11,11 @@ use crate::lua::Lua;
use crate::table::Table;
use crate::value::{FromLuaMulti, IntoLua, IntoLuaMulti};
#[cfg(feature = "async")]
use futures_util::future::{self, LocalBoxFuture};
/// Trait for types [loadable by Lua] and convertible to a [`Chunk`]
///
/// [loadable by Lua]: https://www.lua.org/manual/5.4/manual.html#3.3.2
/// [`Chunk`]: crate::Chunk
pub trait AsChunk<'a> {
pub trait AsChunk<'lua, 'a> {
/// Returns optional chunk name
fn name(&self) -> Option<StdString> {
None
@@ -27,7 +24,7 @@ pub trait AsChunk<'a> {
/// Returns optional chunk [environment]
///
/// [environment]: https://www.lua.org/manual/5.4/manual.html#2.2
fn environment<'lua>(&self, lua: &'lua Lua) -> Result<Option<Table<'lua>>> {
fn environment(&self, lua: &'lua Lua) -> Result<Option<Table<'lua>>> {
let _lua = lua; // suppress warning
Ok(None)
}
@@ -41,43 +38,43 @@ pub trait AsChunk<'a> {
fn source(self) -> IoResult<Cow<'a, [u8]>>;
}
impl<'a> AsChunk<'a> for &'a str {
impl<'a> AsChunk<'_, 'a> for &'a str {
fn source(self) -> IoResult<Cow<'a, [u8]>> {
Ok(Cow::Borrowed(self.as_ref()))
}
}
impl AsChunk<'static> for StdString {
impl AsChunk<'_, 'static> for StdString {
fn source(self) -> IoResult<Cow<'static, [u8]>> {
Ok(Cow::Owned(self.into_bytes()))
}
}
impl<'a> AsChunk<'a> for &'a StdString {
impl<'a> AsChunk<'_, 'a> for &'a StdString {
fn source(self) -> IoResult<Cow<'a, [u8]>> {
Ok(Cow::Borrowed(self.as_bytes()))
}
}
impl<'a> AsChunk<'a> for &'a [u8] {
impl<'a> AsChunk<'_, 'a> for &'a [u8] {
fn source(self) -> IoResult<Cow<'a, [u8]>> {
Ok(Cow::Borrowed(self))
}
}
impl AsChunk<'static> for Vec<u8> {
impl AsChunk<'_, 'static> for Vec<u8> {
fn source(self) -> IoResult<Cow<'static, [u8]>> {
Ok(Cow::Owned(self))
}
}
impl<'a> AsChunk<'a> for &'a Vec<u8> {
impl<'a> AsChunk<'_, 'a> for &'a Vec<u8> {
fn source(self) -> IoResult<Cow<'a, [u8]>> {
Ok(Cow::Borrowed(self.as_ref()))
}
}
impl AsChunk<'static> for &Path {
impl AsChunk<'_, 'static> for &Path {
fn name(&self) -> Option<StdString> {
Some(format!("@{}", self.display()))
}
@@ -87,7 +84,7 @@ impl AsChunk<'static> for &Path {
}
}
impl AsChunk<'static> for PathBuf {
impl AsChunk<'_, 'static> for PathBuf {
fn name(&self) -> Option<StdString> {
Some(format!("@{}", self.display()))
}
@@ -128,12 +125,21 @@ pub struct Compiler {
coverage_level: u8,
vector_lib: Option<String>,
vector_ctor: Option<String>,
vector_type: Option<String>,
mutable_globals: Vec<String>,
}
#[cfg(any(feature = "luau", doc))]
impl Default for Compiler {
fn default() -> Self {
Self::new()
}
}
#[cfg(any(feature = "luau", doc))]
impl Compiler {
/// Creates Luau compiler instance with default options
pub const fn new() -> Self {
// Defaults are taken from luacode.h
Compiler {
optimization_level: 1,
@@ -141,17 +147,10 @@ impl Default for Compiler {
coverage_level: 0,
vector_lib: None,
vector_ctor: None,
vector_type: None,
mutable_globals: Vec::new(),
}
}
}
#[cfg(any(feature = "luau", doc))]
impl Compiler {
/// Creates Luau compiler instance with default options
pub fn new() -> Self {
Compiler::default()
}
/// Sets Luau compiler optimization level.
///
@@ -159,7 +158,8 @@ impl Compiler {
/// * 0 - no optimization
/// * 1 - baseline optimization level that doesn't prevent debuggability (default)
/// * 2 - includes optimizations that harm debuggability such as inlining
pub fn set_optimization_level(mut self, level: u8) -> Self {
#[must_use]
pub const fn set_optimization_level(mut self, level: u8) -> Self {
self.optimization_level = level;
self
}
@@ -170,7 +170,8 @@ impl Compiler {
/// * 0 - no debugging support
/// * 1 - line info & function names only; sufficient for backtraces (default)
/// * 2 - full debug info with local & upvalue names; necessary for debugger
pub fn set_debug_level(mut self, level: u8) -> Self {
#[must_use]
pub const fn set_debug_level(mut self, level: u8) -> Self {
self.debug_level = level;
self
}
@@ -181,26 +182,37 @@ impl Compiler {
/// * 0 - no code coverage support (default)
/// * 1 - statement coverage
/// * 2 - statement and expression coverage (verbose)
pub fn set_coverage_level(mut self, level: u8) -> Self {
#[must_use]
pub const fn set_coverage_level(mut self, level: u8) -> Self {
self.coverage_level = level;
self
}
#[doc(hidden)]
pub fn set_vector_lib(mut self, lib: Option<String>) -> Self {
self.vector_lib = lib;
#[must_use]
pub fn set_vector_lib(mut self, lib: impl Into<String>) -> Self {
self.vector_lib = Some(lib.into());
self
}
#[doc(hidden)]
pub fn set_vector_ctor(mut self, ctor: Option<String>) -> Self {
self.vector_ctor = ctor;
#[must_use]
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
}
/// Sets a list of globals that are mutable.
///
/// It disables the import optimization for fields accessed through these.
#[must_use]
pub fn set_mutable_globals(mut self, globals: Vec<String>) -> Self {
self.mutable_globals = globals;
self
@@ -217,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
@@ -228,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 {
@@ -241,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)
@@ -312,8 +328,8 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
/// [`exec`]: #method.exec
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn exec_async(self) -> LocalBoxFuture<'lua, Result<()>> {
self.call_async(())
pub async fn exec_async(self) -> Result<()> {
self.call_async(()).await
}
/// Evaluate the chunk as either an expression or block.
@@ -344,17 +360,16 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
/// [`eval`]: #method.eval
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn eval_async<'fut, R>(self) -> LocalBoxFuture<'fut, Result<R>>
pub async fn eval_async<R>(self) -> Result<R>
where
'lua: 'fut,
R: FromLuaMulti<'lua> + 'fut,
R: FromLuaMulti<'lua> + 'lua,
{
if self.detect_mode() == ChunkMode::Binary {
self.call_async(())
self.call_async(()).await
} else if let Ok(function) = self.to_expression() {
function.call_async(())
function.call_async(()).await
} else {
self.call_async(())
self.call_async(()).await
}
}
@@ -374,16 +389,12 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
/// [`call`]: #method.call
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn call_async<'fut, A, R>(self, args: A) -> LocalBoxFuture<'fut, Result<R>>
pub async fn call_async<A, R>(self, args: A) -> Result<R>
where
'lua: 'fut,
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut,
R: FromLuaMulti<'lua> + 'lua,
{
match self.into_function() {
Ok(func) => func.call_async(args),
Err(e) => Box::pin(future::err(e)),
}
self.into_function()?.call_async(args).await
}
/// Load this chunk into a regular `Function`.
@@ -470,7 +481,7 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
fn to_expression(&self) -> Result<Function<'lua>> {
// We assume that mode is Text
let source = self.source.as_ref();
let source = source.map_err(|err| Error::RuntimeError(err.to_string()))?;
let source = source.map_err(Error::runtime)?;
let source = Self::expression_source(source);
// We don't need to compile source if no compiler options set
#[cfg(feature = "luau")]
@@ -504,7 +515,7 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
}
fn convert_name(name: String) -> Result<CString> {
CString::new(name).map_err(|err| Error::RuntimeError(format!("invalid name: {err}")))
CString::new(name).map_err(|err| Error::runtime(format!("invalid name: {err}")))
}
fn expression_source(source: &[u8]) -> Vec<u8> {
+93 -44
View File
@@ -3,13 +3,15 @@ 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;
use crate::error::{Error, Result};
use crate::function::{Function, WrappedFunction};
use crate::function::Function;
use crate::lua::Lua;
use crate::string::String;
use crate::table::Table;
@@ -21,9 +23,6 @@ use crate::value::{FromLua, IntoLua, Nil, Value};
#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))]
use crate::{function::OwnedFunction, table::OwnedTable, userdata::OwnedAnyUserData};
#[cfg(feature = "async")]
use crate::function::WrappedAsyncFunction;
impl<'lua> IntoLua<'lua> for Value<'lua> {
#[inline]
fn into_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
@@ -52,7 +51,7 @@ impl<'lua> FromLua<'lua> for String<'lua> {
lua.coerce_string(value)?
.ok_or_else(|| Error::FromLuaConversionError {
from: ty,
to: "String",
to: "string",
message: Some("expected string or number".to_string()),
})
}
@@ -136,21 +135,6 @@ impl<'lua> FromLua<'lua> for OwnedFunction {
}
}
impl<'lua> IntoLua<'lua> for WrappedFunction<'lua> {
#[inline]
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
lua.create_callback(self.0).map(Value::Function)
}
}
#[cfg(feature = "async")]
impl<'lua> IntoLua<'lua> for WrappedAsyncFunction<'lua> {
#[inline]
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
lua.create_async_callback(self.0).map(Value::Function)
}
}
impl<'lua> IntoLua<'lua> for Thread<'lua> {
#[inline]
fn into_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
@@ -198,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,
)))
}
}
@@ -211,7 +198,7 @@ impl<'lua> FromLua<'lua> for OwnedAnyUserData {
}
}
impl<'lua, T: 'static + MaybeSend + UserData> IntoLua<'lua> for T {
impl<'lua, T: UserData + MaybeSend + 'static> IntoLua<'lua> for T {
#[inline]
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::UserData(lua.create_userdata(self)?))
@@ -244,7 +231,7 @@ impl<'lua> FromLua<'lua> for Error {
fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result<Error> {
match value {
Value::Error(err) => Ok(err),
val => Ok(Error::RuntimeError(
val => Ok(Error::runtime(
lua.coerce_string(val)?
.and_then(|s| Some(s.to_str().ok()?.to_owned()))
.unwrap_or_else(|| "<unprintable error>".to_owned()),
@@ -292,11 +279,39 @@ impl<'lua> FromLua<'lua> for LightUserData {
}
}
#[cfg(feature = "luau")]
impl<'lua> IntoLua<'lua> for crate::types::Vector {
#[inline]
fn into_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Vector(self))
}
}
#[cfg(feature = "luau")]
impl<'lua> FromLua<'lua> for crate::types::Vector {
#[inline]
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<Self> {
match value {
Value::Vector(v) => Ok(v),
_ => Err(Error::FromLuaConversionError {
from: value.type_name(),
to: "vector",
message: None,
}),
}
}
}
impl<'lua> IntoLua<'lua> for StdString {
#[inline]
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 {
@@ -313,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 {
@@ -320,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> {
@@ -412,7 +451,7 @@ impl<'lua> FromLua<'lua> for BString {
lua.coerce_string(value)?
.ok_or_else(|| Error::FromLuaConversionError {
from: ty,
to: "String",
to: "BString",
message: Some("expected string or number".to_string()),
})?
.as_bytes()
@@ -428,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 {
@@ -533,7 +587,7 @@ lua_convert_float!(f64);
impl<'lua, T> IntoLua<'lua> for &[T]
where
T: Clone + IntoLua<'lua>,
T: IntoLua<'lua> + Clone,
{
#[inline]
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
@@ -561,21 +615,22 @@ where
fn from_lua(value: Value<'lua>, _lua: &'lua Lua) -> Result<Self> {
match value {
#[cfg(feature = "luau")]
Value::Vector(x, y, z) if N == 3 => Ok(mlua_expect!(
vec![
T::from_lua(Value::Number(x as _), _lua)?,
T::from_lua(Value::Number(y as _), _lua)?,
T::from_lua(Value::Number(z as _), _lua)?,
]
.try_into()
.map_err(|_| ()),
"cannot convert vector to array"
)),
#[rustfmt::skip]
Value::Vector(v) if N == crate::types::Vector::SIZE => unsafe {
use std::{mem, ptr};
let mut arr: [mem::MaybeUninit<T>; N] = mem::MaybeUninit::uninit().assume_init();
ptr::write(arr[0].as_mut_ptr() , T::from_lua(Value::Number(v.x() as _), _lua)?);
ptr::write(arr[1].as_mut_ptr(), T::from_lua(Value::Number(v.y() as _), _lua)?);
ptr::write(arr[2].as_mut_ptr(), T::from_lua(Value::Number(v.z() as _), _lua)?);
#[cfg(feature = "luau-vector4")]
ptr::write(arr[3].as_mut_ptr(), T::from_lua(Value::Number(v.w() as _), _lua)?);
Ok(mem::transmute_copy(&arr))
},
Value::Table(table) => {
let vec = table.sequence_values().collect::<Result<Vec<_>>>()?;
vec.try_into()
.map_err(|vec: Vec<T>| Error::FromLuaConversionError {
from: "Table",
from: "table",
to: "Array",
message: Some(format!("expected table of length {}, got {}", N, vec.len())),
})
@@ -614,12 +669,6 @@ impl<'lua, T: FromLua<'lua>> FromLua<'lua> for Vec<T> {
#[inline]
fn from_lua(value: Value<'lua>, _lua: &'lua Lua) -> Result<Self> {
match value {
#[cfg(feature = "luau")]
Value::Vector(x, y, z) => Ok(vec![
T::from_lua(Value::Number(x as _), _lua)?,
T::from_lua(Value::Number(y as _), _lua)?,
T::from_lua(Value::Number(z as _), _lua)?,
]),
Value::Table(table) => table.sequence_values().collect(),
_ => Err(Error::FromLuaConversionError {
from: value.type_name(),
@@ -691,7 +740,7 @@ impl<'lua, T: Eq + Hash + FromLua<'lua>, S: BuildHasher + Default> FromLua<'lua>
#[inline]
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<Self> {
match value {
Value::Table(table) if table.len()? > 0 => table.sequence_values().collect(),
Value::Table(table) if table.raw_len() > 0 => table.sequence_values().collect(),
Value::Table(table) => table
.pairs::<T, Value<'lua>>()
.map(|res| res.map(|(k, _)| k))
@@ -718,7 +767,7 @@ impl<'lua, T: Ord + FromLua<'lua>> FromLua<'lua> for BTreeSet<T> {
#[inline]
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<Self> {
match value {
Value::Table(table) if table.len()? > 0 => table.sequence_values().collect(),
Value::Table(table) if table.raw_len() > 0 => table.sequence_values().collect(),
Value::Table(table) => table
.pairs::<T, Value<'lua>>()
.map(|res| res.map(|(k, _)| k))
+14 -13
View File
@@ -47,11 +47,6 @@ pub enum Error {
/// This error can only happen when Lua state was not created by us and does not have the
/// custom allocator attached.
MemoryLimitNotAvailable,
/// Main thread is not available.
///
/// This error can only happen in Lua5.1/LuaJIT module mode, when module loaded within a coroutine.
/// These Lua versions does not have `LUA_RIDX_MAINTHREAD` registry key.
MainThreadNotAvailable,
/// A mutable callback has triggered Lua code that has called the same mutable callback again.
///
/// This is an error because a mutable callback can only be borrowed mutably once.
@@ -69,7 +64,7 @@ pub enum Error {
/// called with a huge number of arguments, or a rust callback returns a huge number of return
/// values.
StackError,
/// Too many arguments to `Function::bind`
/// Too many arguments to `Function::bind`.
BindError,
/// Bad argument received from Lua (usually when calling a function).
///
@@ -130,7 +125,7 @@ pub enum Error {
///
/// [`AnyUserData`]: crate::AnyUserData
UserDataDestructed,
/// An [`AnyUserData`] immutable borrow failed because it is already borrowed mutably.
/// An [`AnyUserData`] immutable borrow failed.
///
/// This error can occur when a method on a [`UserData`] type calls back into Lua, which then
/// tries to call a method on the same [`UserData`] type. Consider restructuring your API to
@@ -139,7 +134,7 @@ pub enum Error {
/// [`AnyUserData`]: crate::AnyUserData
/// [`UserData`]: crate::UserData
UserDataBorrowError,
/// An [`AnyUserData`] mutable borrow failed because it is already borrowed.
/// An [`AnyUserData`] mutable borrow failed.
///
/// This error can occur when a method on a [`UserData`] type calls back into Lua, which then
/// tries to call a method on the same [`UserData`] type. Consider restructuring your API to
@@ -226,9 +221,6 @@ impl fmt::Display for Error {
Error::MemoryLimitNotAvailable => {
write!(fmt, "setting memory limit is not available")
}
Error::MainThreadNotAvailable => {
write!(fmt, "main thread is not available in Lua 5.1")
}
Error::RecursiveMutCallback => write!(fmt, "mutable callback called recursively"),
Error::CallbackDestructed => write!(
fmt,
@@ -270,8 +262,8 @@ impl fmt::Display for Error {
Error::CoroutineInactive => write!(fmt, "cannot resume inactive coroutine"),
Error::UserDataTypeMismatch => write!(fmt, "userdata is not expected type"),
Error::UserDataDestructed => write!(fmt, "userdata has been destructed"),
Error::UserDataBorrowError => write!(fmt, "userdata already mutably borrowed"),
Error::UserDataBorrowMutError => write!(fmt, "userdata already borrowed"),
Error::UserDataBorrowError => write!(fmt, "error borrowing userdata"),
Error::UserDataBorrowMutError => write!(fmt, "error mutably borrowing userdata"),
Error::MetaMethodRestricted(ref method) => write!(fmt, "metamethod {method} is restricted"),
Error::MetaMethodTypeError { ref method, type_name, ref message } => {
write!(fmt, "metamethod {method} has unsupported type {type_name}")?;
@@ -345,7 +337,14 @@ impl StdError for Error {
}
impl Error {
/// Creates a new `RuntimeError` with the given message.
#[inline]
pub fn runtime<S: fmt::Display>(message: S) -> Self {
Error::RuntimeError(message.to_string())
}
/// Wraps an external error object.
#[inline]
pub fn external<T: Into<Box<dyn StdError + Send + Sync>>>(err: T) -> Self {
Error::ExternalError(err.into().into())
}
@@ -387,6 +386,7 @@ impl Error {
}
}
/// Trait for converting [`std::error::Error`] into Lua [`Error`].
pub trait ExternalError {
fn into_lua_err(self) -> Error;
}
@@ -397,6 +397,7 @@ impl<E: Into<Box<dyn StdError + Send + Sync>>> ExternalError for E {
}
}
/// Trait for converting [`std::result::Result`] into Lua [`Result`].
pub trait ExternalResult<T> {
fn into_lua_err(self) -> Result<T>;
}
+81 -96
View File
@@ -6,18 +6,18 @@ 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, pop_error, ptr_to_cstr_bytes, StackGuard,
assert_stack, check_stack, linenumber_to_usize, pop_error, ptr_to_lossy_str, ptr_to_str,
StackGuard,
};
use crate::value::{FromLuaMulti, IntoLua, IntoLuaMulti};
use crate::value::{FromLuaMulti, IntoLua, IntoLuaMulti, Value};
#[cfg(feature = "async")]
use {
crate::types::AsyncCallback,
futures_util::future::{self, Future, LocalBoxFuture, TryFutureExt},
futures_util::future::{self, Future},
};
/// Handle to an internal Lua function.
@@ -52,24 +52,22 @@ impl OwnedFunction {
/// [`Lua Debug Interface`]: https://www.lua.org/manual/5.4/manual.html#4.7
#[derive(Clone, Debug)]
pub struct FunctionInfo {
/// A (reasonable) name of the function.
/// A (reasonable) name of the function (`None` if the name cannot be found).
pub name: Option<String>,
/// Explains the `name` field ("global", "local", "method", "field", "upvalue", or "").
/// Explains the `name` field (can be `global`/`local`/`method`/`field`/`upvalue`/etc).
///
/// Always `None` for Luau.
pub name_what: Option<String>,
/// A string "Lua" if the function is a Lua function, "C" if it is a C function, "main" if it is the main part of a chunk.
pub what: Option<String>,
/// The source of the chunk that created the function.
pub source: Option<Vec<u8>>,
/// A "printable" version of source, to be used in error messages.
pub short_src: Option<Vec<u8>>,
pub name_what: Option<&'static str>,
/// A string `Lua` if the function is a Lua function, `C` if it is a C function, `main` if it is the main part of a chunk.
pub what: &'static str,
/// Source of the chunk that created the function.
pub source: Option<String>,
/// A "printable" version of `source`, to be used in error messages.
pub short_src: Option<String>,
/// The line number where the definition of the function starts.
pub line_defined: i32,
/// The line number where the definition of the function ends.
///
/// Always `-1` for Luau.
pub last_line_defined: i32,
pub line_defined: Option<usize>,
/// The line number where the definition of the function ends (not set by Luau).
pub last_line_defined: Option<usize>,
}
/// Luau function coverage snapshot.
@@ -77,7 +75,7 @@ pub struct FunctionInfo {
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CoverageInfo {
pub function: Option<std::string::String>,
pub function: Option<String>,
pub line_defined: i32,
pub depth: i32,
pub hits: Vec<i32>,
@@ -127,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,
@@ -168,14 +157,13 @@ impl<'lua> Function<'lua> {
///
/// ```
/// use std::time::Duration;
/// use futures_timer::Delay;
/// # use mlua::{Lua, Result};
/// # #[tokio::main]
/// # async fn main() -> Result<()> {
/// # let lua = Lua::new();
///
/// let sleep = lua.create_async_function(move |_lua, n: u64| async move {
/// Delay::new(Duration::from_millis(n)).await;
/// tokio::time::sleep(Duration::from_millis(n)).await;
/// Ok(())
/// })?;
///
@@ -188,21 +176,18 @@ impl<'lua> Function<'lua> {
/// [`AsyncThread`]: crate::AsyncThread
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn call_async<'fut, A, R>(&self, args: A) -> LocalBoxFuture<'fut, Result<R>>
pub fn call_async<A, R>(&self, args: A) -> impl Future<Output = Result<R>> + 'lua
where
'lua: 'fut,
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut,
R: FromLuaMulti<'lua> + 'lua,
{
let lua = self.0.lua;
match lua.create_recycled_thread(self) {
Ok(t) => {
let mut t = t.into_async(args);
t.set_recyclable(true);
Box::pin(t)
}
Err(e) => Box::pin(future::err(e)),
}
let thread_res = lua.create_recycled_thread(self).map(|th| {
let mut th = th.into_async(args);
th.set_recyclable(true);
th
});
async move { thread_res?.await }
}
/// Returns a function that, when called, calls `self`, passing `args` as the first set of
@@ -233,7 +218,7 @@ impl<'lua> Function<'lua> {
/// # }
/// ```
pub fn bind<A: IntoLuaMulti<'lua>>(&self, args: A) -> Result<Function<'lua>> {
unsafe extern "C" fn args_wrapper_impl(state: *mut ffi::lua_State) -> c_int {
unsafe extern "C-unwind" fn args_wrapper_impl(state: *mut ffi::lua_State) -> c_int {
let nargs = ffi::lua_gettop(state);
let nbinds = ffi::lua_tointeger(state, ffi::lua_upvalueindex(1)) as c_int;
ffi::luaL_checkstack(state, nbinds, ptr::null());
@@ -303,17 +288,7 @@ impl<'lua> Function<'lua> {
assert_stack(state, 1);
lua.push_ref(&self.0);
let mut ar: ffi::lua_Debug = mem::zeroed();
#[cfg(not(feature = "luau"))]
{
ffi::lua_pushvalue(state, -1);
ffi::lua_getinfo(state, cstr!(">S"), &mut ar);
}
#[cfg(feature = "luau")]
ffi::lua_getinfo(state, -1, cstr!("s"), &mut ar);
if ptr_to_cstr_bytes(ar.what) == Some(b"C") {
if ffi::lua_iscfunction(state, -1) != 0 {
return None;
}
@@ -350,17 +325,7 @@ impl<'lua> Function<'lua> {
check_stack(state, 2)?;
lua.push_ref(&self.0);
let mut ar: ffi::lua_Debug = mem::zeroed();
#[cfg(not(feature = "luau"))]
{
ffi::lua_pushvalue(state, -1);
ffi::lua_getinfo(state, cstr!(">S"), &mut ar);
}
#[cfg(feature = "luau")]
ffi::lua_getinfo(state, -1, cstr!("s"), &mut ar);
if ptr_to_cstr_bytes(ar.what) == Some(b"C") {
if ffi::lua_iscfunction(state, -1) != 0 {
return Ok(false);
}
@@ -414,23 +379,25 @@ impl<'lua> Function<'lua> {
mlua_assert!(res != 0, "lua_getinfo failed with `>Sn`");
FunctionInfo {
name: ptr_to_cstr_bytes(ar.name).map(|s| String::from_utf8_lossy(s).into_owned()),
name: ptr_to_lossy_str(ar.name).map(|s| s.into_owned()),
#[cfg(not(feature = "luau"))]
name_what: ptr_to_cstr_bytes(ar.namewhat)
.map(|s| String::from_utf8_lossy(s).into_owned()),
name_what: match ptr_to_str(ar.namewhat) {
Some("") => None,
val => val,
},
#[cfg(feature = "luau")]
name_what: None,
what: ptr_to_cstr_bytes(ar.what).map(|s| String::from_utf8_lossy(s).into_owned()),
source: ptr_to_cstr_bytes(ar.source).map(|s| s.to_vec()),
what: ptr_to_str(ar.what).unwrap_or("main"),
source: ptr_to_lossy_str(ar.source).map(|s| s.into_owned()),
#[cfg(not(feature = "luau"))]
short_src: ptr_to_cstr_bytes(ar.short_src.as_ptr()).map(|s| s.to_vec()),
short_src: ptr_to_lossy_str(ar.short_src.as_ptr()).map(|s| s.into_owned()),
#[cfg(feature = "luau")]
short_src: ptr_to_cstr_bytes(ar.short_src).map(|s| s.to_vec()),
line_defined: ar.linedefined,
short_src: ptr_to_lossy_str(ar.short_src).map(|s| s.into_owned()),
line_defined: linenumber_to_usize(ar.linedefined),
#[cfg(not(feature = "luau"))]
last_line_defined: ar.lastlinedefined,
last_line_defined: linenumber_to_usize(ar.lastlinedefined),
#[cfg(feature = "luau")]
last_line_defined: -1,
last_line_defined: None,
}
}
}
@@ -446,7 +413,7 @@ impl<'lua> Function<'lua> {
#[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub fn dump(&self, strip: bool) -> Vec<u8> {
unsafe extern "C" fn writer(
unsafe extern "C-unwind" fn writer(
_state: *mut ffi::lua_State,
buf: *const c_void,
buf_len: usize,
@@ -484,7 +451,7 @@ impl<'lua> Function<'lua> {
/// Requires `feature = "luau"`
///
/// [`Compiler::set_coverage_level`]: crate::chunk::Compiler::set_coverage_level
#[cfg(any(feature = "luau", docsrs))]
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn coverage<F>(&self, mut func: F)
where
@@ -493,7 +460,7 @@ impl<'lua> Function<'lua> {
use std::ffi::CStr;
use std::os::raw::c_char;
unsafe extern "C" fn callback<F: FnMut(CoverageInfo)>(
unsafe extern "C-unwind" fn callback<F: FnMut(CoverageInfo)>(
data: *mut c_void,
function: *const c_char,
line_defined: c_int,
@@ -564,12 +531,12 @@ impl OwnedFunction {
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
#[inline]
pub fn call_async<'lua, A, R>(&'lua self, args: A) -> LocalBoxFuture<'lua, Result<R>>
pub async fn call_async<'lua, A, R>(&'lua self, args: A) -> Result<R>
where
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'lua,
{
self.to_ref().call_async(args)
self.to_ref().call_async(args).await
}
}
@@ -587,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)
}))
}
@@ -601,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)
}))
}
@@ -619,16 +588,32 @@ 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)),
};
Box::pin(func(lua, args).and_then(move |ret| future::ready(ret.into_lua_multi(lua))))
let fut = func(lua, args);
Box::pin(async move { fut.await?.push_into_stack_multi(lua) })
}))
}
}
impl<'lua> IntoLua<'lua> for WrappedFunction<'lua> {
#[inline]
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
lua.create_callback(self.0).map(Value::Function)
}
}
#[cfg(feature = "async")]
impl<'lua> IntoLua<'lua> for WrappedAsyncFunction<'lua> {
#[inline]
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
lua.create_async_callback(self.0).map(Value::Function)
}
}
#[cfg(test)]
mod assertions {
use super::*;
+35 -19
View File
@@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::cell::UnsafeCell;
#[cfg(not(feature = "luau"))]
use std::ops::{BitOr, BitOrAssign};
@@ -6,7 +7,7 @@ use std::os::raw::c_int;
use ffi::lua_Debug;
use crate::lua::Lua;
use crate::util::ptr_to_cstr_bytes;
use crate::util::{linenumber_to_usize, ptr_to_lossy_str, ptr_to_str};
/// Contains information about currently executing Lua code.
///
@@ -78,9 +79,12 @@ impl<'lua> Debug<'lua> {
);
DebugNames {
name: ptr_to_cstr_bytes((*self.ar.get()).name),
name: ptr_to_lossy_str((*self.ar.get()).name),
#[cfg(not(feature = "luau"))]
name_what: ptr_to_cstr_bytes((*self.ar.get()).namewhat),
name_what: match ptr_to_str((*self.ar.get()).namewhat) {
Some("") => None,
val => val,
},
#[cfg(feature = "luau")]
name_what: None,
}
@@ -102,15 +106,17 @@ impl<'lua> Debug<'lua> {
);
DebugSource {
source: ptr_to_cstr_bytes((*self.ar.get()).source),
source: ptr_to_lossy_str((*self.ar.get()).source),
#[cfg(not(feature = "luau"))]
short_src: ptr_to_cstr_bytes((*self.ar.get()).short_src.as_ptr()),
short_src: ptr_to_lossy_str((*self.ar.get()).short_src.as_ptr()),
#[cfg(feature = "luau")]
short_src: ptr_to_cstr_bytes((*self.ar.get()).short_src),
line_defined: (*self.ar.get()).linedefined,
short_src: ptr_to_lossy_str((*self.ar.get()).short_src),
line_defined: linenumber_to_usize((*self.ar.get()).linedefined),
#[cfg(not(feature = "luau"))]
last_line_defined: (*self.ar.get()).lastlinedefined,
what: ptr_to_cstr_bytes((*self.ar.get()).what),
last_line_defined: linenumber_to_usize((*self.ar.get()).lastlinedefined),
#[cfg(feature = "luau")]
last_line_defined: None,
what: ptr_to_str((*self.ar.get()).what).unwrap_or("main"),
}
}
}
@@ -210,18 +216,26 @@ pub enum DebugEvent {
#[derive(Clone, Debug)]
pub struct DebugNames<'a> {
pub name: Option<&'a [u8]>,
pub name_what: Option<&'a [u8]>,
/// A (reasonable) name of the function (`None` if the name cannot be found).
pub name: Option<Cow<'a, str>>,
/// Explains the `name` field (can be `global`/`local`/`method`/`field`/`upvalue`/etc).
///
/// Always `None` for Luau.
pub name_what: Option<&'static str>,
}
#[derive(Clone, Debug)]
pub struct DebugSource<'a> {
pub source: Option<&'a [u8]>,
pub short_src: Option<&'a [u8]>,
pub line_defined: i32,
#[cfg(not(feature = "luau"))]
pub last_line_defined: i32,
pub what: Option<&'a [u8]>,
/// Source of the chunk that created the function.
pub source: Option<Cow<'a, str>>,
/// A "printable" version of `source`, to be used in error messages.
pub short_src: Option<Cow<'a, str>>,
/// The line number where the definition of the function starts.
pub line_defined: Option<usize>,
/// The line number where the definition of the function ends (not set by Luau).
pub last_line_defined: Option<usize>,
/// A string `Lua` if the function is a Lua function, `C` if it is a C function, `main` if it is the main part of a chunk.
pub what: &'static str,
}
#[derive(Copy, Clone, Debug)]
@@ -339,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,
}
}
}
+32 -5
View File
@@ -103,7 +103,7 @@ mod value;
pub mod prelude;
pub use ffi::{lua_CFunction, lua_State};
pub use ffi::{self, lua_CFunction, lua_State};
pub use crate::chunk::{AsChunk, Chunk, ChunkMode};
pub use crate::error::{Error, ErrorContext, ExternalError, ExternalResult, Result};
@@ -122,7 +122,7 @@ pub use crate::userdata::{
UserDataRef, UserDataRefMut,
};
pub use crate::userdata_ext::AnyUserDataExt;
pub use crate::userdata_impl::UserDataRegistrar;
pub use crate::userdata_impl::UserDataRegistry;
pub use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, MultiValue, Nil, Value};
#[cfg(not(feature = "luau"))]
@@ -130,7 +130,11 @@ pub use crate::hook::HookTriggers;
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub use crate::{chunk::Compiler, function::CoverageInfo, types::VmState};
pub use crate::{
chunk::Compiler,
function::CoverageInfo,
types::{Vector, VmState},
};
#[cfg(feature = "async")]
pub use crate::thread::AsyncThread;
@@ -153,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.
@@ -212,6 +217,14 @@ pub use crate::{
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
pub use mlua_derive::chunk;
/// Derive [`FromLua`] for a Rust type.
///
/// Current implementation generate code that takes [`UserData`] value, borrow it (of the Rust type)
/// and clone.
#[cfg(feature = "macros")]
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
pub use mlua_derive::FromLua;
/// Registers Lua module entrypoint.
///
/// You can register multiple entrypoints as required.
@@ -231,7 +244,7 @@ pub use mlua_derive::chunk;
///
/// You can also pass options to the attribute:
///
/// name - name of the module, defaults to the name of the function
/// * name - name of the module, defaults to the name of the function
///
/// ```ignore
/// #[mlua::lua_module(name = "alt_module")]
@@ -240,6 +253,20 @@ pub use mlua_derive::chunk;
/// }
/// ```
///
/// * skip_memory_check - skip memory allocation checks for some operations.
///
/// In module mode, mlua runs in unknown environment and cannot say are there any memory
/// limits or not. As result, some operations that require memory allocation runs in
/// protected mode. Setting this attribute will improve performance of such operations
/// with risk of having uncaught exceptions and memory leaks.
///
/// ```ignore
/// #[mlua::lua_module(skip_memory_check)]
/// fn my_module(lua: &Lua) -> Result<Table> {
/// ...
/// }
/// ```
///
#[cfg(any(feature = "module", docsrs))]
#[cfg_attr(docsrs, doc(cfg(feature = "module")))]
pub use mlua_derive::lua_module;
+448 -279
View File
File diff suppressed because it is too large Load Diff
+20 -63
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,9 +24,14 @@ impl Lua {
Ok(())
}
pub(crate) fn disable_c_modules(&self) -> Result<()> {
package::disable_dylibs(self);
Ok(())
}
}
unsafe extern "C" fn lua_collectgarbage(state: *mut ffi::lua_State) -> c_int {
unsafe extern "C-unwind" fn lua_collectgarbage(state: *mut ffi::lua_State) -> c_int {
let option = ffi::luaL_optstring(state, 1, cstr!("collect"));
let option = CStr::from_ptr(option);
let arg = ffi::luaL_optinteger(state, 2, 0);
@@ -69,63 +68,21 @@ unsafe extern "C" fn lua_collectgarbage(state: *mut ffi::lua_State) -> c_int {
}
}
fn lua_require(lua: &Lua, name: Option<StdString>) -> Result<Value> {
let name = name.ok_or_else(|| Error::RuntimeError("invalid module name".into()))?;
// 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::RuntimeError(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" fn lua_vector(state: *mut ffi::lua_State) -> c_int {
unsafe extern "C-unwind" fn lua_vector(state: *mut ffi::lua_State) -> c_int {
let x = ffi::luaL_checknumber(state, 1) as c_float;
let y = ffi::luaL_checknumber(state, 2) as c_float;
let z = ffi::luaL_checknumber(state, 3) as c_float;
#[cfg(feature = "luau-vector4")]
let w = ffi::luaL_checknumber(state, 4) as c_float;
#[cfg(not(feature = "luau-vector4"))]
ffi::lua_pushvector(state, x, y, z);
#[cfg(feature = "luau-vector4")]
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)
}
+2 -1
View File
@@ -101,7 +101,8 @@ macro_rules! protect_lua {
};
($state:expr, $nargs:expr, $nresults:expr, fn($state_inner:ident) $code:expr) => {{
unsafe extern "C" fn do_call($state_inner: *mut ffi::lua_State) -> ::std::os::raw::c_int {
use ::std::os::raw::c_int;
unsafe extern "C-unwind" fn do_call($state_inner: *mut ffi::lua_State) -> c_int {
$code;
let nresults = $nresults;
if nresults == ::ffi::LUA_MULTRET {
+25 -30
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,16 +74,13 @@ 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
}
}
unsafe extern "C" fn allocator(
unsafe extern "C-unwind" fn allocator(
extra: *mut c_void,
ptr: *mut c_void,
osize: usize,
+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,))
}
}
+3 -3
View File
@@ -13,7 +13,7 @@ pub use crate::{
Thread as LuaThread, ThreadStatus as LuaThreadStatus, UserData as LuaUserData,
UserDataFields as LuaUserDataFields, UserDataMetatable as LuaUserDataMetatable,
UserDataMethods as LuaUserDataMethods, UserDataRef as LuaUserDataRef,
UserDataRefMut as LuaUserDataRefMut, UserDataRegistrar as LuaUserDataRegistrar,
UserDataRefMut as LuaUserDataRefMut, UserDataRegistry as LuaUserDataRegistry,
Value as LuaValue,
};
@@ -23,7 +23,7 @@ pub use crate::HookTriggers as LuaHookTriggers;
#[cfg(feature = "luau")]
#[doc(no_inline)]
pub use crate::{CoverageInfo as LuaCoverageInfo, VmState as LuaVmState};
pub use crate::{CoverageInfo as LuaCoverageInfo, Vector as LuaVector, VmState as LuaVmState};
#[cfg(feature = "async")]
#[doc(no_inline)]
@@ -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,
};
+310 -201
View File
@@ -10,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::UserDataRegistrar;
use crate::userdata_impl::UserDataRegistry;
use crate::util::{
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;
@@ -76,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)
}))
}
}
@@ -187,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
@@ -240,7 +258,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
#[cfg(feature = "lua54")]
for i in 1..=USER_VALUE_MAXSLOT {
ffi::lua_pushnil(state);
ffi::lua_setiuservalue(state, -2, i as c_int);
ffi::lua_setiuservalue(state, -2, i as _);
}
#[cfg(any(feature = "lua53", feature = "lua52", feature = "luau"))]
{
@@ -294,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
@@ -306,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();
@@ -369,7 +383,6 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
check_stack(state, 13)?;
#[cfg(not(feature = "luau"))]
#[allow(clippy::let_and_return)]
let ud_ptr = protect_lua!(state, 0, 1, |state| {
let ud = ffi::lua_newuserdata(state, mem::size_of::<UserDataCell<T>>());
@@ -384,64 +397,108 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
})?;
#[cfg(feature = "luau")]
let ud_ptr = {
crate::util::push_userdata(state, UserDataCell::new(data), true)?;
util::push_userdata(state, UserDataCell::new(data), true)?;
ffi::lua_touserdata(state, -1) as *const UserDataCell<T>
};
// 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;
push_table(state, 0, meta_methods_nrec as c_int, true)?;
for (k, m) in ud_methods.meta_methods {
lua.push_value(Value::Function(wrap_method(self, ud_ptr, m)?))?;
let meta_methods_nrec = registry.meta_methods.len() + registry.meta_fields.len() + 1;
push_table(state, 0, meta_methods_nrec, true)?;
for (k, m) in registry.meta_methods {
lua.push_value(Value::Function(wrap_method(self, ud_ptr, &k, m)?))?;
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 as c_int, true)?;
for (k, m) in ud_fields.field_getters {
lua.push_value(Value::Function(wrap_method(self, ud_ptr, m)?))?;
push_table(state, 0, field_getters_nrec, true)?;
for (k, m) in registry.field_getters {
lua.push_value(Value::Function(wrap_method(self, ud_ptr, &k, m)?))?;
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 as c_int, true)?;
for (k, m) in ud_fields.field_setters {
lua.push_value(Value::Function(wrap_method(self, ud_ptr, m)?))?;
push_table(state, 0, field_setters_nrec, true)?;
for (k, m) in registry.field_setters {
lua.push_value(Value::Function(wrap_method(self, ud_ptr, &k, m)?))?;
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 as c_int, true)?;
for (k, m) in ud_methods.methods {
lua.push_value(Value::Function(wrap_method(self, ud_ptr, m)?))?;
push_table(state, 0, methods_nrec, true)?;
for (k, m) in registry.methods {
lua.push_value(Value::Function(wrap_method(self, ud_ptr, &k, m)?))?;
rawset_field(state, -2, &k)?;
}
methods_index = Some(ffi::lua_absindex(state, -1));
}
init_userdata_metatable::<UserDataCell<T>>(
#[cfg(feature = "luau")]
let extra_init = None;
#[cfg(not(feature = "luau"))]
let extra_init: Option<fn(*mut ffi::lua_State) -> Result<()>> = Some(|state| {
ffi::lua_pushcfunction(state, util::userdata_destructor::<UserDataCell<T>>);
rawset_field(state, -2, "__gc")
});
init_userdata_metatable(
state,
metatable_index,
field_getters_index,
field_setters_index,
methods_index,
extra_init,
)?;
let count = field_getters_index.map(|_| 1).unwrap_or(0)
@@ -454,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"))]
@@ -479,7 +536,7 @@ impl<'lua, 'scope> Scope<'lua, 'scope> {
#[cfg(feature = "lua54")]
for i in 1..=USER_VALUE_MAXSLOT {
ffi::lua_pushnil(state);
ffi::lua_setiuservalue(state, -2, i as c_int);
ffi::lua_setiuservalue(state, -2, i as _);
}
#[cfg(any(feature = "lua53", feature = "lua52", feature = "luau"))]
{
@@ -565,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));
}
@@ -604,19 +770,37 @@ 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));
}
#[cfg(feature = "async")]
fn add_async_method<M, A, MR, R>(&mut self, _name: impl AsRef<str>, _method: M)
fn add_async_method<'s, M, A, MR, R>(&mut self, _name: impl AsRef<str>, _method: M)
where
T: Clone,
M: Fn(&'lua Lua, T, A) -> MR + MaybeSend + 'static,
'lua: 's,
T: 'static,
M: Fn(&'lua Lua, &'s T, A) -> MR + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
MR: Future<Output = Result<R>> + 'lua,
MR: Future<Output = Result<R>> + 's,
R: IntoLuaMulti<'lua>,
{
// The panic should never happen as async non-static code wouldn't compile
// Non-static lifetime must be bounded to 'lua lifetime
panic!("asynchronous methods are not supported for non-static userdata")
}
#[cfg(feature = "async")]
fn add_async_method_mut<'s, M, A, MR, R>(&mut self, _name: impl AsRef<str>, _method: M)
where
'lua: 's,
T: 'static,
M: Fn(&'lua Lua, &'s mut T, A) -> MR + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
MR: Future<Output = Result<R>> + 's,
R: IntoLuaMulti<'lua>,
{
// The panic should never happen as async non-static code wouldn't compile
@@ -630,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));
}
@@ -642,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));
}
@@ -667,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));
}
@@ -679,19 +869,37 @@ 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));
}
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
fn add_async_meta_method<M, A, MR, R>(&mut self, _name: impl AsRef<str>, _method: M)
fn add_async_meta_method<'s, M, A, MR, R>(&mut self, _name: impl AsRef<str>, _method: M)
where
T: Clone,
M: Fn(&'lua Lua, T, A) -> MR + MaybeSend + 'static,
'lua: 's,
T: 'static,
M: Fn(&'lua Lua, &'s T, A) -> MR + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
MR: Future<Output = Result<R>> + 'lua,
MR: Future<Output = Result<R>> + 's,
R: IntoLuaMulti<'lua>,
{
// The panic should never happen as async non-static code wouldn't compile
// Non-static lifetime must be bounded to 'lua lifetime
panic!("asynchronous meta methods are not supported for non-static userdata")
}
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
fn add_async_meta_method_mut<'s, M, A, MR, R>(&mut self, _name: impl AsRef<str>, _method: M)
where
'lua: 's,
T: 'static,
M: Fn(&'lua Lua, &'s mut T, A) -> MR + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
MR: Future<Output = Result<R>> + 's,
R: IntoLuaMulti<'lua>,
{
// The panic should never happen as async non-static code wouldn't compile
@@ -705,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));
}
@@ -717,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));
}
@@ -736,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, _| {
UserDataRegistrar::<()>::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, _| UserDataRegistrar::<()>::check_meta_field(lua, &name2, f(lua)?)),
));
}
}
+86 -24
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> {
@@ -124,7 +140,7 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
#[allow(clippy::useless_conversion)]
Value::Number(n) => visitor.visit_f64(n.into()),
#[cfg(feature = "luau")]
Value::Vector(_, _, _) => self.deserialize_seq(visitor),
Value::Vector(_) => self.deserialize_seq(visitor),
Value::String(s) => match s.to_str() {
Ok(s) => visitor.visit_str(s),
Err(_) => visitor.visit_bytes(s.as_bytes()),
@@ -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"));
}
@@ -223,9 +239,9 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
{
match self.value {
#[cfg(feature = "luau")]
Value::Vector(x, y, z) => {
Value::Vector(vec) => {
let mut deserializer = VecDeserializer {
vec: [x, y, z],
vec,
next: 0,
options: self.options,
visited: self.visited,
@@ -235,9 +251,9 @@ 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.raw_sequence_values(),
seq: t.sequence_values(),
options: self.options,
visited: self.visited,
};
@@ -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);
@@ -412,7 +430,7 @@ impl<'lua, 'de> de::SeqAccess<'de> for SeqDeserializer<'lua> {
#[cfg(feature = "luau")]
struct VecDeserializer {
vec: [f32; 3],
vec: crate::types::Vector,
next: usize,
options: Options,
visited: Rc<RefCell<FxHashSet<*const c_void>>>,
@@ -426,7 +444,7 @@ impl<'de> de::SeqAccess<'de> for VecDeserializer {
where
T: de::DeserializeSeed<'de>,
{
match self.vec.get(self.next) {
match self.vec.0.get(self.next) {
Some(&n) => {
self.next += 1;
let visited = Rc::clone(&self.visited);
@@ -439,12 +457,54 @@ impl<'de> de::SeqAccess<'de> for VecDeserializer {
}
fn size_hint(&self) -> Option<usize> {
Some(3)
Some(crate::types::Vector::SIZE)
}
}
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
}
+65 -45
View File
@@ -1,5 +1,3 @@
use std::os::raw::c_int;
use serde::{ser, Serialize};
use super::LuaSerdeExt;
@@ -7,8 +5,6 @@ use crate::error::{Error, Result};
use crate::lua::Lua;
use crate::string::String;
use crate::table::Table;
use crate::types::Integer;
use crate::util::{check_stack, StackGuard};
use crate::value::{IntoLua, Value};
/// A struct for serializing Rust values into Lua values.
@@ -120,9 +116,9 @@ impl<'lua> ser::Serializer for Serializer<'lua> {
// Associated types for keeping track of additional state while serializing
// compound data structures like sequences and maps.
type SerializeSeq = SerializeVec<'lua>;
type SerializeTuple = SerializeVec<'lua>;
type SerializeTupleStruct = SerializeVec<'lua>;
type SerializeSeq = SerializeSeq<'lua>;
type SerializeTuple = SerializeSeq<'lua>;
type SerializeTupleStruct = SerializeSeq<'lua>;
type SerializeTupleVariant = SerializeTupleVariant<'lua>;
type SerializeMap = SerializeMap<'lua>;
type SerializeStruct = SerializeMap<'lua>;
@@ -235,13 +231,11 @@ impl<'lua> ser::Serializer for Serializer<'lua> {
#[inline]
fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq> {
let len = len.unwrap_or(0) as c_int;
let table = self.lua.create_table_with_capacity(len, 0)?;
let table = self.lua.create_table_with_capacity(len.unwrap_or(0), 0)?;
if self.options.set_array_metatable {
table.set_metatable(Some(self.lua.array_metatable()));
}
let options = self.options;
Ok(SerializeVec { table, options })
Ok(SerializeSeq::new(table, self.options))
}
#[inline]
@@ -252,9 +246,14 @@ impl<'lua> ser::Serializer for Serializer<'lua> {
#[inline]
fn serialize_tuple_struct(
self,
_name: &'static str,
name: &'static str,
len: usize,
) -> Result<Self::SerializeTupleStruct> {
#[cfg(feature = "luau")]
if name == "Vector" && len == crate::types::Vector::SIZE {
return Ok(SerializeSeq::new_vector(self.lua, self.options));
}
_ = name;
self.serialize_seq(Some(len))
}
@@ -275,10 +274,9 @@ impl<'lua> ser::Serializer for Serializer<'lua> {
#[inline]
fn serialize_map(self, len: Option<usize>) -> Result<Self::SerializeMap> {
let len = len.unwrap_or(0) as c_int;
Ok(SerializeMap {
key: None,
table: self.lua.create_table_with_capacity(0, len)?,
table: self.lua.create_table_with_capacity(0, len.unwrap_or(0))?,
options: self.options,
})
}
@@ -298,19 +296,47 @@ impl<'lua> ser::Serializer for Serializer<'lua> {
) -> Result<Self::SerializeStructVariant> {
Ok(SerializeStructVariant {
name: self.lua.create_string(variant)?,
table: self.lua.create_table_with_capacity(0, len as c_int)?,
table: self.lua.create_table_with_capacity(0, len)?,
options: self.options,
})
}
}
#[doc(hidden)]
pub struct SerializeVec<'lua> {
table: Table<'lua>,
pub struct SerializeSeq<'lua> {
lua: &'lua Lua,
#[cfg(feature = "luau")]
vector: Option<crate::types::Vector>,
table: Option<Table<'lua>>,
next: usize,
options: Options,
}
impl<'lua> ser::SerializeSeq for SerializeVec<'lua> {
impl<'lua> SerializeSeq<'lua> {
const fn new(table: Table<'lua>, options: Options) -> Self {
Self {
lua: table.0.lua,
#[cfg(feature = "luau")]
vector: None,
table: Some(table),
next: 0,
options,
}
}
#[cfg(feature = "luau")]
const fn new_vector(lua: &'lua Lua, options: Options) -> Self {
Self {
lua,
vector: Some(crate::types::Vector::zero()),
table: None,
next: 0,
options,
}
}
}
impl<'lua> ser::SerializeSeq for SerializeSeq<'lua> {
type Ok = Value<'lua>;
type Error = Error;
@@ -318,35 +344,19 @@ impl<'lua> ser::SerializeSeq for SerializeVec<'lua> {
where
T: Serialize + ?Sized,
{
let lua = self.table.0.lua;
let state = lua.state();
let value = lua.to_value_with(value, self.options)?;
unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 4)?;
lua.push_ref(&self.table.0);
lua.push_value(value)?;
if lua.unlikely_memory_error() {
let len = ffi::lua_rawlen(state, -2) as Integer;
ffi::lua_rawseti(state, -2, len + 1);
ffi::lua_pop(state, 1);
Ok(())
} else {
protect_lua!(state, 2, 0, fn(state) {
let len = ffi::lua_rawlen(state, -2) as Integer;
ffi::lua_rawseti(state, -2, len + 1);
})
}
}
let value = self.lua.to_value_with(value, self.options)?;
let table = self.table.as_ref().unwrap();
table.raw_seti(self.next + 1, value)?;
self.next += 1;
Ok(())
}
fn end(self) -> Result<Value<'lua>> {
Ok(Value::Table(self.table))
Ok(Value::Table(self.table.unwrap()))
}
}
impl<'lua> ser::SerializeTuple for SerializeVec<'lua> {
impl<'lua> ser::SerializeTuple for SerializeSeq<'lua> {
type Ok = Value<'lua>;
type Error = Error;
@@ -362,7 +372,7 @@ impl<'lua> ser::SerializeTuple for SerializeVec<'lua> {
}
}
impl<'lua> ser::SerializeTupleStruct for SerializeVec<'lua> {
impl<'lua> ser::SerializeTupleStruct for SerializeSeq<'lua> {
type Ok = Value<'lua>;
type Error = Error;
@@ -370,10 +380,22 @@ impl<'lua> ser::SerializeTupleStruct for SerializeVec<'lua> {
where
T: Serialize + ?Sized,
{
#[cfg(feature = "luau")]
if let Some(vector) = self.vector.as_mut() {
let value = self.lua.to_value_with(value, self.options)?;
let value = self.lua.unpack(value)?;
vector.0[self.next] = value;
self.next += 1;
return Ok(());
}
ser::SerializeSeq::serialize_element(self, value)
}
fn end(self) -> Result<Value<'lua>> {
#[cfg(feature = "luau")]
if let Some(vector) = self.vector {
return Ok(Value::Vector(vector));
}
ser::SerializeSeq::end(self)
}
}
@@ -394,9 +416,7 @@ impl<'lua> ser::SerializeTupleVariant for SerializeTupleVariant<'lua> {
T: Serialize + ?Sized,
{
let lua = self.table.0.lua;
let idx = self.table.raw_len() + 1;
self.table
.raw_insert(idx, lua.to_value_with(value, self.options)?)
self.table.raw_push(lua.to_value_with(value, self.options)?)
}
fn end(self) -> Result<Value<'lua>> {
+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);
+1 -2
View File
@@ -139,8 +139,7 @@ impl<'lua> String<'lua> {
/// Typically this function is used only for hashing and debug information.
#[inline]
pub fn to_pointer(&self) -> *const c_void {
let ref_thread = self.0.lua.ref_thread();
unsafe { ffi::lua_topointer(ref_thread, self.0.index) }
self.0.to_pointer()
}
/// Convert this handle to owned version.
+330 -188
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};
@@ -45,7 +45,6 @@ impl OwnedTable {
}
}
#[allow(clippy::len_without_is_empty)]
impl<'lua> Table<'lua> {
/// Sets a key-value pair in the table.
///
@@ -87,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))
}
}
@@ -134,27 +130,28 @@ 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`.
///
/// This might invoke the `__index` metamethod.
pub fn contains_key<K: IntoLua<'lua>>(&self, key: K) -> Result<bool> {
Ok(self.get::<_, Value>(key)? != Value::Nil)
}
/// Appends a value to the back of the table.
///
/// This might invoke the `__len` and `__newindex` metamethods.
pub fn push<V: IntoLua<'lua>>(&self, value: V) -> Result<()> {
// Fast track
if !self.has_metatable() {
@@ -163,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);
@@ -179,6 +175,8 @@ impl<'lua> Table<'lua> {
}
/// Removes the last element from the table and returns it.
///
/// This might invoke the `__len` and `__newindex` metamethods.
pub fn pop<V: FromLua<'lua>>(&self) -> Result<V> {
// Fast track
if !self.has_metatable() {
@@ -187,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)?;
@@ -198,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.
@@ -266,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);
@@ -291,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::RuntimeError("index out of bounds".to_string()));
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]
@@ -342,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;
@@ -372,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)?;
@@ -382,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.
@@ -400,9 +387,9 @@ 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::RuntimeError("index out of bounds".to_string()));
return Err(Error::runtime("index out of bounds"));
}
unsafe {
let _sg = StackGuard::new(state);
@@ -472,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;
@@ -487,9 +474,35 @@ 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.
///
/// It checks both the array part and the hash part.
pub fn is_empty(&self) -> bool {
// Check array part
if self.raw_len() != 0 {
return false;
}
// Check hash part
let lua = self.0.lua;
let state = lua.state();
unsafe {
let _sg = StackGuard::new(state);
assert_stack(state, 4);
lua.push_ref(&self.0);
ffi::lua_pushnil(state);
if ffi::lua_next(state, -2) != 0 {
return false;
}
}
true
}
/// Returns a reference to the metatable of this table, or `None` if no metatable is set.
@@ -586,8 +599,7 @@ impl<'lua> Table<'lua> {
/// Typically this function is used only for hashing and debug information.
#[inline]
pub fn to_pointer(&self) -> *const c_void {
let ref_thread = self.0.lua.ref_thread();
unsafe { ffi::lua_topointer(ref_thread, self.0.index) }
self.0.to_pointer()
}
/// Convert this handle to owned version.
@@ -639,14 +651,39 @@ 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
/// encountered. This mirrors the behavior of Lua's `ipairs` function and will invoke the
/// `__index` metamethod according to the usual rules. However, the deprecated `__ipairs`
/// metatable will not be called.
///
/// Just like [`pairs`], the values are wrapped in a [`Result`].
/// The iterator will yield all values `t[1]`, `t[2]` and so on, until a `nil` value is
/// encountered. This mirrors the behavior of Lua's `ipairs` function but does not invoke
/// any metamethods.
///
/// # Note
///
@@ -683,43 +720,57 @@ 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,
raw: false,
_phantom: PhantomData,
}
}
/// Consume this table and return an iterator over all values in the sequence part of the table.
///
/// Unlike the `sequence_values`, does not invoke `__index` metamethod when iterating.
///
/// [`sequence_values`]: #method.sequence_values
#[doc(hidden)]
#[deprecated(since = "0.9.0", note = "use `sequence_values` instead")]
pub fn raw_sequence_values<V: FromLua<'lua>>(self) -> TableSequence<'lua, V> {
TableSequence {
table: self.0,
index: Some(1),
len: None,
raw: true,
_phantom: PhantomData,
}
self.sequence_values()
}
#[cfg(feature = "serialize")]
pub(crate) fn raw_sequence_values_by_len<V: FromLua<'lua>>(
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),
raw: true,
_phantom: PhantomData,
}
}
/// Sets element value at position `idx` without invoking metamethods.
#[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();
unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 5)?;
lua.push_ref(&self.0);
value.push_into_stack(lua)?;
let idx = idx.try_into().unwrap();
if lua.unlikely_memory_error() {
ffi::lua_rawseti(state, -2, idx);
} else {
protect_lua!(state, 2, 0, |state| ffi::lua_rawseti(state, -2, idx))?;
}
}
Ok(())
}
#[cfg(feature = "serialize")]
pub(crate) fn is_array(&self) -> bool {
let lua = self.0.lua;
@@ -741,8 +792,7 @@ impl<'lua> Table<'lua> {
#[inline(always)]
pub(crate) fn check_readonly_write(&self) -> Result<()> {
if self.is_readonly() {
let err = "attempt to modify a readonly table".to_string();
return Err(Error::RuntimeError(err));
return Err(Error::runtime("attempt to modify a readonly table"));
}
Ok(())
}
@@ -797,6 +847,56 @@ impl<'lua> AsRef<Table<'lua>> for Table<'lua> {
}
}
impl<'lua, T> PartialEq<[T]> for Table<'lua>
where
T: IntoLua<'lua> + Clone,
{
fn eq(&self, other: &[T]) -> bool {
let lua = self.0.lua;
let state = lua.state();
unsafe {
let _sg = StackGuard::new(state);
assert_stack(state, 4);
lua.push_ref(&self.0);
let len = ffi::lua_rawlen(state, -1);
for i in 0..len {
ffi::lua_rawgeti(state, -1, (i + 1) as _);
let val = lua.pop_value();
if val == Nil {
return i == other.len();
}
match other.get(i).map(|v| v.clone().into_lua(lua)) {
Some(Ok(other_val)) if val == other_val => continue,
_ => return false,
}
}
}
true
}
}
impl<'lua, T> PartialEq<&[T]> for Table<'lua>
where
T: IntoLua<'lua> + Clone,
{
#[inline]
fn eq(&self, other: &&[T]) -> bool {
self == *other
}
}
impl<'lua, T, const N: usize> PartialEq<[T; N]> for Table<'lua>
where
T: IntoLua<'lua> + Clone,
{
#[inline]
fn eq(&self, other: &[T; N]) -> bool {
self == &other[..]
}
}
/// An extension trait for `Table`s that provides a variety of convenient functionality.
pub trait TableExt<'lua>: Sealed {
/// Calls the table as function assuming it has `__call` metamethod.
@@ -812,11 +912,10 @@ pub trait TableExt<'lua>: Sealed {
/// The metamethod is called with the table as its first argument, followed by the passed arguments.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn call_async<'fut, A, R>(&self, args: A) -> LocalBoxFuture<'fut, Result<R>>
fn call_async<A, R>(&self, args: A) -> LocalBoxFuture<'lua, Result<R>>
where
'lua: 'fut,
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut;
R: FromLuaMulti<'lua> + 'lua;
/// Gets the function associated to `key` from the table and executes it,
/// passing the table itself along with `args` as function arguments.
@@ -825,9 +924,8 @@ pub trait TableExt<'lua>: Sealed {
/// `table.get::<_, Function>(key)?.call((table.clone(), arg1, ..., argN))`
///
/// This might invoke the `__index` metamethod.
fn call_method<K, A, R>(&self, key: K, args: A) -> Result<R>
fn call_method<A, R>(&self, name: &str, args: A) -> Result<R>
where
K: IntoLua<'lua>,
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua>;
@@ -838,9 +936,8 @@ pub trait TableExt<'lua>: Sealed {
/// `table.get::<_, Function>(key)?.call(args)`
///
/// This might invoke the `__index` metamethod.
fn call_function<K, A, R>(&self, key: K, args: A) -> Result<R>
fn call_function<A, R>(&self, name: &str, args: A) -> Result<R>
where
K: IntoLua<'lua>,
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua>;
@@ -852,12 +949,10 @@ pub trait TableExt<'lua>: Sealed {
/// This might invoke the `__index` metamethod.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn call_async_method<'fut, K, A, R>(&self, key: K, args: A) -> LocalBoxFuture<'fut, Result<R>>
fn call_async_method<A, R>(&self, name: &str, args: A) -> LocalBoxFuture<'lua, Result<R>>
where
'lua: 'fut,
K: IntoLua<'lua>,
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut;
R: FromLuaMulti<'lua> + 'lua;
/// Gets the function associated to `key` from the table and asynchronously executes it,
/// passing `args` as function arguments and returning Future.
@@ -867,16 +962,10 @@ pub trait TableExt<'lua>: Sealed {
/// This might invoke the `__index` metamethod.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn call_async_function<'fut, K, A, R>(
&self,
key: K,
args: A,
) -> LocalBoxFuture<'fut, Result<R>>
fn call_async_function<A, R>(&self, name: &str, args: A) -> LocalBoxFuture<'lua, Result<R>>
where
'lua: 'fut,
K: IntoLua<'lua>,
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut;
R: FromLuaMulti<'lua> + 'lua;
}
impl<'lua> TableExt<'lua> for Table<'lua> {
@@ -890,43 +979,43 @@ impl<'lua> TableExt<'lua> for Table<'lua> {
}
#[cfg(feature = "async")]
fn call_async<'fut, A, R>(&self, args: A) -> LocalBoxFuture<'fut, Result<R>>
fn call_async<A, R>(&self, args: A) -> LocalBoxFuture<'lua, Result<R>>
where
'lua: 'fut,
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut,
R: FromLuaMulti<'lua> + 'lua,
{
Function(self.0.clone()).call_async(args)
let args = match args.into_lua_multi(self.0.lua) {
Ok(args) => args,
Err(e) => return Box::pin(future::err(e)),
};
let func = Function(self.0.clone());
Box::pin(async move { func.call_async(args).await })
}
fn call_method<K, A, R>(&self, key: K, args: A) -> Result<R>
fn call_method<A, R>(&self, name: &str, args: A) -> Result<R>
where
K: IntoLua<'lua>,
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua>,
{
let lua = self.0.lua;
let mut args = args.into_lua_multi(lua)?;
args.push_front(Value::Table(self.clone()));
self.get::<_, Function>(key)?.call(args)
self.get::<_, Function>(name)?.call(args)
}
fn call_function<K, A, R>(&self, key: K, args: A) -> Result<R>
fn call_function<A, R>(&self, name: &str, args: A) -> Result<R>
where
K: IntoLua<'lua>,
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua>,
{
self.get::<_, Function>(key)?.call(args)
self.get::<_, Function>(name)?.call(args)
}
#[cfg(feature = "async")]
fn call_async_method<'fut, K, A, R>(&self, key: K, args: A) -> LocalBoxFuture<'fut, Result<R>>
fn call_async_method<A, R>(&self, name: &str, args: A) -> LocalBoxFuture<'lua, Result<R>>
where
'lua: 'fut,
K: IntoLua<'lua>,
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut,
R: FromLuaMulti<'lua> + 'lua,
{
let lua = self.0.lua;
let mut args = match args.into_lua_multi(lua) {
@@ -934,65 +1023,134 @@ impl<'lua> TableExt<'lua> for Table<'lua> {
Err(e) => return Box::pin(future::err(e)),
};
args.push_front(Value::Table(self.clone()));
self.call_async_function(key, args)
self.call_async_function(name, args)
}
#[cfg(feature = "async")]
fn call_async_function<'fut, K, A, R>(&self, key: K, args: A) -> LocalBoxFuture<'fut, Result<R>>
fn call_async_function<A, R>(&self, name: &str, args: A) -> LocalBoxFuture<'lua, Result<R>>
where
'lua: 'fut,
K: IntoLua<'lua>,
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut,
R: FromLuaMulti<'lua> + 'lua,
{
match self.get::<_, Function>(key) {
Ok(func) => func.call_async(args),
let lua = self.0.lua;
let args = match args.into_lua_multi(lua) {
Ok(args) => args,
Err(e) => return Box::pin(future::err(e)),
};
match self.get::<_, Function>(name) {
Ok(func) => Box::pin(async move { func.call_async(args).await }),
Err(e) => Box::pin(future::err(e)),
}
}
}
/// 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().raw_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()
}
}
@@ -1026,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)
@@ -1063,9 +1220,8 @@ where
/// [`Table::sequence_values`]: crate::Table::sequence_values
pub struct TableSequence<'lua, V> {
table: LuaRef<'lua>,
index: Option<Integer>,
index: Integer,
len: Option<Integer>,
raw: bool,
_phantom: PhantomData<V>,
}
@@ -1076,36 +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 + if self.raw { 0 } else { 3 })?;
lua.push_ref(&self.table);
let res = if self.raw {
ffi::lua_rawgeti(state, -1, index)
} else {
protect_lua!(state, 1, 1, |state| ffi::lua_geti(state, -1, index))?
};
match res {
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
}
}
}
+202 -160
View File
@@ -1,4 +1,3 @@
use std::cmp;
use std::os::raw::c_int;
use crate::error::{Error, Result};
@@ -8,13 +7,6 @@ use crate::types::LuaRef;
use crate::util::{check_stack, error_traceback_thread, pop_error, StackGuard};
use crate::value::{FromLuaMulti, IntoLuaMulti};
#[cfg(any(
feature = "lua54",
all(feature = "luajit", feature = "vendored"),
feature = "luau",
))]
use crate::function::Function;
#[cfg(not(feature = "luau"))]
use crate::{
hook::{Debug, HookTriggers},
@@ -23,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,
@@ -37,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`.
@@ -52,27 +41,61 @@ 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`].
///
/// Requires `feature = "async"`
///
/// [`Future`]: futures_core::future::Future
/// [`Stream`]: futures_core::stream::Stream
/// [`Future`]: std::future::Future
/// [`Stream`]: futures_util::stream::Stream
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct AsyncThread<'lua, R> {
thread: Thread<'lua>,
args0: Option<Result<MultiValue<'lua>>>,
init_args: Option<Result<MultiValue<'lua>>>,
ret: PhantomData<R>,
recycle: bool,
}
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`.
@@ -121,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
@@ -198,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);
}
}
@@ -209,45 +230,33 @@ impl<'lua> Thread<'lua> {
/// Returns a error in case of either the original error that stopped the thread or errors
/// in closing methods.
///
/// In [LuaJIT] and Luau: resets to the initial state of a newly created Lua thread.
/// In Luau: resets to the initial state of a newly created Lua thread.
/// Lua threads in arbitrary states (like yielded or errored) can be reset properly.
///
/// Sets a Lua function for the thread afterwards.
///
/// Requires `feature = "lua54"` OR `feature = "luajit,vendored"` OR `feature = "luau"`
/// Requires `feature = "lua54"` OR `feature = "luau"`.
///
/// [Lua 5.4]: https://www.lua.org/manual/5.4/manual.html#lua_resetthread
/// [LuaJIT]: https://github.com/openresty/luajit2#lua_resetthread
#[cfg(any(
feature = "lua54",
all(feature = "luajit", feature = "vendored"),
feature = "luau",
))]
pub fn reset(&self, func: Function<'lua>) -> Result<()> {
/// [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(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));
}
#[cfg(all(feature = "luajit", feature = "vendored"))]
ffi::lua_resetthread(state, thread_state);
#[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")]
{
@@ -272,8 +281,8 @@ impl<'lua> Thread<'lua> {
///
/// Requires `feature = "async"`
///
/// [`Future`]: futures_core::future::Future
/// [`Stream`]: futures_core::stream::Stream
/// [`Future`]: std::future::Future
/// [`Stream`]: futures_util::stream::Stream
/// [`resume()`]: https://www.lua.org/manual/5.4/manual.html#lua_resume
///
/// # Examples
@@ -315,7 +324,7 @@ impl<'lua> Thread<'lua> {
let args = args.into_lua_multi(self.0.lua);
AsyncThread {
thread: self,
args0: Some(args),
init_args: Some(args),
ret: PhantomData,
recycle: false,
}
@@ -358,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> {
@@ -373,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]
@@ -382,11 +419,7 @@ impl<'lua, R> AsyncThread<'lua, R> {
}
#[cfg(feature = "async")]
#[cfg(any(
feature = "lua54",
all(feature = "luajit", feature = "vendored"),
feature = "luau",
))]
#[cfg(any(feature = "lua54", feature = "luau"))]
impl<'lua, R> Drop for AsyncThread<'lua, R> {
fn drop(&mut self) {
if self.recycle {
@@ -396,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());
}
}
}
@@ -416,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.args0.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)))
}
}
}
@@ -450,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.args0.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")]
@@ -503,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) };
}
}
+122 -27
View File
@@ -8,35 +8,42 @@ use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::{fmt, mem, ptr};
#[cfg(feature = "lua54")]
use std::ffi::CStr;
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::util::{assert_stack, StackGuard};
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};
#[cfg(all(feature = "luau", feature = "serialize"))]
use serde::ser::{Serialize, SerializeTupleStruct, Serializer};
/// Type of Lua integer numbers.
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,
@@ -47,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))]
@@ -76,10 +83,10 @@ pub(crate) type InterruptCallback = Arc<dyn Fn(&Lua) -> Result<VmState> + Send>;
pub(crate) type InterruptCallback = Arc<dyn Fn(&Lua) -> Result<VmState>>;
#[cfg(all(feature = "send", feature = "lua54"))]
pub(crate) type WarnCallback = Box<dyn Fn(&Lua, &CStr, bool) -> Result<()> + Send>;
pub(crate) type WarnCallback = Box<dyn Fn(&Lua, &str, bool) -> Result<()> + Send>;
#[cfg(all(not(feature = "send"), feature = "lua54"))]
pub(crate) type WarnCallback = Box<dyn Fn(&Lua, &CStr, bool) -> Result<()>>;
pub(crate) type WarnCallback = Box<dyn Fn(&Lua, &str, bool) -> Result<()>>;
#[cfg(feature = "send")]
pub trait MaybeSend: Send {}
@@ -91,6 +98,92 @@ pub trait MaybeSend {}
#[cfg(not(feature = "send"))]
impl<T> MaybeSend for T {}
/// A Luau vector type.
///
/// By default vectors are 3-dimensional, but can be 4-dimensional
/// if the `luau-vector4` feature is enabled.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
#[derive(Debug, Default, Clone, Copy, PartialEq)]
pub struct Vector(pub(crate) [f32; Self::SIZE]);
#[cfg(any(feature = "luau", doc))]
impl fmt::Display for Vector {
#[rustfmt::skip]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
#[cfg(not(feature = "luau-vector4"))]
return write!(f, "vector({}, {}, {})", self.x(), self.y(), self.z());
#[cfg(feature = "luau-vector4")]
return write!(f, "vector({}, {}, {}, {})", self.x(), self.y(), self.z(), self.w());
}
}
#[cfg(any(feature = "luau", doc))]
impl Vector {
pub(crate) const SIZE: usize = if cfg!(feature = "luau-vector4") { 4 } else { 3 };
/// Creates a new vector.
#[cfg(not(feature = "luau-vector4"))]
pub const fn new(x: f32, y: f32, z: f32) -> Self {
Self([x, y, z])
}
/// Creates a new vector.
#[cfg(feature = "luau-vector4")]
pub const fn new(x: f32, y: f32, z: f32, w: f32) -> Self {
Self([x, y, z, w])
}
/// Creates a new vector with all components set to `0.0`.
#[doc(hidden)]
pub const fn zero() -> Self {
Self([0.0; Self::SIZE])
}
/// Returns 1st component of the vector.
pub const fn x(&self) -> f32 {
self.0[0]
}
/// Returns 2nd component of the vector.
pub const fn y(&self) -> f32 {
self.0[1]
}
/// Returns 3rd component of the vector.
pub const fn z(&self) -> f32 {
self.0[2]
}
/// Returns 4th component of the vector.
#[cfg(any(feature = "luau-vector4", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau-vector4")))]
pub const fn w(&self) -> f32 {
self.0[3]
}
}
#[cfg(all(feature = "luau", feature = "serialize"))]
impl Serialize for Vector {
fn serialize<S: Serializer>(&self, serializer: S) -> StdResult<S::Ok, S::Error> {
let mut ts = serializer.serialize_tuple_struct("Vector", Self::SIZE)?;
ts.serialize_field(&self.x())?;
ts.serialize_field(&self.y())?;
ts.serialize_field(&self.z())?;
#[cfg(feature = "luau-vector4")]
ts.serialize_field(&self.w())?;
ts.end()
}
}
#[cfg(any(feature = "luau", doc))]
impl PartialEq<[f32; Self::SIZE]> for Vector {
#[inline]
fn eq(&self, other: &[f32; Self::SIZE]) -> bool {
self.0 == *other
}
}
pub(crate) struct DestructedUserdata;
/// An auto generated key into the Lua registry.
@@ -102,14 +195,14 @@ pub(crate) struct DestructedUserdata;
/// Be warned, If you place this into Lua via a [`UserData`] type or a rust callback, it is *very
/// easy* to accidentally cause reference cycles that the Lua garbage collector cannot resolve.
/// Instead of placing a [`RegistryKey`] into a [`UserData`] type, prefer instead to use
/// [`AnyUserData::set_user_value`] / [`AnyUserData::get_user_value`].
/// [`AnyUserData::set_user_value`] / [`AnyUserData::user_value`].
///
/// [`UserData`]: crate::UserData
/// [`RegistryKey`]: crate::RegistryKey
/// [`Lua::remove_registry_value`]: crate::Lua::remove_registry_value
/// [`Lua::expire_registry_values`]: crate::Lua::expire_registry_values
/// [`AnyUserData::set_user_value`]: crate::AnyUserData::set_user_value
/// [`AnyUserData::get_user_value`]: crate::AnyUserData::get_user_value
/// [`AnyUserData::user_value`]: crate::AnyUserData::user_value
pub struct RegistryKey {
pub(crate) registry_id: c_int,
pub(crate) is_nil: AtomicBool,
@@ -199,6 +292,11 @@ impl<'lua> LuaRef<'lua> {
}
}
#[inline]
pub(crate) fn to_pointer(&self) -> *const c_void {
unsafe { ffi::lua_topointer(self.lua.ref_thread(), self.index) }
}
#[cfg(feature = "unstable")]
#[inline]
pub(crate) fn into_owned(self) -> LuaOwnedRef {
@@ -211,7 +309,7 @@ impl<'lua> LuaRef<'lua> {
impl<'lua> fmt::Debug for LuaRef<'lua> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Ref({})", self.index)
write!(f, "Ref({:p})", self.to_pointer())
}
}
@@ -231,15 +329,12 @@ impl<'lua> Drop for LuaRef<'lua> {
impl<'lua> PartialEq for LuaRef<'lua> {
fn eq(&self, other: &Self) -> bool {
let lua = self.lua;
let state = lua.state();
unsafe {
let _sg = StackGuard::new(state);
assert_stack(state, 2);
lua.push_ref(self);
lua.push_ref(other);
ffi::lua_rawequal(state, -1, -2) == 1
}
let ref_thread = self.lua.ref_thread();
assert!(
ref_thread == other.lua.ref_thread(),
"Lua instance passed Value created from a different main Lua state"
);
unsafe { ffi::lua_rawequal(ref_thread, self.index, other.index) == 1 }
}
}
@@ -253,7 +348,7 @@ pub(crate) struct LuaOwnedRef {
#[cfg(feature = "unstable")]
impl fmt::Debug for LuaOwnedRef {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "OwnedRef({})", self.index)
write!(f, "OwnedRef({:p})", self.to_ref().to_pointer())
}
}
+159 -84
View File
@@ -1,5 +1,6 @@
use std::any::{type_name, TypeId};
use std::cell::{Ref, RefCell, RefMut};
use std::ffi::CStr;
use std::fmt;
use std::hash::Hash;
use std::mem;
@@ -21,10 +22,10 @@ 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::UserDataRegistrar;
use crate::UserDataRegistry;
#[cfg(feature = "lua54")]
pub(crate) const USER_VALUE_MAXSLOT: usize = 8;
@@ -36,6 +37,7 @@ pub(crate) const USER_VALUE_MAXSLOT: usize = 8;
///
/// [`UserData`]: crate::UserData
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum MetaMethod {
/// The `+` operator.
Add,
@@ -141,6 +143,11 @@ pub enum MetaMethod {
#[cfg(feature = "lua54")]
#[cfg_attr(docsrs, doc(cfg(feature = "lua54")))]
Close,
/// The `__name`/`__type` metafield.
///
/// This is not a function, but it's value can be used by `tostring` and `typeof` built-in functions.
#[doc(hidden)]
Type,
}
impl PartialEq<MetaMethod> for &str {
@@ -212,6 +219,19 @@ impl MetaMethod {
#[cfg(feature = "lua54")]
MetaMethod::Close => "__close",
#[rustfmt::skip]
MetaMethod::Type => if cfg!(feature = "luau") { "__type" } else { "__name" },
}
}
pub(crate) const fn as_cstr(self) -> &'static CStr {
match self {
#[rustfmt::skip]
MetaMethod::Type => unsafe {
CStr::from_bytes_with_nul_unchecked(if cfg!(feature = "luau") { b"__type\0" } else { b"__name\0" })
},
_ => unreachable!(),
}
}
@@ -259,8 +279,7 @@ pub trait UserDataMethods<'lua, T> {
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>;
/// Add an async method which accepts a `T` as the first parameter and returns Future.
/// The passed `T` is cloned from the original value.
/// Add an async method which accepts a `&T` as the first parameter and returns Future.
///
/// Refer to [`add_method`] for more information about the implementation.
///
@@ -269,12 +288,31 @@ pub trait UserDataMethods<'lua, T> {
/// [`add_method`]: #method.add_method
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn add_async_method<M, A, MR, R>(&mut self, name: impl AsRef<str>, method: M)
fn add_async_method<'s, M, A, MR, R>(&mut self, name: impl AsRef<str>, method: M)
where
T: Clone,
M: Fn(&'lua Lua, T, A) -> MR + MaybeSend + 'static,
'lua: 's,
T: 'static,
M: Fn(&'lua Lua, &'s T, A) -> MR + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
MR: Future<Output = Result<R>> + 'lua,
MR: Future<Output = Result<R>> + 's,
R: IntoLuaMulti<'lua>;
/// Add an async method which accepts a `&mut T` as the first parameter and returns Future.
///
/// Refer to [`add_method`] for more information about the implementation.
///
/// Requires `feature = "async"`
///
/// [`add_method`]: #method.add_method
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn add_async_method_mut<'s, M, A, MR, R>(&mut self, name: impl AsRef<str>, method: M)
where
'lua: 's,
T: 'static,
M: Fn(&'lua Lua, &'s mut T, A) -> MR + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
MR: Future<Output = Result<R>> + 's,
R: IntoLuaMulti<'lua>;
/// Add a regular method as a function which accepts generic arguments, the first argument will
@@ -349,8 +387,7 @@ pub trait UserDataMethods<'lua, T> {
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>;
/// Add an async metamethod which accepts a `T` as the first parameter and returns Future.
/// The passed `T` is cloned from the original value.
/// Add an async metamethod which accepts a `&T` as the first parameter and returns Future.
///
/// This is an async version of [`add_meta_method`].
///
@@ -359,12 +396,31 @@ pub trait UserDataMethods<'lua, T> {
/// [`add_meta_method`]: #method.add_meta_method
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn add_async_meta_method<M, A, MR, R>(&mut self, name: impl AsRef<str>, method: M)
fn add_async_meta_method<'s, M, A, MR, R>(&mut self, name: impl AsRef<str>, method: M)
where
T: Clone,
M: Fn(&'lua Lua, T, A) -> MR + MaybeSend + 'static,
'lua: 's,
T: 'static,
M: Fn(&'lua Lua, &'s T, A) -> MR + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
MR: Future<Output = Result<R>> + 'lua,
MR: Future<Output = Result<R>> + 's,
R: IntoLuaMulti<'lua>;
/// Add an async metamethod which accepts a `&mut T` as the first parameter and returns Future.
///
/// This is an async version of [`add_meta_method_mut`].
///
/// Requires `feature = "async"`
///
/// [`add_meta_method_mut`]: #method.add_meta_method_mut
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn add_async_meta_method_mut<'s, M, A, MR, R>(&mut self, name: impl AsRef<str>, method: M)
where
'lua: 's,
T: 'static,
M: Fn(&'lua Lua, &'s mut T, A) -> MR + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
MR: Future<Output = Result<R>> + 's,
R: IntoLuaMulti<'lua>;
/// Add a metamethod which accepts generic arguments.
@@ -410,7 +466,7 @@ pub trait UserDataMethods<'lua, T> {
//
#[doc(hidden)]
fn append_methods_from<S>(&mut self, _other: UserDataRegistrar<'lua, S>) {}
fn append_methods_from<S>(&mut self, _other: UserDataRegistry<'lua, S>) {}
}
/// Field registry for [`UserData`] implementors.
@@ -508,7 +564,7 @@ pub trait UserDataFields<'lua, T> {
//
#[doc(hidden)]
fn append_fields_from<S>(&mut self, _other: UserDataRegistrar<'lua, S>) {}
fn append_fields_from<S>(&mut self, _other: UserDataRegistry<'lua, S>) {}
}
/// Trait for custom userdata types.
@@ -524,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(())
@@ -718,19 +774,6 @@ impl<T> Deref for UserDataVariant<T> {
}
}
#[cfg(feature = "serialize")]
struct UserDataSerializeError;
#[cfg(feature = "serialize")]
impl Serialize for UserDataSerializeError {
fn serialize<S>(&self, _serializer: S) -> StdResult<S::Ok, S::Error>
where
S: Serializer,
{
Err(ser::Error::custom("cannot serialize <userdata>"))
}
}
/// Handle to an internal Lua userdata for any type that implements [`UserData`].
///
/// Similar to `std::any::Any`, this provides an interface for dynamic type checking via the [`is`]
@@ -748,7 +791,7 @@ impl Serialize for UserDataSerializeError {
/// [`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.
///
@@ -758,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)
}
}
@@ -826,11 +869,11 @@ impl<'lua> AnyUserData<'lua> {
/// Sets an associated value to this `AnyUserData`.
///
/// The value may be any Lua value whatsoever, and can be retrieved with [`get_user_value`].
/// The value may be any Lua value whatsoever, and can be retrieved with [`user_value`].
///
/// This is the same as calling [`set_nth_user_value`] with `n` set to 1.
///
/// [`get_user_value`]: #method.get_user_value
/// [`user_value`]: #method.user_value
/// [`set_nth_user_value`]: #method.set_nth_user_value
#[inline]
pub fn set_user_value<V: IntoLua<'lua>>(&self, v: V) -> Result<()> {
@@ -839,30 +882,34 @@ impl<'lua> AnyUserData<'lua> {
/// Returns an associated value set by [`set_user_value`].
///
/// This is the same as calling [`get_nth_user_value`] with `n` set to 1.
/// This is the same as calling [`nth_user_value`] with `n` set to 1.
///
/// [`set_user_value`]: #method.set_user_value
/// [`get_nth_user_value`]: #method.get_nth_user_value
/// [`nth_user_value`]: #method.nth_user_value
#[inline]
pub fn user_value<V: FromLua<'lua>>(&self) -> Result<V> {
self.nth_user_value(1)
}
#[doc(hidden)]
#[deprecated(since = "0.9.0", note = "please use `user_value` instead")]
pub fn get_user_value<V: FromLua<'lua>>(&self) -> Result<V> {
self.get_nth_user_value(1)
self.nth_user_value(1)
}
/// Sets an associated `n`th value to this `AnyUserData`.
///
/// The value may be any Lua value whatsoever, and can be retrieved with [`get_nth_user_value`].
/// The value may be any Lua value whatsoever, and can be retrieved with [`nth_user_value`].
/// `n` starts from 1 and can be up to 65535.
///
/// This is supported for all Lua versions.
/// In Lua 5.4 first 7 elements are stored in a most efficient way.
/// For other Lua versions this functionality is provided using a wrapping table.
///
/// [`get_nth_user_value`]: #method.get_nth_user_value
/// [`nth_user_value`]: #method.nth_user_value
pub fn set_nth_user_value<V: IntoLua<'lua>>(&self, n: usize, v: V) -> Result<()> {
if n < 1 || n > u16::MAX as usize {
return Err(Error::RuntimeError(
"user value index out of bounds".to_string(),
));
return Err(Error::runtime("user value index out of bounds"));
}
let lua = self.0.lua;
@@ -913,11 +960,9 @@ impl<'lua> AnyUserData<'lua> {
/// For other Lua versions this functionality is provided using a wrapping table.
///
/// [`set_nth_user_value`]: #method.set_nth_user_value
pub fn get_nth_user_value<V: FromLua<'lua>>(&self, n: usize) -> Result<V> {
pub fn nth_user_value<V: FromLua<'lua>>(&self, n: usize) -> Result<V> {
if n < 1 || n > u16::MAX as usize {
return Err(Error::RuntimeError(
"user value index out of bounds".to_string(),
));
return Err(Error::runtime("user value index out of bounds"));
}
let lua = self.0.lua;
@@ -950,18 +995,20 @@ impl<'lua> AnyUserData<'lua> {
}
}
#[doc(hidden)]
#[deprecated(since = "0.9.0", note = "please use `nth_user_value` instead")]
pub fn get_nth_user_value<V: FromLua<'lua>>(&self, n: usize) -> Result<V> {
self.nth_user_value(n)
}
/// Sets an associated value to this `AnyUserData` by name.
///
/// The value can be retrieved with [`get_named_user_value`].
/// The value can be retrieved with [`named_user_value`].
///
/// [`get_named_user_value`]: #method.get_named_user_value
pub fn set_named_user_value<V>(&self, name: impl AsRef<str>, v: V) -> Result<()>
where
V: IntoLua<'lua>,
{
/// [`named_user_value`]: #method.named_user_value
pub fn set_named_user_value<V: IntoLua<'lua>>(&self, name: &str, v: V) -> Result<()> {
let lua = self.0.lua;
let state = lua.state();
let name = name.as_ref();
unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 5)?;
@@ -994,13 +1041,9 @@ impl<'lua> AnyUserData<'lua> {
/// Returns an associated value by name set by [`set_named_user_value`].
///
/// [`set_named_user_value`]: #method.set_named_user_value
pub fn get_named_user_value<V>(&self, name: impl AsRef<str>) -> Result<V>
where
V: FromLua<'lua>,
{
pub fn named_user_value<V: FromLua<'lua>>(&self, name: &str) -> Result<V> {
let lua = self.0.lua;
let state = lua.state();
let name = name.as_ref();
unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 4)?;
@@ -1021,6 +1064,12 @@ impl<'lua> AnyUserData<'lua> {
}
}
#[doc(hidden)]
#[deprecated(since = "0.9.0", note = "please use `named_user_value` instead")]
pub fn get_named_user_value<V: FromLua<'lua>>(&self, name: &str) -> Result<V> {
self.named_user_value(name)
}
/// Returns a metatable of this `UserData`.
///
/// Returned [`UserDataMetatable`] object wraps the original metatable and
@@ -1052,11 +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)
}
/// Returns a type name of this `UserData` (from `__name` metatable field).
#[cfg(feature = "async")]
#[inline]
pub(crate) fn type_id(&self) -> Result<Option<TypeId>> {
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 {
@@ -1067,10 +1130,10 @@ impl<'lua> AnyUserData<'lua> {
let protect = !lua.unlikely_memory_error();
let name_type = if protect {
protect_lua!(state, 1, 1, |state| {
ffi::luaL_getmetafield(state, -1, cstr!("__name"))
ffi::luaL_getmetafield(state, -1, MetaMethod::Type.as_cstr().as_ptr())
})?
} else {
ffi::luaL_getmetafield(state, -1, cstr!("__name"))
ffi::luaL_getmetafield(state, -1, MetaMethod::Type.as_cstr().as_ptr())
};
match name_type {
ffi::LUA_TSTRING => Ok(Some(String(lua.pop_ref()).to_str()?.to_owned())),
@@ -1100,19 +1163,15 @@ 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 state = lua.state();
let is_serializable = || unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 2)?;
// Userdata can be unregistered or destructed
lua.push_userdata_ref(&self.0)?;
let _ = lua.get_userdata_ref_type_id(&self.0)?;
let ud = &*get_userdata::<UserDataCell<()>>(state, -1);
let ud = &*get_userdata::<UserDataCell<()>>(lua.ref_thread(), self.0.index);
match &*ud.0.try_borrow().map_err(|_| Error::UserDataBorrowError)? {
UserDataVariant::Serializable(_) => Result::Ok(true),
_ => Result::Ok(false),
@@ -1127,15 +1186,12 @@ impl<'lua> AnyUserData<'lua> {
F: FnOnce(&'a UserDataCell<T>) -> Result<R>,
{
let lua = self.0.lua;
let state = lua.state();
unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 2)?;
let type_id = lua.push_userdata_ref(&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>() => {
func(&*get_userdata::<UserDataCell<T>>(state, -1))
let ref_thread = lua.ref_thread();
func(&*get_userdata::<UserDataCell<T>>(ref_thread, self.0.index))
}
_ => Err(Error::UserDataTypeMismatch),
}
@@ -1278,19 +1334,17 @@ impl<'lua> Serialize for AnyUserData<'lua> {
S: Serializer,
{
let lua = self.0.lua;
let state = lua.state();
let data = unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 3).map_err(ser::Error::custom)?;
lua.push_userdata_ref(&self.0).map_err(ser::Error::custom)?;
let ud = &*get_userdata::<UserDataCell<()>>(state, -1);
let _ = lua
.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()
.map_err(|_| ser::Error::custom(Error::UserDataBorrowError))?
};
match &*data {
UserDataVariant::Serializable(ser) => ser.serialize(serializer),
_ => UserDataSerializeError.serialize(serializer),
_ => Err(ser::Error::custom("cannot serialize <userdata>")),
}
}
}
@@ -1345,6 +1399,27 @@ impl<'lua, T: 'static> UserDataRefMut<'lua, T> {
}
}
pub(crate) struct WrappedUserdata<F: for<'lua> FnOnce(&'lua Lua) -> Result<AnyUserData<'lua>>>(F);
impl<'lua> AnyUserData<'lua> {
/// Wraps any Rust type, returning an opaque type that implements [`IntoLua`] trait.
///
/// This function uses [`Lua::create_any_userdata()`] under the hood.
pub fn wrap<T: MaybeSend + 'static>(data: T) -> impl IntoLua<'lua> {
WrappedUserdata(move |lua| lua.create_any_userdata(data))
}
}
impl<'lua, F> IntoLua<'lua> for WrappedUserdata<F>
where
F: for<'l> FnOnce(&'l Lua) -> Result<AnyUserData<'l>>,
{
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
(self.0)(lua).map(Value::UserData)
}
}
#[inline]
fn try_value_to_userdata<T>(value: Value) -> Result<AnyUserData> {
match value {
Value::UserData(ud) => Ok(ud),
+46 -61
View File
@@ -27,15 +27,14 @@ pub trait AnyUserDataExt<'lua>: Sealed {
/// The metamethod is called with the userdata as its first argument, followed by the passed arguments.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn call_async<'fut, A, R>(&self, args: A) -> LocalBoxFuture<'fut, Result<R>>
fn call_async<A, R>(&self, args: A) -> LocalBoxFuture<'lua, Result<R>>
where
'lua: 'fut,
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut;
R: FromLuaMulti<'lua> + 'lua;
/// Calls the userdata method, assuming it has `__index` metamethod
/// and a function associated to `name`.
fn call_method<A, R>(&self, name: impl AsRef<str>, args: A) -> Result<R>
fn call_method<A, R>(&self, name: &str, args: A) -> Result<R>
where
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua>;
@@ -48,15 +47,10 @@ pub trait AnyUserDataExt<'lua>: Sealed {
/// This might invoke the `__index` metamethod.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn call_async_method<'fut, A, R>(
&self,
name: impl AsRef<str>,
args: A,
) -> LocalBoxFuture<'fut, Result<R>>
fn call_async_method<A, R>(&self, name: &str, args: A) -> LocalBoxFuture<'lua, Result<R>>
where
'lua: 'fut,
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut;
R: FromLuaMulti<'lua> + 'lua;
/// Gets the function associated to `key` from the table and executes it,
/// passing `args` as function arguments.
@@ -65,7 +59,7 @@ pub trait AnyUserDataExt<'lua>: Sealed {
/// `table.get::<_, Function>(key)?.call(args)`
///
/// This might invoke the `__index` metamethod.
fn call_function<A, R>(&self, name: impl AsRef<str>, args: A) -> Result<R>
fn call_function<A, R>(&self, name: &str, args: A) -> Result<R>
where
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua>;
@@ -78,15 +72,10 @@ pub trait AnyUserDataExt<'lua>: Sealed {
/// This might invoke the `__index` metamethod.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn call_async_function<'fut, A, R>(
&self,
name: impl AsRef<str>,
args: A,
) -> LocalBoxFuture<'fut, Result<R>>
fn call_async_function<A, R>(&self, name: &str, args: A) -> LocalBoxFuture<'lua, Result<R>>
where
'lua: 'fut,
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut;
R: FromLuaMulti<'lua> + 'lua;
}
impl<'lua> AnyUserDataExt<'lua> for AnyUserData<'lua> {
@@ -95,9 +84,7 @@ impl<'lua> AnyUserDataExt<'lua> for AnyUserData<'lua> {
match metatable.get::<Value>(MetaMethod::Index)? {
Value::Table(table) => table.raw_get(key),
Value::Function(func) => func.call((self.clone(), key)),
_ => Err(Error::RuntimeError(
"attempt to index a userdata value".to_string(),
)),
_ => Err(Error::runtime("attempt to index a userdata value")),
}
}
@@ -106,9 +93,7 @@ impl<'lua> AnyUserDataExt<'lua> for AnyUserData<'lua> {
match metatable.get::<Value>(MetaMethod::NewIndex)? {
Value::Table(table) => table.raw_set(key, value),
Value::Function(func) => func.call((self.clone(), key, value)),
_ => Err(Error::RuntimeError(
"attempt to index a userdata value".to_string(),
)),
_ => Err(Error::runtime("attempt to index a userdata value")),
}
}
@@ -120,33 +105,37 @@ impl<'lua> AnyUserDataExt<'lua> for AnyUserData<'lua> {
let metatable = self.get_metatable()?;
match metatable.get::<Value>(MetaMethod::Call)? {
Value::Function(func) => func.call((self.clone(), args)),
_ => Err(Error::RuntimeError(
"attempt to call a userdata value".to_string(),
)),
_ => Err(Error::runtime("attempt to call a userdata value")),
}
}
#[cfg(feature = "async")]
fn call_async<'fut, A, R>(&self, args: A) -> LocalBoxFuture<'fut, Result<R>>
fn call_async<A, R>(&self, args: A) -> LocalBoxFuture<'lua, Result<R>>
where
'lua: 'fut,
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut,
R: FromLuaMulti<'lua> + 'lua,
{
let metatable = match self.get_metatable() {
Ok(metatable) => metatable,
Err(err) => return Box::pin(future::err(err)),
};
match metatable.get::<Value>(MetaMethod::Call) {
Ok(Value::Function(func)) => func.call_async((self.clone(), args)),
Ok(_) => Box::pin(future::err(Error::RuntimeError(
"attempt to call a userdata value".to_string(),
Ok(Value::Function(func)) => {
let mut args = match args.into_lua_multi(self.0.lua) {
Ok(args) => args,
Err(e) => return Box::pin(future::err(e)),
};
args.push_front(Value::UserData(self.clone()));
Box::pin(async move { func.call_async(args).await })
}
Ok(_) => Box::pin(future::err(Error::runtime(
"attempt to call a userdata value",
))),
Err(err) => Box::pin(future::err(err)),
}
}
fn call_method<A, R>(&self, name: impl AsRef<str>, args: A) -> Result<R>
fn call_method<A, R>(&self, name: &str, args: A) -> Result<R>
where
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua>,
@@ -155,50 +144,46 @@ impl<'lua> AnyUserDataExt<'lua> for AnyUserData<'lua> {
}
#[cfg(feature = "async")]
fn call_async_method<'fut, A, R>(
&self,
name: impl AsRef<str>,
args: A,
) -> LocalBoxFuture<'fut, Result<R>>
fn call_async_method<A, R>(&self, name: &str, args: A) -> LocalBoxFuture<'lua, Result<R>>
where
'lua: 'fut,
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut,
R: FromLuaMulti<'lua> + 'lua,
{
self.call_async_function(name, (self.clone(), args))
}
fn call_function<A, R>(&self, name: impl AsRef<str>, args: A) -> Result<R>
fn call_function<A, R>(&self, name: &str, args: A) -> Result<R>
where
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua>,
{
match self.get(name.as_ref())? {
match self.get(name)? {
Value::Function(func) => func.call(args),
val => Err(Error::RuntimeError(format!(
"attempt to call a {} value",
val.type_name()
))),
val => {
let msg = format!("attempt to call a {} value", val.type_name());
Err(Error::runtime(msg))
}
}
}
#[cfg(feature = "async")]
fn call_async_function<'fut, A, R>(
&self,
name: impl AsRef<str>,
args: A,
) -> LocalBoxFuture<'fut, Result<R>>
fn call_async_function<A, R>(&self, name: &str, args: A) -> LocalBoxFuture<'lua, Result<R>>
where
'lua: 'fut,
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'fut,
R: FromLuaMulti<'lua> + 'lua,
{
match self.get(name.as_ref()) {
Ok(Value::Function(func)) => func.call_async(args),
Ok(val) => Box::pin(future::err(Error::RuntimeError(format!(
"attempt to call a {} value",
val.type_name()
)))),
match self.get(name) {
Ok(Value::Function(func)) => {
let args = match args.into_lua_multi(self.0.lua) {
Ok(args) => args,
Err(e) => return Box::pin(future::err(e)),
};
Box::pin(async move { func.call_async(args).await })
}
Ok(val) => {
let msg = format!("attempt to call a {} value", val.type_name());
Box::pin(future::err(Error::runtime(msg)))
}
Err(err) => Box::pin(future::err(err)),
}
}
+366 -243
View File
@@ -1,6 +1,9 @@
#![allow(clippy::await_holding_refcell_ref, clippy::await_holding_lock)]
use std::any::TypeId;
use std::cell::{Ref, RefCell, RefMut};
use std::marker::PhantomData;
use std::os::raw::c_int;
use std::string::String as StdString;
use std::sync::{Arc, Mutex, RwLock};
@@ -10,20 +13,17 @@ use crate::types::{Callback, MaybeSend};
use crate::userdata::{
AnyUserData, MetaMethod, UserData, UserDataCell, UserDataFields, UserDataMethods,
};
use crate::util::{check_stack, get_userdata, short_type_name, StackGuard};
use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, MultiValue, Value};
use crate::util::{get_userdata, short_type_name};
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};
pub struct UserDataRegistrar<'lua, T: 'static> {
/// Handle to registry for userdata methods and metamethods.
pub struct UserDataRegistry<'lua, T: 'static> {
// Fields
pub(crate) fields: Vec<(String, Callback<'lua, 'static>)>,
pub(crate) field_getters: Vec<(String, Callback<'lua, 'static>)>,
@@ -41,9 +41,9 @@ pub struct UserDataRegistrar<'lua, T: 'static> {
_type: PhantomData<T>,
}
impl<'lua, T: 'static> UserDataRegistrar<'lua, T> {
impl<'lua, T: 'static> UserDataRegistry<'lua, T> {
pub(crate) const fn new() -> Self {
UserDataRegistrar {
UserDataRegistry {
fields: Vec::new(),
field_getters: Vec::new(),
field_setters: Vec::new(),
@@ -74,63 +74,62 @@ impl<'lua, T: 'static> UserDataRegistrar<'lua, T> {
};
}
Box::new(move |lua, mut args| {
let front = args.pop_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 let Some(front) = front {
let state = lua.state();
let userdata = try_self_arg!(AnyUserData::from_lua(front, lua));
unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 2)?;
let type_id = try_self_arg!(lua.push_userdata_ref(&userdata.0));
match type_id {
Some(id) if id == TypeId::of::<T>() => {
let ud = try_self_arg!(get_userdata_ref::<T>(state));
call(&ud)
}
#[cfg(not(feature = "send"))]
Some(id) if id == TypeId::of::<Rc<RefCell<T>>>() => {
let ud = try_self_arg!(get_userdata_ref::<Rc<RefCell<T>>>(state));
let ud = try_self_arg!(ud.try_borrow(), Error::UserDataBorrowError);
call(&ud)
}
Some(id) if id == TypeId::of::<Arc<Mutex<T>>>() => {
let ud = try_self_arg!(get_userdata_ref::<Arc<Mutex<T>>>(state));
let ud = try_self_arg!(ud.try_lock(), Error::UserDataBorrowError);
call(&ud)
}
#[cfg(feature = "parking_lot")]
Some(id) if id == TypeId::of::<Arc<parking_lot::Mutex<T>>>() => {
let ud = get_userdata_ref::<Arc<parking_lot::Mutex<T>>>(state);
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>>>() => {
let ud = try_self_arg!(get_userdata_ref::<Arc<RwLock<T>>>(state));
let ud = try_self_arg!(ud.try_read(), Error::UserDataBorrowError);
call(&ud)
}
#[cfg(feature = "parking_lot")]
Some(id) if id == TypeId::of::<Arc<parking_lot::RwLock<T>>>() => {
let ud = get_userdata_ref::<Arc<parking_lot::RwLock<T>>>(state);
let ud = try_self_arg!(ud);
let ud = try_self_arg!(ud.try_read().ok_or(Error::UserDataBorrowError));
call(&ud)
}
_ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)),
}
}
} else {
Box::new(move |lua, nargs| unsafe {
if nargs == 0 {
let err = Error::from_lua_conversion("missing argument", "userdata", None);
Err(Error::bad_self_argument(&name, err))
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);
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>>() => {
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>>>() => {
let ud = try_self_arg!(get_userdata_ref::<Rc<RefCell<T>>>(state, index));
let ud = try_self_arg!(ud.try_borrow(), Error::UserDataBorrowError);
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);
method(lua, &ud, args?)?.push_into_stack_multi(lua)
}
#[cfg(feature = "parking_lot")]
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));
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);
method(lua, &ud, args?)?.push_into_stack_multi(lua)
}
#[cfg(feature = "parking_lot")]
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));
method(lua, &ud, args?)?.push_into_stack_multi(lua)
}
_ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)),
}
})
}
@@ -152,162 +151,246 @@ impl<'lua, T: 'static> UserDataRegistrar<'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();
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 let Some(front) = front {
let state = lua.state();
let userdata = try_self_arg!(AnyUserData::from_lua(front, lua));
unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 2)?;
let type_id = try_self_arg!(lua.push_userdata_ref(&userdata.0));
match type_id {
Some(id) if id == TypeId::of::<T>() => {
let mut ud = try_self_arg!(get_userdata_mut::<T>(state));
call(&mut ud)
}
#[cfg(not(feature = "send"))]
Some(id) if id == TypeId::of::<Rc<RefCell<T>>>() => {
let ud = try_self_arg!(get_userdata_mut::<Rc<RefCell<T>>>(state));
let mut ud =
try_self_arg!(ud.try_borrow_mut(), Error::UserDataBorrowMutError);
call(&mut ud)
}
Some(id) if id == TypeId::of::<Arc<Mutex<T>>>() => {
let ud = try_self_arg!(get_userdata_mut::<Arc<Mutex<T>>>(state));
let mut ud =
try_self_arg!(ud.try_lock(), Error::UserDataBorrowMutError);
call(&mut ud)
}
#[cfg(feature = "parking_lot")]
Some(id) if id == TypeId::of::<Arc<parking_lot::Mutex<T>>>() => {
let ud = get_userdata_mut::<Arc<parking_lot::Mutex<T>>>(state);
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>>>() => {
let ud = try_self_arg!(get_userdata_mut::<Arc<RwLock<T>>>(state));
let mut ud =
try_self_arg!(ud.try_write(), Error::UserDataBorrowMutError);
call(&mut ud)
}
#[cfg(feature = "parking_lot")]
Some(id) if id == TypeId::of::<Arc<parking_lot::RwLock<T>>>() => {
let ud = get_userdata_mut::<Arc<parking_lot::RwLock<T>>>(state);
let ud = try_self_arg!(ud);
let mut ud =
try_self_arg!(ud.try_write().ok_or(Error::UserDataBorrowMutError));
call(&mut ud)
}
_ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)),
}
}
} else {
if nargs == 0 {
let err = Error::from_lua_conversion("missing argument", "userdata", None);
Err(Error::bad_self_argument(&name, err))
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);
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>>>() => {
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);
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>>>() => {
let ud = try_self_arg!(get_userdata_mut::<Arc<Mutex<T>>>(state, index));
let mut ud = try_self_arg!(ud.try_lock(), Error::UserDataBorrowMutError);
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>>>() => {
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));
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);
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>>>() => {
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));
method(lua, &mut ud, args?)?.push_into_stack_multi(lua)
}
_ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)),
}
})
}
#[cfg(feature = "async")]
fn box_async_method<M, A, MR, R>(name: &str, method: M) -> AsyncCallback<'lua, 'static>
fn box_async_method<'s, M, A, MR, R>(name: &str, method: M) -> AsyncCallback<'lua, 'static>
where
T: Clone,
M: Fn(&'lua Lua, T, A) -> MR + MaybeSend + 'static,
'lua: 's,
T: 'static,
M: Fn(&'lua Lua, &'s T, A) -> MR + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
MR: Future<Output = Result<R>> + 'lua,
MR: Future<Output = Result<R>> + 's,
R: IntoLuaMulti<'lua>,
{
let name = get_function_name::<T>(name);
macro_rules! try_self_arg {
($res:expr) => {
$res.map_err(|err| Error::bad_self_argument(&name, err))?
};
($res:expr, $err:expr) => {
$res.map_err(|_| Error::bad_self_argument(&name, $err))?
};
}
let name = Arc::new(get_function_name::<T>(name));
let method = Arc::new(method);
Box::new(move |lua, mut args| {
let front = args.pop_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)?;
Ok(method(lua, ud, args))
};
let fut_res = || {
if let Some(front) = front {
let state = lua.state();
let userdata = AnyUserData::from_lua(front, lua)?;
unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 2)?;
let type_id = try_self_arg!(lua.push_userdata_ref(&userdata.0));
match type_id {
Some(id) if id == TypeId::of::<T>() => {
let ud = get_userdata_ref::<T>(state)?;
call(ud.clone())
}
#[cfg(not(feature = "send"))]
Some(id) if id == TypeId::of::<Rc<RefCell<T>>>() => {
let ud = try_self_arg!(get_userdata_ref::<Rc<RefCell<T>>>(state));
let ud = try_self_arg!(ud.try_borrow(), Error::UserDataBorrowError);
call(ud.clone())
}
Some(id) if id == TypeId::of::<Arc<Mutex<T>>>() => {
let ud = try_self_arg!(get_userdata_ref::<Arc<Mutex<T>>>(state));
let ud = try_self_arg!(ud.try_lock(), Error::UserDataBorrowError);
call(ud.clone())
}
#[cfg(feature = "parking_lot")]
Some(id) if id == TypeId::of::<Arc<parking_lot::Mutex<T>>>() => {
let ud = get_userdata_ref::<Arc<parking_lot::Mutex<T>>>(state);
let ud = try_self_arg!(ud);
let ud =
try_self_arg!(ud.try_lock().ok_or(Error::UserDataBorrowError));
call(ud.clone())
}
Some(id) if id == TypeId::of::<Arc<RwLock<T>>>() => {
let ud = try_self_arg!(get_userdata_ref::<Arc<RwLock<T>>>(state));
let ud = try_self_arg!(ud.try_read(), Error::UserDataBorrowError);
call(ud.clone())
}
#[cfg(feature = "parking_lot")]
Some(id) if id == TypeId::of::<Arc<parking_lot::RwLock<T>>>() => {
let ud = get_userdata_ref::<Arc<parking_lot::RwLock<T>>>(state);
let ud = try_self_arg!(ud);
let ud =
try_self_arg!(ud.try_read().ok_or(Error::UserDataBorrowError));
call(ud.clone())
}
_ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)),
}
}
} else {
let err = Error::from_lua_conversion("missing argument", "userdata", None);
Err(Error::bad_self_argument(&name, err))
}
};
match fut_res() {
Ok(fut) => {
Box::pin(fut.and_then(move |ret| future::ready(ret.into_lua_multi(lua))))
}
Err(e) => Box::pin(future::err(e)),
Box::new(move |lua, mut args| unsafe {
let name = name.clone();
let method = method.clone();
macro_rules! try_self_arg {
($res:expr) => {
$res.map_err(|err| Error::bad_self_argument(&name, err))?
};
($res:expr, $err:expr) => {
$res.map_err(|_| Error::bad_self_argument(&name, $err))?
};
}
Box::pin(async move {
let this = args.pop_front().ok_or_else(|| {
Error::from_lua_conversion("missing argument", "userdata", None)
});
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);
method(lua, ud, args?).await?.push_into_stack_multi(lua)
}
#[cfg(not(feature = "send"))]
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);
method(lua, ud, args?).await?.push_into_stack_multi(lua)
}
#[cfg(not(feature = "send"))]
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);
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);
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);
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>>>() => {
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);
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);
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>>>() => {
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);
method(lua, ud, args?).await?.push_into_stack_multi(lua)
}
_ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)),
}
})
})
}
#[cfg(feature = "async")]
fn box_async_method_mut<'s, M, A, MR, R>(name: &str, method: M) -> AsyncCallback<'lua, 'static>
where
'lua: 's,
T: 'static,
M: Fn(&'lua Lua, &'s mut T, A) -> MR + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
MR: Future<Output = Result<R>> + 's,
R: IntoLuaMulti<'lua>,
{
let name = Arc::new(get_function_name::<T>(name));
let method = Arc::new(method);
Box::new(move |lua, mut args| unsafe {
let name = name.clone();
let method = method.clone();
macro_rules! try_self_arg {
($res:expr) => {
$res.map_err(|err| Error::bad_self_argument(&name, err))?
};
($res:expr, $err:expr) => {
$res.map_err(|_| Error::bad_self_argument(&name, $err))?
};
}
Box::pin(async move {
let this = args.pop_front().ok_or_else(|| {
Error::from_lua_conversion("missing argument", "userdata", None)
});
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);
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>>>() => {
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);
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>>>() => {
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);
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>>>() => {
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);
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);
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>>>() => {
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);
method(lua, ud, args?).await?.push_into_stack_multi(lua)
}
_ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)),
}
})
})
}
@@ -318,8 +401,9 @@ impl<'lua, T: 'static> UserDataRegistrar<'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)
})
}
@@ -331,11 +415,12 @@ impl<'lua, T: 'static> UserDataRegistrar<'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)
})
}
@@ -348,22 +433,17 @@ impl<'lua, T: 'static> UserDataRegistrar<'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>,
{
@@ -380,7 +460,7 @@ impl<'lua, T: 'static> UserDataRegistrar<'lua, T> {
}
}
}
value.into_lua_multi(lua)
value.into_lua(lua)
}
}
@@ -389,7 +469,7 @@ fn get_function_name<T>(name: &str) -> StdString {
format!("{}.{name}", short_type_name::<T>())
}
impl<'lua, T: 'static> UserDataFields<'lua, T> for UserDataRegistrar<'lua, T> {
impl<'lua, T: 'static> UserDataFields<'lua, T> for UserDataRegistry<'lua, T> {
fn add_field<V>(&mut self, name: impl AsRef<str>, value: V)
where
V: IntoLua<'lua> + Clone + 'static,
@@ -397,7 +477,7 @@ impl<'lua, T: 'static> UserDataFields<'lua, T> for UserDataRegistrar<'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) }),
));
}
@@ -449,7 +529,9 @@ impl<'lua, T: 'static> UserDataFields<'lua, T> for UserDataRegistrar<'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)
}),
));
}
@@ -462,13 +544,15 @@ impl<'lua, T: 'static> UserDataFields<'lua, T> for UserDataRegistrar<'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)
}),
));
}
// Below are internal methods
fn append_fields_from<S>(&mut self, other: UserDataRegistrar<'lua, S>) {
fn append_fields_from<S>(&mut self, other: UserDataRegistry<'lua, S>) {
self.fields.extend(other.fields);
self.field_getters.extend(other.field_getters);
self.field_setters.extend(other.field_setters);
@@ -476,7 +560,7 @@ impl<'lua, T: 'static> UserDataFields<'lua, T> for UserDataRegistrar<'lua, T> {
}
}
impl<'lua, T: 'static> UserDataMethods<'lua, T> for UserDataRegistrar<'lua, T> {
impl<'lua, T: 'static> UserDataMethods<'lua, T> for UserDataRegistry<'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,
@@ -500,12 +584,13 @@ impl<'lua, T: 'static> UserDataMethods<'lua, T> for UserDataRegistrar<'lua, T> {
}
#[cfg(feature = "async")]
fn add_async_method<M, A, MR, R>(&mut self, name: impl AsRef<str>, method: M)
fn add_async_method<'s, M, A, MR, R>(&mut self, name: impl AsRef<str>, method: M)
where
T: Clone,
M: Fn(&'lua Lua, T, A) -> MR + MaybeSend + 'static,
'lua: 's,
T: 'static,
M: Fn(&'lua Lua, &'s T, A) -> MR + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
MR: Future<Output = Result<R>> + 'lua,
MR: Future<Output = Result<R>> + 's,
R: IntoLuaMulti<'lua>,
{
let name = name.as_ref();
@@ -513,6 +598,21 @@ impl<'lua, T: 'static> UserDataMethods<'lua, T> for UserDataRegistrar<'lua, T> {
.push((name.into(), Self::box_async_method(name, method)));
}
#[cfg(feature = "async")]
fn add_async_method_mut<'s, M, A, MR, R>(&mut self, name: impl AsRef<str>, method: M)
where
'lua: 's,
T: 'static,
M: Fn(&'lua Lua, &'s mut T, A) -> MR + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
MR: Future<Output = Result<R>> + 's,
R: IntoLuaMulti<'lua>,
{
let name = name.as_ref();
self.async_methods
.push((name.into(), Self::box_async_method_mut(name, method)));
}
fn add_function<F, A, R>(&mut self, name: impl AsRef<str>, function: F)
where
F: Fn(&'lua Lua, A) -> Result<R> + MaybeSend + 'static,
@@ -571,12 +671,13 @@ impl<'lua, T: 'static> UserDataMethods<'lua, T> for UserDataRegistrar<'lua, T> {
}
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
fn add_async_meta_method<M, A, MR, R>(&mut self, name: impl AsRef<str>, method: M)
fn add_async_meta_method<'s, M, A, MR, R>(&mut self, name: impl AsRef<str>, method: M)
where
T: Clone,
M: Fn(&'lua Lua, T, A) -> MR + MaybeSend + 'static,
'lua: 's,
T: 'static,
M: Fn(&'lua Lua, &'s T, A) -> MR + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
MR: Future<Output = Result<R>> + 'lua,
MR: Future<Output = Result<R>> + 's,
R: IntoLuaMulti<'lua>,
{
let name = name.as_ref();
@@ -584,6 +685,21 @@ impl<'lua, T: 'static> UserDataMethods<'lua, T> for UserDataRegistrar<'lua, T> {
.push((name.into(), Self::box_async_method(name, method)));
}
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
fn add_async_meta_method_mut<'s, M, A, MR, R>(&mut self, name: impl AsRef<str>, method: M)
where
'lua: 's,
T: 'static,
M: Fn(&'lua Lua, &'s mut T, A) -> MR + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
MR: Future<Output = Result<R>> + 's,
R: IntoLuaMulti<'lua>,
{
let name = name.as_ref();
self.async_meta_methods
.push((name.into(), Self::box_async_method_mut(name, method)));
}
fn add_meta_function<F, A, R>(&mut self, name: impl AsRef<str>, function: F)
where
F: Fn(&'lua Lua, A) -> Result<R> + MaybeSend + 'static,
@@ -621,7 +737,7 @@ impl<'lua, T: 'static> UserDataMethods<'lua, T> for UserDataRegistrar<'lua, T> {
// Below are internal methods used in generated code
fn append_methods_from<S>(&mut self, other: UserDataRegistrar<'lua, S>) {
fn append_methods_from<S>(&mut self, other: UserDataRegistry<'lua, S>) {
self.methods.extend(other.methods);
#[cfg(feature = "async")]
self.async_methods.extend(other.async_methods);
@@ -632,26 +748,29 @@ impl<'lua, T: 'static> UserDataMethods<'lua, T> for UserDataRegistrar<'lua, T> {
}
#[inline]
unsafe fn get_userdata_ref<'a, T>(state: *mut ffi::lua_State) -> Result<Ref<'a, T>> {
(*get_userdata::<UserDataCell<T>>(state, -1)).try_borrow()
unsafe fn get_userdata_ref<'a, T>(state: *mut ffi::lua_State, index: c_int) -> Result<Ref<'a, T>> {
(*get_userdata::<UserDataCell<T>>(state, index)).try_borrow()
}
#[inline]
unsafe fn get_userdata_mut<'a, T>(state: *mut ffi::lua_State) -> Result<RefMut<'a, T>> {
(*get_userdata::<UserDataCell<T>>(state, -1)).try_borrow_mut()
unsafe fn get_userdata_mut<'a, T>(
state: *mut ffi::lua_State,
index: c_int,
) -> Result<RefMut<'a, T>> {
(*get_userdata::<UserDataCell<T>>(state, index)).try_borrow_mut()
}
macro_rules! lua_userdata_impl {
($type:ty) => {
impl<T: UserData + 'static> UserData for $type {
fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) {
let mut orig_fields = UserDataRegistrar::new();
let mut orig_fields = UserDataRegistry::new();
T::add_fields(&mut orig_fields);
fields.append_fields_from(orig_fields);
}
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
let mut orig_methods = UserDataRegistrar::new();
let mut orig_methods = UserDataRegistry::new();
T::add_methods(&mut orig_methods);
methods.append_methods_from(orig_methods);
}
@@ -659,8 +778,12 @@ macro_rules! lua_userdata_impl {
};
}
#[cfg(not(feature = "send"))]
lua_userdata_impl!(Rc<T>);
#[cfg(not(feature = "send"))]
lua_userdata_impl!(Rc<RefCell<T>>);
lua_userdata_impl!(Arc<T>);
lua_userdata_impl!(Arc<Mutex<T>>);
lua_userdata_impl!(Arc<RwLock<T>>);
#[cfg(feature = "parking_lot")]
+63 -61
View File
@@ -1,11 +1,12 @@
use std::any::{Any, TypeId};
use std::borrow::Cow;
use std::ffi::CStr;
use std::fmt::Write;
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};
use std::{ptr, slice, str};
use once_cell::sync::Lazy;
use rustc_hash::FxHashMap;
@@ -62,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 {
@@ -87,7 +93,7 @@ impl Drop for StackGuard {
pub unsafe fn protect_lua_call(
state: *mut ffi::lua_State,
nargs: c_int,
f: unsafe extern "C" fn(*mut ffi::lua_State) -> c_int,
f: unsafe extern "C-unwind" fn(*mut ffi::lua_State) -> c_int,
) -> Result<()> {
let stack_start = ffi::lua_gettop(state) - nargs;
@@ -132,7 +138,7 @@ where
nresults: c_int,
}
unsafe extern "C" fn do_call<F, R>(state: *mut ffi::lua_State) -> c_int
unsafe extern "C-unwind" fn do_call<F, R>(state: *mut ffi::lua_State) -> c_int
where
F: Fn(*mut ffi::lua_State) -> R,
R: Copy,
@@ -251,10 +257,12 @@ pub unsafe fn push_string(state: *mut ffi::lua_State, s: &[u8], protect: bool) -
#[inline]
pub unsafe fn push_table(
state: *mut ffi::lua_State,
narr: c_int,
nrec: c_int,
narr: usize,
nrec: usize,
protect: bool,
) -> Result<()> {
let narr: c_int = narr.try_into().unwrap_or(c_int::MAX);
let nrec: c_int = nrec.try_into().unwrap_or(c_int::MAX);
if protect {
protect_lua!(state, 0, 1, |state| ffi::lua_createtable(state, narr, nrec))
} else {
@@ -274,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" 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(())
}
@@ -320,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(())
@@ -397,16 +390,16 @@ pub unsafe fn get_gc_userdata<T: Any>(
ud
}
unsafe extern "C" fn lua_error_impl(state: *mut ffi::lua_State) -> c_int {
unsafe extern "C-unwind" fn lua_error_impl(state: *mut ffi::lua_State) -> c_int {
ffi::lua_error(state);
}
unsafe extern "C" fn lua_isfunction_impl(state: *mut ffi::lua_State) -> c_int {
unsafe extern "C-unwind" fn lua_isfunction_impl(state: *mut ffi::lua_State) -> c_int {
ffi::lua_pushboolean(state, ffi::lua_isfunction(state, -1));
1
}
unsafe extern "C" fn lua_istable_impl(state: *mut ffi::lua_State) -> c_int {
unsafe extern "C-unwind" fn lua_istable_impl(state: *mut ffi::lua_State) -> c_int {
ffi::lua_pushboolean(state, ffi::lua_istable(state, -1));
1
}
@@ -543,12 +536,13 @@ pub unsafe fn init_userdata_metatable_newindex(state: *mut ffi::lua_State) -> Re
// captured `__index` if no matches found.
// The same is also applicable for `__newindex` metamethod and `field_setters` table.
// Internally uses 9 stack spaces and does not call checkstack.
pub unsafe fn init_userdata_metatable<T>(
pub unsafe fn init_userdata_metatable(
state: *mut ffi::lua_State,
metatable: c_int,
field_getters: Option<c_int>,
field_setters: Option<c_int>,
methods: Option<c_int>,
extra_init: Option<fn(*mut ffi::lua_State) -> Result<()>>,
) -> Result<()> {
ffi::lua_pushvalue(state, metatable);
@@ -595,10 +589,9 @@ pub unsafe fn init_userdata_metatable<T>(
rawset_field(state, -2, "__newindex")?;
}
#[cfg(not(feature = "luau"))]
{
ffi::lua_pushcfunction(state, userdata_destructor::<T>);
rawset_field(state, -2, "__gc")?;
// Additional initialization
if let Some(extra_init) = extra_init {
extra_init(state)?;
}
ffi::lua_pushboolean(state, 0);
@@ -610,7 +603,7 @@ pub unsafe fn init_userdata_metatable<T>(
}
#[cfg(not(feature = "luau"))]
pub unsafe extern "C" fn userdata_destructor<T>(state: *mut ffi::lua_State) -> c_int {
pub unsafe extern "C-unwind" fn userdata_destructor<T>(state: *mut ffi::lua_State) -> c_int {
// It's probably NOT a good idea to catch Rust panics in finalizer
// Lua 5.4 ignores it, other versions generates `LUA_ERRGCMM` without calling message handler
take_userdata::<T>(state);
@@ -655,8 +648,6 @@ where
Ok(Err(err)) => {
ffi::lua_settop(state, 1);
let wrapped_error = ud as *mut WrappedFailure;
// Build `CallbackError` with traceback
let traceback = if ffi::lua_checkstack(state, ffi::LUA_TRACEBACK_STACK) != 0 {
ffi::luaL_traceback(state, state, ptr::null(), 0);
@@ -667,10 +658,8 @@ where
"<not enough stack space for traceback>".to_string()
};
let cause = Arc::new(err);
ptr::write(
wrapped_error,
WrappedFailure::Error(Error::CallbackError { traceback, cause }),
);
let wrapped_error = WrappedFailure::Error(Error::CallbackError { traceback, cause });
ptr::write(ud, wrapped_error);
get_gc_metatable::<WrappedFailure>(state);
ffi::lua_setmetatable(state, -2);
@@ -678,7 +667,7 @@ where
}
Err(p) => {
ffi::lua_settop(state, 1);
ptr::write(ud as *mut WrappedFailure, WrappedFailure::Panic(Some(p)));
ptr::write(ud, WrappedFailure::Panic(Some(p)));
get_gc_metatable::<WrappedFailure>(state);
ffi::lua_setmetatable(state, -2);
ffi::lua_error(state)
@@ -686,7 +675,7 @@ where
}
}
pub unsafe extern "C" fn error_traceback(state: *mut ffi::lua_State) -> c_int {
pub unsafe extern "C-unwind" fn error_traceback(state: *mut ffi::lua_State) -> c_int {
// Luau calls error handler for memory allocation errors, skip it
// See https://github.com/Roblox/luau/issues/880
#[cfg(feature = "luau")]
@@ -726,7 +715,7 @@ pub unsafe fn error_traceback_thread(state: *mut ffi::lua_State, thread: *mut ff
}
// A variant of `pcall` that does not allow Lua to catch Rust panics from `callback_error`.
pub unsafe extern "C" fn safe_pcall(state: *mut ffi::lua_State) -> c_int {
pub unsafe extern "C-unwind" fn safe_pcall(state: *mut ffi::lua_State) -> c_int {
ffi::luaL_checkstack(state, 2, ptr::null());
let top = ffi::lua_gettop(state);
@@ -752,8 +741,8 @@ pub unsafe extern "C" fn safe_pcall(state: *mut ffi::lua_State) -> c_int {
}
// A variant of `xpcall` that does not allow Lua to catch Rust panics from `callback_error`.
pub unsafe extern "C" fn safe_xpcall(state: *mut ffi::lua_State) -> c_int {
unsafe extern "C" fn xpcall_msgh(state: *mut ffi::lua_State) -> c_int {
pub unsafe extern "C-unwind" fn safe_xpcall(state: *mut ffi::lua_State) -> c_int {
unsafe extern "C-unwind" fn xpcall_msgh(state: *mut ffi::lua_State) -> c_int {
ffi::luaL_checkstack(state, 2, ptr::null());
if let Some(WrappedFailure::Panic(_)) =
@@ -867,7 +856,7 @@ pub unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<()> {
// Create error and panic metatables
unsafe extern "C" fn error_tostring(state: *mut ffi::lua_State) -> c_int {
unsafe extern "C-unwind" fn error_tostring(state: *mut ffi::lua_State) -> c_int {
callback_error(state, |_| {
check_stack(state, 3)?;
@@ -925,7 +914,7 @@ pub unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<()> {
// Create destructed userdata metatable
unsafe extern "C" fn destructed_error(state: *mut ffi::lua_State) -> c_int {
unsafe extern "C-unwind" fn destructed_error(state: *mut ffi::lua_State) -> c_int {
callback_error(state, |_| Err(Error::CallbackDestructed))
}
@@ -1005,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" 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
}
@@ -1044,7 +1027,10 @@ pub(crate) unsafe fn to_string(state: *mut ffi::lua_State, index: c_int) -> Stri
let v = ffi::lua_tovector(state, index);
mlua_debug_assert!(!v.is_null(), "vector is null");
let (x, y, z) = (*v, *v.add(1), *v.add(2));
format!("vector({x},{y},{z})")
#[cfg(not(feature = "luau-vector4"))]
return format!("vector({x}, {y}, {z})");
#[cfg(feature = "luau-vector4")]
return format!("vector({x}, {y}, {z}, {w})", w = *v.add(3));
}
ffi::LUA_TSTRING => {
let mut size = 0;
@@ -1057,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(),
}
}
@@ -1066,11 +1054,25 @@ pub(crate) unsafe fn get_destructed_userdata_metatable(state: *mut ffi::lua_Stat
ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, key);
}
pub(crate) unsafe fn ptr_to_cstr_bytes<'a>(input: *const c_char) -> Option<&'a [u8]> {
pub(crate) unsafe fn ptr_to_str<'a>(input: *const c_char) -> Option<&'a str> {
if input.is_null() {
return None;
}
Some(CStr::from_ptr(input).to_bytes())
str::from_utf8(CStr::from_ptr(input).to_bytes()).ok()
}
pub(crate) unsafe fn ptr_to_lossy_str<'a>(input: *const c_char) -> Option<Cow<'a, str>> {
if input.is_null() {
return None;
}
Some(String::from_utf8_lossy(CStr::from_ptr(input).to_bytes()))
}
pub(crate) fn linenumber_to_usize(n: c_int) -> Option<usize> {
match n {
n if n < 0 => None,
n => Some(n as usize),
}
}
static DESTRUCTED_USERDATA_METATABLE: u8 = 0;
+3 -2
View File
@@ -1,5 +1,6 @@
//! Mostly copied from bevy_utils
//! https://github.com/bevyengine/bevy/blob/main/crates/bevy_utils/src/short_names.rs
//! Mostly copied from [bevy_utils]
//!
//! [bevy_utils]: https://github.com/bevyengine/bevy/blob/main/crates/bevy_utils/src/short_names.rs
use std::any::type_name;
+545 -80
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};
@@ -44,7 +48,7 @@ pub enum Value<'lua> {
/// A Luau vector.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
Vector(f32, f32, f32),
Vector(crate::types::Vector),
/// An interned string, managed by Lua.
///
/// Unlike Rust strings, Lua strings may not be valid UTF-8.
@@ -79,12 +83,16 @@ impl<'lua> Value<'lua> {
Value::Integer(_) => "integer",
Value::Number(_) => "number",
#[cfg(feature = "luau")]
Value::Vector(_, _, _) => "vector",
Value::Vector(_) => "vector",
Value::String(_) => "string",
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",
}
}
@@ -116,18 +124,14 @@ impl<'lua> Value<'lua> {
/// Typically this function is used only for hashing and debug information.
#[inline]
pub fn to_pointer(&self) -> *const c_void {
unsafe {
match self {
Value::LightUserData(ud) => ud.0,
Value::Table(t) => t.to_pointer(),
Value::String(s) => s.to_pointer(),
Value::Function(Function(r))
| Value::Thread(Thread(r))
| Value::UserData(AnyUserData(r)) => {
ffi::lua_topointer(r.lua.ref_thread(), r.index)
}
_ => ptr::null(),
}
match self {
Value::LightUserData(ud) => ud.0,
Value::String(String(r))
| Value::Table(Table(r))
| Value::Function(Function(r))
| Value::Thread(Thread(r, ..))
| Value::UserData(AnyUserData(r, ..)) => r.to_pointer(),
_ => ptr::null(),
}
}
@@ -143,12 +147,12 @@ impl<'lua> Value<'lua> {
Value::Integer(i) => Ok(i.to_string()),
Value::Number(n) => Ok(n.to_string()),
#[cfg(feature = "luau")]
Value::Vector(x, y, z) => Ok(format!("vector({x}, {y}, {z})")),
Value::Vector(v) => Ok(v.to_string()),
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)?;
@@ -163,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 {
@@ -218,7 +501,7 @@ impl<'lua> Value<'lua> {
Value::Integer(i) => write!(fmt, "{i}"),
Value::Number(n) => write!(fmt, "{n}"),
#[cfg(feature = "luau")]
Value::Vector(x, y, z) => write!(fmt, "vector({x}, {y}, {z})"),
Value::Vector(v) => write!(fmt, "{v}"),
Value::String(s) => write!(fmt, "{s:?}"),
Value::Table(t) if recursive && !visited.contains(&t.to_pointer()) => {
t.fmt_pretty(fmt, ident, visited)
@@ -227,9 +510,13 @@ impl<'lua> Value<'lua> {
f @ Value::Function(_) => write!(fmt, "function: {:?}", f.to_pointer()),
t @ Value::Thread(_) => write!(fmt, "thread: {:?}", t.to_pointer()),
u @ Value::UserData(ud) => {
// Try `__name/__type` first then `__tostring`
let name = ud.type_name().ok().flatten();
let name = name.unwrap_or_else(|| "userdata".to_string());
write!(fmt, "{name}: {:?}", u.to_pointer())
let s = name
.map(|name| format!("{name}: {:?}", u.to_pointer()))
.or_else(|| u.to_string().ok())
.unwrap_or_else(|| format!("userdata: {:?}", u.to_pointer()));
write!(fmt, "{s}")
}
Value::Error(e) if recursive => write!(fmt, "{e:?}"),
Value::Error(_) => write!(fmt, "error"),
@@ -249,7 +536,7 @@ impl fmt::Debug for Value<'_> {
Value::Integer(i) => write!(fmt, "Integer({i})"),
Value::Number(n) => write!(fmt, "Number({n})"),
#[cfg(feature = "luau")]
Value::Vector(x, y, z) => write!(fmt, "Vector({x}, {y}, {z})"),
Value::Vector(v) => write!(fmt, "{v:?}"),
Value::String(s) => write!(fmt, "String({s:?})"),
Value::Table(t) => write!(fmt, "{t:?}"),
Value::Function(f) => write!(fmt, "{f:?}"),
@@ -271,7 +558,7 @@ impl<'lua> PartialEq for Value<'lua> {
(Value::Number(a), Value::Integer(b)) => *a == *b as Number,
(Value::Number(a), Value::Number(b)) => *a == *b,
#[cfg(feature = "luau")]
(Value::Vector(x1, y1, z1), Value::Vector(x2, y2, z2)) => (x1, y1, z1) == (x2, y2, z2),
(Value::Vector(v1), Value::Vector(v2)) => v1 == v2,
(Value::String(a), Value::String(b)) => a == b,
(Value::Table(a), Value::Table(b)) => a == b,
(Value::Function(a), Value::Function(b)) => a == b,
@@ -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)]
@@ -303,23 +663,46 @@ impl<'lua> Serialize for Value<'lua> {
.serialize_i64((*i).try_into().expect("cannot convert Lua Integer to i64")),
Value::Number(n) => serializer.serialize_f64(*n),
#[cfg(feature = "luau")]
Value::Vector(x, y, z) => (x, y, z).serialize(serializer),
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)]
+68 -46
View File
@@ -1,17 +1,26 @@
#![cfg(feature = "async")]
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use std::time::Duration;
use futures_timer::Delay;
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();
@@ -44,7 +53,7 @@ async fn test_async_sleep() -> Result<()> {
let lua = Lua::new();
let sleep = lua.create_async_function(move |_lua, n: u64| async move {
Delay::new(Duration::from_millis(n)).await;
sleep_ms(n).await;
Ok(format!("elapsed:{}ms", n))
})?;
lua.globals().set("sleep", sleep)?;
@@ -60,7 +69,7 @@ async fn test_async_call() -> Result<()> {
let lua = Lua::new();
let hello = lua.create_async_function(|_lua, name: String| async move {
Delay::new(Duration::from_millis(10)).await;
sleep_ms(10).await;
Ok(format!("hello, {}!", name))
})?;
@@ -80,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();
@@ -103,7 +131,7 @@ async fn test_async_handle_yield() -> Result<()> {
let lua = Lua::new();
let sum = lua.create_async_function(|_lua, (a, b): (i64, i64)| async move {
Delay::new(Duration::from_millis(10)).await;
sleep_ms(10).await;
Ok(a + b)
})?;
@@ -161,10 +189,10 @@ async fn test_async_return_async_closure() -> Result<()> {
let lua = Lua::new();
let f = lua.create_async_function(|lua, a: i64| async move {
Delay::new(Duration::from_millis(10)).await;
sleep_ms(10).await;
let g = lua.create_async_function(move |_, b: i64| async move {
Delay::new(Duration::from_millis(10)).await;
sleep_ms(10).await;
return Ok(a + b);
})?;
@@ -254,7 +282,7 @@ async fn test_async_thread() -> Result<()> {
let f = lua.create_async_function(move |_lua, ()| {
let cnt3 = cnt2.clone();
async move {
Delay::new(Duration::from_millis(*cnt3.as_ref())).await;
sleep_ms(*cnt3.as_ref()).await;
Ok("done")
}
})?;
@@ -297,40 +325,34 @@ async fn test_async_table() -> Result<()> {
table.set("val", 10)?;
let get_value = lua.create_async_function(|_, table: Table| async move {
Delay::new(Duration::from_millis(10)).await;
sleep_ms(10).await;
table.get::<_, i64>("val")
})?;
table.set("get_value", get_value)?;
let set_value = lua.create_async_function(|_, (table, n): (Table, i64)| async move {
Delay::new(Duration::from_millis(10)).await;
sleep_ms(10).await;
table.set("val", n)
})?;
table.set("set_value", set_value)?;
let sleep = lua.create_async_function(|_, n| async move {
Delay::new(Duration::from_millis(n)).await;
sleep_ms(n).await;
Ok(format!("elapsed:{}ms", n))
})?;
table.set("sleep", sleep)?;
assert_eq!(
table
.call_async_method::<_, _, i64>("get_value", ())
.await?,
table.call_async_method::<_, i64>("get_value", ()).await?,
10
);
table.call_async_method("set_value", 15).await?;
assert_eq!(
table
.call_async_method::<_, _, i64>("get_value", ())
.await?,
table.call_async_method::<_, i64>("get_value", ()).await?,
15
);
assert_eq!(
table
.call_async_function::<_, _, String>("sleep", 7)
.await?,
table.call_async_function::<_, String>("sleep", 7).await?,
"elapsed:7ms"
);
@@ -343,12 +365,12 @@ async fn test_async_thread_pool() -> Result<()> {
let lua = Lua::new_with(StdLib::ALL_SAFE, options)?;
let error_f = lua.create_async_function(|_, ()| async move {
Delay::new(Duration::from_millis(10)).await;
Err::<(), _>(Error::RuntimeError("test".to_string()))
sleep_ms(10).await;
Err::<(), _>(Error::runtime("test"))
})?;
let sleep = lua.create_async_function(|_, n| async move {
Delay::new(Duration::from_millis(n)).await;
sleep_ms(n).await;
Ok(format!("elapsed:{}ms", n))
})?;
@@ -361,31 +383,30 @@ async fn test_async_thread_pool() -> Result<()> {
#[tokio::test]
async fn test_async_userdata() -> Result<()> {
#[derive(Clone)]
struct MyUserData(Arc<AtomicU64>);
struct MyUserData(u64);
impl UserData for MyUserData {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_async_method("get_value", |_, data, ()| async move {
Delay::new(Duration::from_millis(10)).await;
Ok(data.0.load(Ordering::Relaxed))
sleep_ms(10).await;
Ok(data.0)
});
methods.add_async_method("set_value", |_, data, n| async move {
Delay::new(Duration::from_millis(10)).await;
data.0.store(n, Ordering::Relaxed);
methods.add_async_method_mut("set_value", |_, data, n| async move {
sleep_ms(10).await;
data.0 = n;
Ok(())
});
methods.add_async_function("sleep", |_, n| async move {
Delay::new(Duration::from_millis(n)).await;
sleep_ms(n).await;
Ok(format!("elapsed:{}ms", n))
});
#[cfg(not(any(feature = "lua51", feature = "luau")))]
methods.add_async_meta_method(mlua::MetaMethod::Call, |_, data, ()| async move {
let n = data.0.load(Ordering::Relaxed);
Delay::new(Duration::from_millis(n)).await;
let n = data.0;
sleep_ms(n).await;
Ok(format!("elapsed:{}ms", n))
});
@@ -393,25 +414,26 @@ async fn test_async_userdata() -> Result<()> {
methods.add_async_meta_method(
mlua::MetaMethod::Index,
|_, data, key: String| async move {
Delay::new(Duration::from_millis(10)).await;
sleep_ms(10).await;
match key.as_str() {
"ms" => Ok(Some(data.0.load(Ordering::Relaxed) as f64)),
"s" => Ok(Some((data.0.load(Ordering::Relaxed) as f64) / 1000.0)),
"ms" => Ok(Some(data.0 as f64)),
"s" => Ok(Some((data.0 as f64) / 1000.0)),
_ => Ok(None),
}
},
);
#[cfg(not(any(feature = "lua51", feature = "luau")))]
methods.add_async_meta_method(
methods.add_async_meta_method_mut(
mlua::MetaMethod::NewIndex,
|_, data, (key, value): (String, f64)| async move {
Delay::new(Duration::from_millis(10)).await;
sleep_ms(10).await;
match key.as_str() {
"ms" => Ok(data.0.store(value as u64, Ordering::Relaxed)),
"s" => Ok(data.0.store((value * 1000.0) as u64, Ordering::Relaxed)),
_ => Err(Error::external(format!("key '{}' not found", key))),
"ms" => data.0 = value as u64,
"s" => data.0 = (value * 1000.0) as u64,
_ => return Err(Error::external(format!("key '{}' not found", key))),
}
Ok(())
},
);
}
@@ -420,7 +442,7 @@ async fn test_async_userdata() -> Result<()> {
let lua = Lua::new();
let globals = lua.globals();
let userdata = lua.create_userdata(MyUserData(Arc::new(AtomicU64::new(11))))?;
let userdata = lua.create_userdata(MyUserData(11))?;
globals.set("userdata", userdata.clone())?;
lua.load(
@@ -492,7 +514,7 @@ async fn test_owned_async_call() -> Result<()> {
let hello = lua
.create_async_function(|_, name: String| async move {
Delay::new(Duration::from_millis(10)).await;
sleep_ms(10).await;
Ok(format!("hello, {}!", name))
})?
.into_owned();
@@ -513,7 +535,7 @@ async fn test_async_terminate() -> Result<()> {
let mutex = mutex2.clone();
async move {
let _guard = mutex.lock();
Delay::new(Duration::from_millis(100)).await;
sleep_ms(100).await;
Ok(())
}
})?;
+18
View File
@@ -7,6 +7,12 @@ use mlua::{Lua, Result};
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,12 +42,24 @@ fn test_chunk_macro() -> Result<()> {
let data = lua.create_table()?;
data.raw_set("num", 1)?;
let ud = mlua::AnyUserData::wrap("hello");
let f = mlua::Function::wrap(|_lua, ()| Ok(()));
lua.globals().set("g", 123)?;
let string = String::new();
let str = string.as_str();
lua.load(mlua::chunk! {
assert($name == "Rustacean")
assert(type($table) == "table")
assert($table[1] == 1)
assert(type($data) == "table")
assert($data.num == 1)
assert(type($ud) == "userdata")
assert(type($f) == "function")
assert(type($str) == "string")
assert($str == "")
assert(g == 123)
s = 321
})
+5 -1
View File
@@ -15,7 +15,11 @@ fn test_compilation() {
t.compile_fail("tests/compile/static_callback_args.rs");
#[cfg(feature = "async")]
t.compile_fail("tests/compile/async_nonstatic_userdata.rs");
{
t.compile_fail("tests/compile/async_any_userdata_method.rs");
t.compile_fail("tests/compile/async_nonstatic_userdata.rs");
t.compile_fail("tests/compile/async_userdata_method.rs");
}
#[cfg(feature = "send")]
t.compile_fail("tests/compile/non_send.rs");
@@ -0,0 +1,14 @@
use mlua::{UserDataMethods, Lua};
fn main() {
let lua = Lua::new();
lua.register_userdata_type::<String>(|reg| {
let s = String::new();
let mut s = &s;
reg.add_async_method("t", |_, this: &String, ()| async {
s = this;
Ok(())
});
}).unwrap();
}
@@ -0,0 +1,81 @@
error: lifetime may not live long enough
--> tests/compile/async_any_userdata_method.rs:9:58
|
9 | reg.add_async_method("t", |_, this: &String, ()| async {
| ___________________________________----------------------_^
| | | |
| | | return type of closure `[async block@$DIR/tests/compile/async_any_userdata_method.rs:9:58: 12:10]` contains a lifetime `'2`
| | lifetime `'1` represents this closure's body
10 | | s = this;
11 | | Ok(())
12 | | });
| |_________^ returning this value requires that `'1` must outlive `'2`
|
= note: closure implements `Fn`, so references to captured variables can't escape the closure
error[E0596]: cannot borrow `s` as mutable, as it is a captured variable in a `Fn` closure
--> tests/compile/async_any_userdata_method.rs:9:58
|
9 | reg.add_async_method("t", |_, this: &String, ()| async {
| __________________________________________________________^
10 | | s = this;
| | - mutable borrow occurs due to use of `s` in closure
11 | | Ok(())
12 | | });
| |_________^ cannot borrow as mutable
error[E0597]: `s` does not live long enough
--> tests/compile/async_any_userdata_method.rs:8:21
|
8 | let mut s = &s;
| ^^ borrowed value does not live long enough
9 | / reg.add_async_method("t", |_, this: &String, ()| async {
10 | | s = this;
11 | | Ok(())
12 | | });
| |__________- argument requires that `s` is borrowed for `'static`
13 | }).unwrap();
| - `s` dropped here while still borrowed
error[E0521]: borrowed data escapes outside of closure
--> tests/compile/async_any_userdata_method.rs:9:9
|
6 | lua.register_userdata_type::<String>(|reg| {
| ---
| |
| `reg` is a reference that is only valid in the closure body
| has type `&mut LuaUserDataRegistry<'1, std::string::String>`
...
9 | / reg.add_async_method("t", |_, this: &String, ()| async {
10 | | s = this;
11 | | Ok(())
12 | | });
| | ^
| | |
| |__________`reg` escapes the closure body here
| argument requires that `'1` must outlive `'static`
|
= note: requirement occurs because of a mutable reference to `LuaUserDataRegistry<'_, std::string::String>`
= note: mutable references are invariant over their type parameter
= help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
error[E0373]: closure may outlive the current function, but it borrows `s`, which is owned by the current function
--> tests/compile/async_any_userdata_method.rs:9:35
|
9 | reg.add_async_method("t", |_, this: &String, ()| async {
| ^^^^^^^^^^^^^^^^^^^^^^ may outlive borrowed value `s`
10 | s = this;
| - `s` is borrowed here
|
note: function requires argument type to outlive `'static`
--> tests/compile/async_any_userdata_method.rs:9:9
|
9 | / reg.add_async_method("t", |_, this: &String, ()| async {
10 | | s = this;
11 | | Ok(())
12 | | });
| |__________^
help: to force the closure to take ownership of `s` (and any other referenced variables), use the `move` keyword
|
9 | reg.add_async_method("t", move |_, this: &String, ()| async {
| ++++
@@ -4,11 +4,8 @@ error: lifetime may not live long enough
7 | impl<'a> UserData for MyUserData<'a> {
| -- lifetime `'a` defined here
8 | fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
| ---- lifetime `'lua` defined here
9 | / methods.add_async_method("print", |_, data, ()| async move {
10 | | println!("{}", data.0);
11 | | Ok(())
12 | | });
| |______________^ argument requires that `'a` must outlive `'lua`
|
= help: consider adding the following bound: `'a: 'lua`
| |______________^ requires that `'a` must outlive `'static`
+14
View File
@@ -0,0 +1,14 @@
use mlua::{UserData, UserDataMethods};
struct MyUserData;
impl UserData for MyUserData {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_async_method("method", |_, this: &'static Self, ()| async {
Ok(())
});
// ^ lifetime may not live long enough
}
}
fn main() {}
@@ -0,0 +1,17 @@
warning: unused variable: `this`
--> tests/compile/async_userdata_method.rs:7:48
|
7 | methods.add_async_method("method", |_, this: &'static Self, ()| async {
| ^^^^ help: if this is intentional, prefix it with an underscore: `_this`
|
= note: `#[warn(unused_variables)]` on by default
error: lifetime may not live long enough
--> tests/compile/async_userdata_method.rs:7:9
|
6 | fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
| ---- lifetime `'lua` defined here
7 | / methods.add_async_method("method", |_, this: &'static Self, ()| async {
8 | | Ok(())
9 | | });
| |__________^ argument requires that `'lua` must outlive `'static`
+2 -2
View File
@@ -21,5 +21,5 @@ note: required because it's used within this closure
note: required by a bound in `Lua::create_function`
--> src/lua.rs
|
| F: 'static + MaybeSend + Fn(&'lua Lua, A) -> Result<R>,
| ^^^^^^^^^ required by this bound in `Lua::create_function`
| F: Fn(&'lua Lua, A) -> Result<R> + MaybeSend + 'static,
| ^^^^^^^^^ required by this bound in `Lua::create_function`
+4 -2
View File
@@ -7,7 +7,7 @@ fn test_error_context() -> Result<()> {
let lua = Lua::new();
let func = lua.create_function(|_, ()| {
Err::<(), _>(Error::RuntimeError("runtime error".into())).context("some context")
Err::<(), _>(Error::runtime("runtime error")).context("some context")
})?;
lua.globals().set("func", func)?;
@@ -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());
+18 -18
View File
@@ -86,7 +86,7 @@ fn test_rust_function() -> Result<()> {
fn test_c_function() -> Result<()> {
let lua = Lua::new();
unsafe extern "C" fn c_function(state: *mut mlua::lua_State) -> std::os::raw::c_int {
unsafe extern "C-unwind" fn c_function(state: *mut mlua::lua_State) -> std::os::raw::c_int {
let lua = Lua::init_from_ptr(state);
lua.globals().set("c_function", true).unwrap();
0
@@ -196,37 +196,37 @@ fn test_function_info() -> Result<()> {
let function1_info = function1.info();
#[cfg(feature = "luau")]
assert_eq!(function1_info.name.as_deref(), Some("function1"));
assert_eq!(function1_info.source.as_deref(), Some(b"source1".as_ref()));
assert_eq!(function1_info.line_defined, 2);
assert_eq!(function1_info.source.as_deref(), Some("source1"));
assert_eq!(function1_info.line_defined, Some(2));
#[cfg(not(feature = "luau"))]
assert_eq!(function1_info.last_line_defined, 4);
assert_eq!(function1_info.last_line_defined, Some(4));
#[cfg(feature = "luau")]
assert_eq!(function1_info.last_line_defined, -1);
assert_eq!(function1_info.what.as_deref(), Some("Lua"));
assert_eq!(function1_info.last_line_defined, None);
assert_eq!(function1_info.what, "Lua");
let function2_info = function2.info();
assert_eq!(function2_info.name, None);
assert_eq!(function2_info.source.as_deref(), Some(b"source1".as_ref()));
assert_eq!(function2_info.line_defined, 3);
assert_eq!(function2_info.source.as_deref(), Some("source1"));
assert_eq!(function2_info.line_defined, Some(3));
#[cfg(not(feature = "luau"))]
assert_eq!(function2_info.last_line_defined, 3);
assert_eq!(function2_info.last_line_defined, Some(3));
#[cfg(feature = "luau")]
assert_eq!(function2_info.last_line_defined, -1);
assert_eq!(function2_info.what.as_deref(), Some("Lua"));
assert_eq!(function2_info.last_line_defined, None);
assert_eq!(function2_info.what, "Lua");
let function3_info = function3.info();
assert_eq!(function3_info.name, None);
assert_eq!(function3_info.source.as_deref(), Some(b"=[C]".as_ref()));
assert_eq!(function3_info.line_defined, -1);
assert_eq!(function3_info.last_line_defined, -1);
assert_eq!(function3_info.what.as_deref(), Some("C"));
assert_eq!(function3_info.source.as_deref(), Some("=[C]"));
assert_eq!(function3_info.line_defined, None);
assert_eq!(function3_info.last_line_defined, None);
assert_eq!(function3_info.what, "C");
let print_info = globals.get::<_, Function>("print")?.info();
#[cfg(feature = "luau")]
assert_eq!(print_info.name.as_deref(), Some("print"));
assert_eq!(print_info.source.as_deref(), Some(b"=[C]".as_ref()));
assert_eq!(print_info.what.as_deref(), Some("C"));
assert_eq!(print_info.line_defined, -1);
assert_eq!(print_info.source.as_deref(), Some("=[C]"));
assert_eq!(print_info.what, "C");
assert_eq!(print_info.line_defined, None);
Ok(())
}
+20 -27
View File
@@ -1,8 +1,6 @@
#![cfg(not(feature = "luau"))]
use std::cell::RefCell;
use std::ops::Deref;
use std::str;
use std::sync::atomic::{AtomicI64, Ordering};
use std::sync::{Arc, Mutex};
@@ -29,7 +27,7 @@ fn test_line_counts() -> Result<()> {
assert_eq!(debug.event(), DebugEvent::Line);
hook_output.lock().unwrap().push(debug.curr_line());
Ok(())
})?;
});
lua.load(
r#"
local x = 2 + 3
@@ -61,11 +59,10 @@ fn test_function_calls() -> Result<()> {
assert_eq!(debug.event(), DebugEvent::Call);
let names = debug.names();
let source = debug.source();
let name = names.name.map(|s| str::from_utf8(s).unwrap().to_owned());
let what = source.what.map(|s| str::from_utf8(s).unwrap().to_owned());
hook_output.lock().unwrap().push((name, what));
let name = names.name.map(|s| s.into_owned());
hook_output.lock().unwrap().push((name, source.what));
Ok(())
})?;
});
lua.load(
r#"
@@ -80,18 +77,12 @@ fn test_function_calls() -> Result<()> {
if cfg!(feature = "luajit") && lua.load("jit.version_num").eval::<i64>()? >= 20100 {
assert_eq!(
*output,
vec![
(None, Some("main".to_string())),
(Some("len".to_string()), Some("Lua".to_string()))
]
vec![(None, "main"), (Some("len".to_string()), "Lua")]
);
} else {
assert_eq!(
*output,
vec![
(None, Some("main".to_string())),
(Some("len".to_string()), Some("C".to_string()))
]
vec![(None, "main"), (Some("len".to_string()), "C")]
);
}
@@ -103,10 +94,8 @@ fn test_error_within_hook() -> Result<()> {
let lua = Lua::new();
lua.set_hook(HookTriggers::EVERY_LINE, |_lua, _debug| {
Err(Error::RuntimeError(
"Something happened in there!".to_string(),
))
})?;
Err(Error::runtime("Something happened in there!"))
});
let err = lua
.load("x = 1")
@@ -138,12 +127,12 @@ fn test_limit_execution_instructions() -> Result<()> {
move |_lua, debug| {
assert_eq!(debug.event(), DebugEvent::Count);
if max_instructions.fetch_sub(30, Ordering::Relaxed) <= 30 {
Err(Error::RuntimeError("time's up".to_string()))
Err(Error::runtime("time's up"))
} else {
Ok(())
}
},
)?;
);
lua.globals().set("x", Value::Integer(0))?;
let _ = lua
@@ -167,11 +156,11 @@ fn test_hook_removal() -> Result<()> {
lua.set_hook(
HookTriggers::new().every_nth_instruction(1),
|_lua, _debug| {
Err(Error::RuntimeError(
"this hook should've been removed by this time".to_string(),
Err(Error::runtime(
"this hook should've been removed by this time",
))
},
)?;
);
assert!(lua.load("local x = 1").exec().is_err());
lua.remove_hook();
@@ -180,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| {
@@ -215,9 +207,10 @@ fn test_hook_swap_within_hook() -> Result<()> {
Ok(())
},
)
})
});
Ok(())
})
})?;
});
TL_LUA.with(|tl| {
let tl = tl.borrow();
+160 -10
View File
@@ -1,13 +1,15 @@
#![cfg(feature = "luau")]
use std::env;
use std::fmt::Debug;
use std::fs;
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::Arc;
use mlua::{Compiler, CoverageInfo, Error, Lua, Result, Table, ThreadStatus, Value, VmState};
use mlua::{
Compiler, CoverageInfo, Error, Lua, LuaOptions, Result, StdLib, Table, ThreadStatus, Value,
Vector, VmState,
};
#[test]
fn test_version() -> Result<()> {
@@ -21,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(
@@ -35,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")
@@ -47,16 +63,42 @@ 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"))]
#[test]
fn test_vectors() -> Result<()> {
let lua = Lua::new();
let v: [f32; 3] = lua.load("vector(1, 2, 3) + vector(3, 2, 1)").eval()?;
let v: Vector = lua.load("vector(1, 2, 3) + vector(3, 2, 1)").eval()?;
assert_eq!(v, [4.0, 4.0, 4.0]);
// Test conversion into Rust array
let v: [f64; 3] = lua.load("vector(1, 2, 3)").eval()?;
assert!(v == [1.0, 2.0, 3.0]);
// Test vector methods
lua.load(
r#"
@@ -77,7 +119,89 @@ 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(())
}
#[cfg(feature = "luau-vector4")]
#[test]
fn test_vectors() -> Result<()> {
let lua = Lua::new();
let v: Vector = lua.load("vector(1, 2, 3, 4) + vector(4, 3, 2, 1)").eval()?;
assert_eq!(v, [5.0, 5.0, 5.0, 5.0]);
// Test conversion into Rust array
let v: [f64; 4] = lua.load("vector(1, 2, 3, 4)").eval()?;
assert!(v == [1.0, 2.0, 3.0, 4.0]);
// Test vector methods
lua.load(
r#"
local v = vector(1, 2, 3, 4)
assert(v.x == 1)
assert(v.y == 2)
assert(v.z == 3)
assert(v.w == 4)
"#,
)
.exec()?;
// Test vector methods (fastcall)
lua.load(
r#"
local v = vector(1, 2, 3, 4)
assert(v.x == 1)
assert(v.y == 2)
assert(v.z == 3)
assert(v.w == 4)
"#,
)
.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(())
@@ -232,7 +356,7 @@ fn test_interrupts() -> Result<()> {
//
// Test errors in interrupts
//
lua.set_interrupt(|_| Err(Error::RuntimeError("error from interrupt".into())));
lua.set_interrupt(|_| Err(Error::runtime("error from interrupt")));
match f.call::<_, ()>(()) {
Err(Error::CallbackError { cause, .. }) => match *cause {
Error::RuntimeError(ref m) if m == "error from interrupt" => {}
@@ -254,8 +378,8 @@ fn test_coverage() -> Result<()> {
let f = lua
.load(
r#"local v = vector(1, 2, 3)
assert(v.x == 1 and v.y == 2 and v.z == 3)
r#"local s = "abc"
assert(#s == 3)
function abc(i)
if i < 5 then
@@ -318,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(())
}
+4 -8
View File
@@ -17,12 +17,8 @@ fn test_memory_limit() -> Result<()> {
.into_function()?;
f.call::<_, ()>(()).expect("should trigger no memory limit");
if cfg!(feature = "luajit") && cfg!(not(feature = "vendored")) {
// we don't support setting memory limit for non-vendored luajit
assert!(matches!(
lua.set_memory_limit(0),
Err(Error::MemoryLimitNotAvailable)
));
if cfg!(feature = "luajit") && lua.set_memory_limit(0).is_err() {
// seems this luajit version does not support memory limit
return Ok(());
}
@@ -46,8 +42,8 @@ fn test_memory_limit_thread() -> Result<()> {
.load("local t = {}; for i = 1,10000 do t[i] = i end")
.into_function()?;
if cfg!(feature = "luajit") && cfg!(not(feature = "vendored")) {
// we don't support setting memory limit for non-vendored luajit
if cfg!(feature = "luajit") && lua.set_memory_limit(0).is_err() {
// seems this luajit version does not support memory limit
return Ok(());
}
-11
View File
@@ -1,11 +0,0 @@
[target.x86_64-apple-darwin]
rustflags = [
"-C", "link-arg=-undefined",
"-C", "link-arg=dynamic_lookup",
]
[target.aarch64-apple-darwin]
rustflags = [
"-C", "link-arg=-undefined",
"-C", "link-arg=dynamic_lookup",
]
+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"] }
+7
View File
@@ -0,0 +1,7 @@
fn main() {
#[cfg(target_os = "macos")]
{
println!("cargo:rustc-cdylib-link-arg=-undefined");
println!("cargo:rustc-cdylib-link-arg=dynamic_lookup");
}
}
+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#"
+1 -1
View File
@@ -26,7 +26,7 @@ struct MyUserData(i32);
impl LuaUserData for MyUserData {}
#[mlua::lua_module(name = "test_module_second")]
#[mlua::lua_module(name = "test_module_second", skip_memory_check)]
fn test_module2(lua: &Lua) -> LuaResult<LuaTable> {
let exports = lua.create_table()?;
exports.set("userdata", MyUserData(123))?;
+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();
+113 -4
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};
@@ -145,7 +145,7 @@ fn test_serialize_failure() -> Result<(), Box<dyn StdError>> {
Ok(())
}
#[cfg(feature = "luau")]
#[cfg(all(feature = "luau", not(feature = "luau-vector4")))]
#[test]
fn test_serialize_vector() -> Result<(), Box<dyn StdError>> {
let lua = Lua::new();
@@ -153,7 +153,7 @@ fn test_serialize_vector() -> Result<(), Box<dyn StdError>> {
let globals = lua.globals();
globals.set(
"vector",
lua.create_function(|_, (x, y, z)| Ok(Value::Vector(x, y, z)))?,
lua.create_function(|_, (x, y, z)| Ok(mlua::Vector::new(x, y, z)))?,
)?;
let val = lua.load("{_vector = vector(1, 2, 3)}").eval::<Value>()?;
@@ -168,6 +168,92 @@ fn test_serialize_vector() -> Result<(), Box<dyn StdError>> {
Ok(())
}
#[cfg(feature = "luau-vector4")]
#[test]
fn test_serialize_vector() -> Result<(), Box<dyn StdError>> {
let lua = Lua::new();
let globals = lua.globals();
globals.set(
"vector",
lua.create_function(|_, (x, y, z, w)| Ok(mlua::Vector::new(x, y, z, w)))?,
)?;
let val = lua.load("{_vector = vector(1, 2, 3, 4)}").eval::<Value>()?;
let json = serde_json::json!({
"_vector": [1.0, 2.0, 3.0, 4.0],
});
assert_eq!(serde_json::to_value(&val)?, json);
let expected_json = lua.from_value::<serde_json::Value>(val)?;
assert_eq!(expected_json, json);
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();
@@ -588,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(())
+88 -38
View File
@@ -1,7 +1,7 @@
use mlua::{Error, Lua, Nil, Result, Table, TableExt, Value};
#[test]
fn test_set_get() -> Result<()> {
fn test_globals_set_get() -> Result<()> {
let lua = Lua::new();
let globals = lua.globals();
@@ -43,6 +43,7 @@ fn test_table() -> Result<()> {
let table3 = globals.get::<_, Table>("table3")?;
assert_eq!(table1.len()?, 5);
assert!(!table1.is_empty());
assert_eq!(
table1
.clone()
@@ -57,8 +58,10 @@ fn test_table() -> Result<()> {
.collect::<Result<Vec<i64>>>()?,
vec![1, 2, 3, 4, 5]
);
assert_eq!(table1, [1, 2, 3, 4, 5]);
assert_eq!(table2.len()?, 0);
assert!(table2.is_empty());
assert_eq!(
table2
.clone()
@@ -66,12 +69,10 @@ fn test_table() -> Result<()> {
.collect::<Result<Vec<(i64, i64)>>>()?,
vec![]
);
assert_eq!(
table2.sequence_values().collect::<Result<Vec<i64>>>()?,
vec![]
);
assert_eq!(table2, [0; 0]);
// sequence_values should only iterate until the first border
assert_eq!(table3, [1, 2]);
assert_eq!(
table3.sequence_values().collect::<Result<Vec<i64>>>()?,
vec![1, 2]
@@ -116,17 +117,12 @@ fn test_table_push_pop() -> Result<()> {
// Test raw access
let table1 = lua.create_sequence_from(vec![123])?;
table1.raw_push(321)?;
assert_eq!(
table1
.clone()
.raw_sequence_values::<i64>()
.collect::<Result<Vec<_>>>()?,
vec![123, 321]
);
assert_eq!(table1, [123, 321]);
assert_eq!(table1.raw_pop::<i64>()?, 321);
assert_eq!(table1.raw_pop::<i64>()?, 123);
assert_eq!(table1.raw_pop::<Value>()?, Value::Nil); // An extra pop should do nothing
assert_eq!(table1.raw_len(), 0);
assert_eq!(table1, [0; 0]);
// Test access through metamethods
let table2 = lua
@@ -144,6 +140,13 @@ fn test_table_push_pop() -> Result<()> {
.eval::<Table>()?;
table2.push(345)?;
assert_eq!(table2.len()?, 2);
assert_eq!(
table2
.clone()
.sequence_values::<i64>()
.collect::<Result<Vec<_>>>()?,
vec![]
);
assert_eq!(table2.pop::<i64>()?, 345);
assert_eq!(table2.pop::<i64>()?, 234);
assert_eq!(table2.pop::<Value>()?, Value::Nil);
@@ -191,8 +194,10 @@ fn test_table_clear() -> Result<()> {
)
.eval::<Table>()?;
assert_eq!(t2.raw_len(), 3);
assert!(!t2.is_empty());
t2.clear()?;
assert_eq!(t2.raw_len(), 0);
assert!(t2.is_empty());
assert_eq!(t2.raw_get::<_, Value>("a")?, Value::Nil);
assert_ne!(t2.get_metatable(), None);
@@ -205,29 +210,77 @@ fn test_table_sequence_from() -> Result<()> {
let get_table = lua.create_function(|_, t: Table| Ok(t))?;
assert_eq!(
get_table
.call::<_, Table>(vec![1, 2, 3])?
.sequence_values()
.collect::<Result<Vec<i64>>>()?,
vec![1, 2, 3]
);
assert_eq!(get_table.call::<_, Table>(vec![1, 2, 3])?, [1, 2, 3]);
assert_eq!(get_table.call::<_, Table>([4, 5, 6])?, [4, 5, 6]);
assert_eq!(get_table.call::<_, Table>([7, 8, 9].as_slice())?, [7, 8, 9]);
assert_eq!(
get_table
.call::<_, Table>([1, 2, 3].as_ref())?
.sequence_values()
.collect::<Result<Vec<i64>>>()?,
vec![1, 2, 3]
);
Ok(())
}
assert_eq!(
get_table
.call::<_, Table>([1, 2, 3])?
.sequence_values()
.collect::<Result<Vec<i64>>>()?,
vec![1, 2, 3]
);
#[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(())
}
@@ -377,11 +430,8 @@ fn test_table_call() -> Result<()> {
let table: Table = lua.globals().get("table")?;
assert_eq!(table.call::<_, String>("b")?, "call_2");
assert_eq!(table.call_function::<_, _, String>("func", "a")?, "func_a");
assert_eq!(
table.call_method::<_, _, String>("method", "a")?,
"method_1"
);
assert_eq!(table.call_function::<_, String>("func", "a")?, "func_a");
assert_eq!(table.call_method::<_, String>("method", "a")?, "method_1");
// Test calling non-callable table
let table2 = lua.create_table()?;
+57 -32
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;
@@ -69,7 +70,7 @@ fn test_safety() -> Result<()> {
fn test_load() -> Result<()> {
let lua = Lua::new();
let func = lua.load("return 1+2").into_function()?;
let func = lua.load("\treturn 1+2").into_function()?;
let result: i32 = func.call(())?;
assert_eq!(result, 3);
@@ -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();
@@ -1226,8 +1244,8 @@ fn test_inspect_stack() -> Result<()> {
let logline = lua.create_function(|lua, msg: StdString| {
let debug = lua.inspect_stack(1).unwrap(); // caller
let source = debug.source().short_src.map(core::str::from_utf8);
let source = source.transpose().unwrap().unwrap_or("?");
let source = debug.source().short_src;
let source = source.as_deref().unwrap_or("?");
let line = debug.curr_line();
Ok(format!("{}:{} {}", source, line, msg))
})?;
@@ -1278,20 +1296,20 @@ fn test_warnings() -> Result<()> {
let lua = Lua::new();
lua.set_app_data::<Vec<(StdString, bool)>>(Vec::new());
lua.set_warning_function(|lua, msg, tocont| {
let msg = msg.to_string_lossy().to_string();
lua.set_warning_function(|lua, msg, incomplete| {
lua.app_data_mut::<Vec<(StdString, bool)>>()
.unwrap()
.push((msg, tocont));
.push((msg.to_string(), incomplete));
Ok(())
});
lua.warning("native warning ...", true)?;
lua.warning("finish", false)?;
lua.warning("native warning ...", true);
lua.warning("finish", false);
lua.warning("\0", false);
lua.load(r#"warn("lua warning", "continue")"#).exec()?;
lua.remove_warning_function();
lua.warning("one more warning", false)?;
lua.warning("one more warning", false);
let messages = lua.app_data_ref::<Vec<(StdString, bool)>>().unwrap();
assert_eq!(
@@ -1299,13 +1317,14 @@ fn test_warnings() -> Result<()> {
vec![
("native warning ...".to_string(), true),
("finish".to_string(), false),
("".to_string(), false),
("lua warning".to_string(), true),
("continue".to_string(), false),
]
);
// Trigger error inside warning
lua.set_warning_function(|_, _, _| Err(Error::RuntimeError("warning error".to_string())));
lua.set_warning_function(|_, _, _| Err(Error::runtime("warning error")));
assert!(matches!(
lua.load(r#"warn("test")"#).exec(),
Err(Error::CallbackError { cause, .. })
@@ -1317,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")
@@ -1333,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 || {
+32 -5
View File
@@ -94,11 +94,7 @@ fn test_thread() -> Result<()> {
}
#[test]
#[cfg(any(
feature = "lua54",
all(feature = "luajit", feature = "vendored"),
feature = "luau",
))]
#[cfg(any(feature = "lua54", feature = "luau"))]
fn test_thread_reset() -> Result<()> {
use mlua::{AnyUserData, UserData};
use std::sync::Arc;
@@ -194,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(())
}
+186 -25
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)]
@@ -411,21 +435,21 @@ fn test_user_values() -> Result<()> {
ud.set_nth_user_value(1, "hello")?;
ud.set_nth_user_value(2, "world")?;
ud.set_nth_user_value(65535, 321)?;
assert_eq!(ud.get_nth_user_value::<String>(1)?, "hello");
assert_eq!(ud.get_nth_user_value::<String>(2)?, "world");
assert_eq!(ud.get_nth_user_value::<Value>(3)?, Value::Nil);
assert_eq!(ud.get_nth_user_value::<i32>(65535)?, 321);
assert_eq!(ud.nth_user_value::<String>(1)?, "hello");
assert_eq!(ud.nth_user_value::<String>(2)?, "world");
assert_eq!(ud.nth_user_value::<Value>(3)?, Value::Nil);
assert_eq!(ud.nth_user_value::<i32>(65535)?, 321);
assert!(ud.get_nth_user_value::<Value>(0).is_err());
assert!(ud.get_nth_user_value::<Value>(65536).is_err());
assert!(ud.nth_user_value::<Value>(0).is_err());
assert!(ud.nth_user_value::<Value>(65536).is_err());
// Named user values
ud.set_named_user_value("name", "alex")?;
ud.set_named_user_value("age", 10)?;
assert_eq!(ud.get_named_user_value::<String>("name")?, "alex");
assert_eq!(ud.get_named_user_value::<i32>("age")?, 10);
assert_eq!(ud.get_named_user_value::<Value>("nonexist")?, Value::Nil);
assert_eq!(ud.named_user_value::<String>("name")?, "alex");
assert_eq!(ud.named_user_value::<i32>("age")?, 10);
assert_eq!(ud.named_user_value::<Value>("nonexist")?, Value::Nil);
Ok(())
}
@@ -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);
@@ -495,7 +522,7 @@ fn test_fields() -> Result<()> {
});
// Use userdata "uservalue" storage
fields.add_field_function_get("uval", |_, ud| ud.get_user_value::<Option<String>>());
fields.add_field_function_get("uval", |_, ud| ud.user_value::<Option<String>>());
fields
.add_field_function_set("uval", |_, ud, s| ud.set_user_value::<Option<String>>(s));
@@ -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(())
}
@@ -543,7 +595,7 @@ fn test_metatable() -> Result<()> {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_function("my_type_name", |_, data: AnyUserData| {
let metatable = data.get_metatable()?;
metatable.get::<String>("__name")
metatable.get::<String>(MetaMethod::Type)
});
}
}
@@ -554,6 +606,12 @@ fn test_metatable() -> Result<()> {
lua.load(r#"assert(ud:my_type_name() == "MyUserData")"#)
.exec()?;
#[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))]
lua.load(r#"assert(tostring(ud):sub(1, 11) == "MyUserData:")"#)
.exec()?;
#[cfg(feature = "luau")]
lua.load(r#"assert(typeof(ud) == "MyUserData")"#).exec()?;
let ud: AnyUserData = globals.get("ud")?;
let metatable = ud.get_metatable()?;
@@ -574,7 +632,7 @@ fn test_metatable() -> Result<()> {
.map(|kv: Result<(_, Value)>| Ok(kv?.0))
.collect::<Result<Vec<_>>>()?;
methods.sort();
assert_eq!(methods, vec!["__index", "__name"]);
assert_eq!(methods, vec!["__index", MetaMethod::Type.name()]);
#[derive(Copy, Clone)]
struct MyUserData2;
@@ -596,13 +654,16 @@ fn test_metatable() -> Result<()> {
impl UserData for MyUserData3 {
fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_meta_field_with("__name", |_| Ok("CustomName"));
fields.add_meta_field_with(MetaMethod::Type, |_| Ok("CustomName"));
}
}
let ud = lua.create_userdata(MyUserData3)?;
let metatable = ud.get_metatable()?;
assert_eq!(metatable.get::<String>("__name")?.to_str()?, "CustomName");
assert_eq!(
metatable.get::<String>(MetaMethod::Type)?.to_str()?,
"CustomName"
);
Ok(())
}
@@ -625,10 +686,33 @@ fn test_userdata_wrapped() -> Result<()> {
let lua = Lua::new();
let globals = lua.globals();
// Rc<T>
#[cfg(not(feature = "send"))]
{
let ud1 = Rc::new(RefCell::new(MyUserData(1)));
globals.set("rc_refcell_ud", ud1.clone())?;
let ud = Rc::new(MyUserData(1));
globals.set("rc_ud", ud.clone())?;
lua.load(
r#"
assert(rc_ud.static == "constant")
local ok, err = pcall(function() rc_ud.data = 2 end)
assert(
tostring(err):sub(1, 32) == "error mutably borrowing userdata",
"expected error mutably borrowing userdata, got " .. tostring(err)
)
assert(rc_ud.data == 1)
"#,
)
.exec()?;
globals.set("rc_ud", Nil)?;
lua.gc_collect()?;
assert_eq!(Rc::strong_count(&ud), 1);
}
// Rc<RefCell<T>>
#[cfg(not(feature = "send"))]
{
let ud = Rc::new(RefCell::new(MyUserData(1)));
globals.set("rc_refcell_ud", ud.clone())?;
lua.load(
r#"
assert(rc_refcell_ud.static == "constant")
@@ -637,12 +721,32 @@ fn test_userdata_wrapped() -> Result<()> {
"#,
)
.exec()?;
assert_eq!(ud1.borrow().0, 2);
assert_eq!(ud.borrow().0, 2);
globals.set("rc_refcell_ud", Nil)?;
lua.gc_collect()?;
assert_eq!(Rc::strong_count(&ud1), 1);
assert_eq!(Rc::strong_count(&ud), 1);
}
// Arc<T>
let ud1 = Arc::new(MyUserData(2));
globals.set("arc_ud", ud1.clone())?;
lua.load(
r#"
assert(arc_ud.static == "constant")
local ok, err = pcall(function() arc_ud.data = 3 end)
assert(
tostring(err):sub(1, 32) == "error mutably borrowing userdata",
"expected error mutably borrowing userdata, got " .. tostring(err)
)
assert(arc_ud.data == 2)
"#,
)
.exec()?;
globals.set("arc_ud", Nil)?;
lua.gc_collect()?;
assert_eq!(Arc::strong_count(&ud1), 1);
// Arc<Mutex<T>>
let ud2 = Arc::new(Mutex::new(MyUserData(2)));
globals.set("arc_mutex_ud", ud2.clone())?;
lua.load(
@@ -657,7 +761,11 @@ fn test_userdata_wrapped() -> Result<()> {
assert_eq!(ud2.lock().unwrap().0, 3);
#[cfg(feature = "parking_lot")]
assert_eq!(ud2.lock().0, 3);
globals.set("arc_mutex_ud", Nil)?;
lua.gc_collect()?;
assert_eq!(Arc::strong_count(&ud2), 1);
// Arc<RwLock<T>>
let ud3 = Arc::new(RwLock::new(MyUserData(3)));
globals.set("arc_rwlock_ud", ud3.clone())?;
lua.load(
@@ -672,12 +780,8 @@ fn test_userdata_wrapped() -> Result<()> {
assert_eq!(ud3.read().unwrap().0, 4);
#[cfg(feature = "parking_lot")]
assert_eq!(ud3.read().0, 4);
// Test drop
globals.set("arc_mutex_ud", Nil)?;
globals.set("arc_rwlock_ud", Nil)?;
lua.gc_collect()?;
assert_eq!(Arc::strong_count(&ud2), 1);
assert_eq!(Arc::strong_count(&ud3), 1);
Ok(())
@@ -753,6 +857,27 @@ fn test_any_userdata() -> Result<()> {
Ok(())
}
#[test]
fn test_any_userdata_wrap() -> Result<()> {
let lua = Lua::new();
lua.register_userdata_type::<StdString>(|reg| {
reg.add_method("get", |_, this, ()| Ok(this.clone()));
})?;
lua.globals()
.set("s", AnyUserData::wrap("hello".to_string()))?;
lua.load(
r#"
assert(s:get() == "hello")
"#,
)
.exec()
.unwrap();
Ok(())
}
#[test]
fn test_userdata_ext() -> Result<()> {
let lua = Lua::new();
@@ -844,3 +969,39 @@ fn test_owned_userdata() -> Result<()> {
Ok(())
}
#[cfg(feature = "macros")]
#[test]
fn test_userdata_derive() -> Result<()> {
let lua = Lua::new();
// Simple struct
#[derive(Clone, Copy, mlua::FromLua)]
struct MyUserData(i32);
lua.register_userdata_type::<MyUserData>(|reg| {
reg.add_function("val", |_, this: MyUserData| Ok(this.0));
})?;
lua.globals()
.set("ud", AnyUserData::wrap(MyUserData(123)))?;
lua.load("assert(ud:val() == 123)").exec()?;
// More complex struct where generics and where clause
#[derive(Clone, Copy, mlua::FromLua)]
struct MyUserData2<'a, T>(&'a T)
where
T: ?Sized;
lua.register_userdata_type::<MyUserData2<'static, i32>>(|reg| {
reg.add_function("val", |_, this: MyUserData2<'static, i32>| Ok(*this.0));
})?;
lua.globals()
.set("ud", AnyUserData::wrap(MyUserData2(&321)))?;
lua.load("assert(ud:val() == 321)").exec()?;
Ok(())
}
+79 -3
View File
@@ -101,11 +101,16 @@ fn test_value_to_string() -> Result<()> {
);
assert_eq!(Value::Integer(1).to_string()?, "1");
assert_eq!(Value::Number(34.59).to_string()?, "34.59");
#[cfg(feature = "luau")]
#[cfg(all(feature = "luau", not(feature = "luau-vector4")))]
assert_eq!(
Value::Vector(10.0, 11.1, 12.2).to_string()?,
Value::Vector(mlua::Vector::new(10.0, 11.1, 12.2)).to_string()?,
"vector(10, 11.1, 12.2)"
);
#[cfg(feature = "luau-vector4")]
assert_eq!(
Value::Vector(mlua::Vector::new(10.0, 11.1, 12.2, 13.3)).to_string()?,
"vector(10, 11.1, 12.2, 13.3)"
);
assert_eq!(
Value::String(lua.create_string("hello")?).to_string()?,
"hello"
@@ -135,7 +140,7 @@ fn test_value_to_string() -> Result<()> {
let ud: Value = Value::UserData(lua.create_userdata(MyUserData)?);
assert!(ud.to_string()?.starts_with("MyUserData:"));
let err = Value::Error(Error::RuntimeError("test error".to_string()));
let err = Value::Error(Error::runtime("test error"));
assert_eq!(err.to_string()?, "runtime error: test error");
Ok(())
@@ -153,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(())
}