Compare commits

...

381 Commits

Author SHA1 Message Date
Alex Orlenko 7d3704c222 mlua-sys: v0.6.2 2024-07-25 22:49:56 +01:00
Alex Orlenko 496c8fa0e9 Fix references to master branch 2024-07-25 22:49:32 +01:00
Alex Orlenko 9ce0e11518 Use Lua 5.4.7 2024-07-25 22:45:40 +01:00
Caleb Maclennan b77836920a Link to other mlua projects published to LuaRocks (#425)
* Add a link to decasify, another mlua success story published on LuaRocks

* Add a few more downstream project links that seem reasonably maintained
2024-07-04 23:31:05 +01:00
Alex Orlenko f1ceaf0ff1 v0.9.9 2024-06-18 16:09:08 +01:00
Alex Orlenko 884a025b52 Fix some clippy warnings 2024-06-18 16:09:08 +01:00
Alex Orlenko c23fa5aa6c Optimize RegistryKey internals
- Store single `AtomicI32` field instead of pair i32,AtomicBool
- Make creation faster by skipping intermediate `Value` layer
Add new `RegistryKey::id()` method to return underlying identifier
2024-06-18 16:09:06 +01:00
Alex Orlenko 4f1d2abbcb Bump rustc-hash to 2.0 2024-06-18 11:18:55 +01:00
Alex Orlenko a25e81036e Do not allow already running coroutines to be reset or resumed.
This is a wrong use of the Lua API and is not supported.
See #416 for the reference.
2024-06-12 23:18:46 +01:00
Alex Orlenko b46cad1db1 Support Luau v0.629 2024-06-09 00:37:58 +01:00
Alex Orlenko f2d48ce296 v0.9.8 2024-05-15 23:17:30 +01:00
Alex Orlenko 0fa39d431d mlua_derive: v0.9.3 2024-05-15 23:14:23 +01:00
Alex Orlenko 0aa86c4d47 Update CHANGELOG 2024-05-15 23:11:50 +01:00
Alex Orlenko ea2faa3755 clippy 2024-05-15 23:11:33 +01:00
Alex Orlenko 59c9abbac7 Fix serializing same table multiple times.
Fixes #408
2024-05-14 00:42:04 +01:00
Alex Orlenko 8f3de8aa19 mlua-sys: v0.6.0 2024-05-04 21:22:17 +01:00
Alex Orlenko 317ce7caa6 Add Lua::set_fflag() to control Luau feature flags 2024-05-04 21:15:22 +01:00
Alex Orlenko 3a44729a48 Mark lua_Callbacks as non exhaustive (Luau) 2024-05-04 21:11:29 +01:00
Alex Orlenko 3d46fad459 Update luau-src to v0.9
Mark `lua_CompileOptions` as non exhaustive
2024-05-04 21:10:24 +01:00
Joris Willems ffc4bd599c Fix module imports for export (#394) 2024-04-18 10:22:41 +01:00
Eric Stokes 1c969da286 update build script to fix cross compilation of windows dlls from unix (#397) 2024-04-17 20:29:41 +01:00
Alex Orlenko 45fd2fa40a mlua-sys: v0.5.2 2024-04-05 14:04:15 +01:00
Alex Orlenko 806bd202d6 v0.9.7 2024-04-05 12:46:12 +01:00
Alex Orlenko a644049087 Cosmetic changes for clippy 2024-04-05 12:44:18 +01:00
Alex Orlenko ad31bed1db Minor improvements in serializing:
Use `&str` instead of creating Lua string when serializing tuple variant and struct variant
2024-04-05 12:44:08 +01:00
Alex Orlenko 62f0bb97b0 Add Lua::create_ser_any_userdata() function 2024-04-05 12:03:40 +01:00
Alex Orlenko f67f8646ae Implement push_into_stack/from_stack for Option<T> 2024-03-28 18:18:08 +00:00
Alex Orlenko fa217d3706 Better Luau buffer type support.
- Add `Lua::create_buffer()` function
- Support serializing buffer type as a byte slice
- Support accessing copy of underlying bytes using `BString`
2024-03-28 13:05:01 +00:00
Yiyu Lin b62f2ee0f7 chore: make clippy happy (#388)
Co-authored-by: hzlinyiyu <hzlinyiyu@corp.netease.com>
2024-03-25 13:26:06 +00:00
Alex Orlenko 6e6c73e4c7 Add deserialize json benchmark 2024-03-23 22:31:42 +00:00
Alex Orlenko 508517c45e Do not use dependencies as implicit features.
Add `dep:` prefix instead.
2024-03-23 19:47:42 +00:00
Alex Orlenko a79840afc9 Suppress Rust 1.77 dead_code false warnings. 2024-03-23 11:42:46 +00:00
Alex Orlenko 39afe4c6f7 Add SerializeOptions::detect_serde_json_arbitrary_precision to detect serde_json::Number with arbitrary_precision and convert it to Lua number.
By default the option is disabled and such numbers represented as Lua objects with `$serde_json::private::Number` key.
Fixes #385
2024-03-23 11:27:46 +00:00
Alex Orlenko 038cc5f974 Bump rustyline to 14.0 2024-03-22 00:40:21 +00:00
Alex Orlenko 59b14000f3 Update hyper examples 2024-03-22 00:35:37 +00:00
19年梦醒 849206ef9d update hyper to v1, and add shell command example (#384)
Co-authored-by: Alex Orlenko <zxteam@protonmail.com>
2024-03-21 23:06:00 +00:00
Alex Orlenko 80fff4f2e7 Update reqwest to 0.12 2024-03-21 22:29:34 +00:00
Alex Orlenko 9734146313 Add Function::deep_clone() (Luau only) 2024-03-21 22:14:00 +00:00
Alex Orlenko 58be624222 Remove redundant "match" when checking userdata type via AnyUserData::is.
Fixes #386
2024-03-20 19:29:47 +00:00
Alex Orlenko 3d43103431 Make __idiv metamethod available for luau
Closes #383
2024-03-20 19:20:05 +00:00
Alex Orlenko 5a22437d5f Assert that luau_compile returns non-null pointer.
Fixes #381
2024-03-16 23:39:10 +00:00
Alex Orlenko 83c075c72b Implement IntoLua for RegistryKey 2024-02-29 22:45:32 +00:00
Alex Orlenko 270b98a429 v0.9.6 2024-02-29 15:39:14 +00:00
Alex Orlenko 0ee3324462 Add LUA_TCDATA to util::to_string() helper 2024-02-29 12:57:04 +00:00
Alex Orlenko 8a9c4f0b15 Optimize table array traversal during serialization 2024-02-11 23:02:36 +00:00
Alex Orlenko 34db5f985e Refactor benchmarks 2024-02-11 17:48:25 +00:00
Alex Orlenko 020e8a78a8 Impl FromLua for RegistryKey 2024-02-10 15:41:48 +00:00
Alex Orlenko 3ca7b4942e Implement IntoLua for &Value 2024-02-07 00:10:49 +00:00
Alex Orlenko 1754226c74 Impl IntoLua::push_into_stack for integers 2024-02-03 21:32:49 +00:00
Alex Orlenko 3014c4d7a1 Add REF_STACK_RESERVE constant 2024-02-02 23:26:33 +00:00
Alex Orlenko 908f37656a Add to_pointer function to Function/Table/Thread 2024-02-02 09:23:16 +00:00
Alex Orlenko f4d783cb41 Impl push_into_stack for StdResult 2024-02-01 23:42:44 +00:00
Alex Orlenko f5982bc204 Add inline to FromLua<bool>::from_stack 2024-01-27 14:40:32 +00:00
Alex Orlenko e30b425224 Fix crash when initializing Luau sandbox without stdlibs (#361) 2024-01-27 11:51:59 +00:00
Alex Orlenko 512921404c Add fastpath push_into_stack/from_stack methods for bool type 2024-01-26 14:07:56 +00:00
Alex Orlenko dfd82edc42 Add Lua::push() helper 2024-01-26 13:52:19 +00:00
Alex Orlenko 75a15ceabf v0.9.5 2024-01-25 22:24:58 +00:00
Alex Orlenko 60730fd068 Update compile tests messages 2024-01-25 18:19:16 +00:00
Alex Orlenko df778b7b33 Impl Into/FromLua for OwnedString 2024-01-25 18:04:55 +00:00
Alex Orlenko 45299c0ef1 Update authors 2024-01-25 12:56:57 +00:00
Alex Orlenko 38eec1236c Update itertools dependency 2024-01-25 10:49:39 +00:00
Alex Orlenko 145c5b316b Fix FromLua derive proc macro to cover more cases 2024-01-25 10:26:43 +00:00
Alex Orlenko e97e69a309 Update Luau to 0.609 (luau-src v0.8.0) 2024-01-25 09:38:46 +00:00
Alex Orlenko 2ac7b23596 Impl Into/FromLua for OwnedThread 2024-01-25 09:34:37 +00:00
Alex Orlenko 8200bee467 Implement IntoLua for ref to String/Table/Function/AnyUserData
This would prevent cloning plus has better performance when pushing values to Lua stack (`IntoLua::push_into_stack` method)
2024-01-23 22:14:50 +00:00
Alex Orlenko fe6ab250bf Update codecov links after moving repo 2024-01-23 21:56:00 +00:00
Alex Orlenko 804972b099 Fix typos in examples/guided_tour 2024-01-23 20:50:57 +00:00
Alex Orlenko 727f99ee4d Implement IntoLua for &RegistryKey
This would allow just passing registry keys to arguments with fasttrack to push directly into stack.
2024-01-20 22:04:28 +00:00
Alex Orlenko 3c801e7b17 Expose internal POLL_PENDING constant (hidden) 2024-01-20 15:52:00 +00:00
Alex Orlenko a38e484fe9 Increase luau max stack size to 1M from 100k 2024-01-18 23:05:23 +00:00
Alex Orlenko eed48889cd v0.9.4 2024-01-10 15:41:31 +00:00
Alex Orlenko b5896173fd Include skip_memory_check code only when the corresponding attribute set for module 2024-01-10 10:08:33 +00:00
Alex Orlenko 205989f569 Fix edge case when loading many-in-one module from thread without using its state.
If Lua previously been initialized in main thread and then new module was loaded from thread
we reuse old state which confuses Lua loader.
2024-01-10 00:37:02 +00:00
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
Alex Orlenko 03b6dfb2c3 v0.9.0-beta.3 2023-06-07 14:40:49 +01:00
Alex Orlenko cf1cb31150 mlua_derive: v0.9.0-beta.2 2023-06-07 13:08:02 +01:00
Alex Orlenko 7d586f52f2 mlua-sys: v0.2.0 2023-06-07 13:06:51 +01:00
Alex Orlenko e85818e199 mlua-sys: include luau to luau-codegen feature flag 2023-06-07 13:05:16 +01:00
Alex Orlenko 16bec29274 Fix setting Luau version number in _VERSION env var 2023-06-07 12:35:45 +01:00
Alex Orlenko 5a135a331a Add UserDataFields::add_field() method to add static fields to UserData.
Plus `UserDataFields::add_meta_field()` for static meta fields.
Fix propagating fields to wrapped UserData types.
2023-06-07 12:18:24 +01:00
Alex Orlenko e7b712e29f Rely on LUA_MINSTACK in callbacks 2023-06-06 21:38:45 +01:00
Alex Orlenko d5483988d2 Optimize polling async functions (40% performance win) 2023-06-04 20:53:12 +01:00
Alex Orlenko 0f5c68dcf8 Update criterion to 0.5 2023-06-04 18:31:10 +01:00
Alex Orlenko 8ab0ccf11c Don't keep poll function in environment globals when polling async functions.
This is redundant after deprecating scoped async.
Closes #281
2023-06-04 02:31:29 +01:00
Alex Orlenko 9596f2e9ee Fix warning when compiling for lua51 2023-06-03 14:24:34 +01:00
Alex Orlenko 1dc32452e6 Support getting and setting environment for Lua functions.
Closes #218
2023-06-03 12:56:49 +01:00
Alex Orlenko 9785722d61 Require environment to be a Table instead of Value in Chunks.
In addition to that, `AsChunk::env()` renamed to `AsChunk::environment()`.
2023-06-03 00:00:53 +01:00
Alex Orlenko c905a34b1d (Luau) Set thread globals to main thread globals when resetting the thread 2023-06-02 21:13:23 +01:00
Alex Orlenko a1089dbf95 Optimize a bit calling Rust functions from Lua.
This is done by reusing upvalue as a captured variable.
Also in async functions upvalue is always set (async cannot be scoped) so extra checks removed.
2023-05-30 15:15:20 +01:00
Alex Orlenko baf25e263f Change _mlua to __mlua prefix for internal chunk names 2023-05-30 00:36:09 +01:00
Alex Orlenko 3abf73dee5 Update FunctionInfo doc and interface 2023-05-30 00:33:57 +01:00
Alex Orlenko 4adebd31f9 Update .gitignore 2023-05-30 00:33:16 +01:00
Alex Orlenko cea2d7fd15 Refactor application data container.
Now it's allowed at the same time mutably and immutably borrow different types.
Each value in the application data container is stored in it's own `RefCell` wrapper.
Also added new function `Lua::try_set_app_data()`.
2023-05-29 00:30:31 +01:00
Alex Orlenko e0224ab159 Use futures-core and futures-tasks via futures-util.
Just to reduce number of dependencies.
2023-05-29 00:30:29 +01:00
Alex Orlenko 6dee339783 Use lua_closethread in AsyncThread::drop() 2023-05-28 01:43:15 +01:00
Alex Orlenko b674d7906d Include (known) userdata type to Value debug pretty-print 2023-05-28 01:12:42 +01:00
Alex Orlenko 3d7796de55 clippy 2023-05-27 22:08:42 +01:00
Alex Orlenko 4306e6e978 Add Value::to_string() method similar to luaL_tolstring.
It uses `__tostring` metamethod if set.
Closes #279
2023-05-27 01:29:08 +01:00
Alex Orlenko 68e65a8ffe Set __name field in userdata metatable if not provided 2023-05-25 10:59:46 +01:00
Alex Orlenko 2efc637ab9 Move util into mod and add short_type_name function 2023-05-25 10:23:52 +01:00
Alex Orlenko 22e748557c Add new feature flag luau-jit to enable experimental Luau codegen backend 2023-05-24 23:13:12 +01:00
Alex Orlenko 77effb5055 Refactor callback_error_ext
Also it makes rust-analyzer happy
2023-05-24 00:46:59 +01:00
Alex Orlenko 1c66a02878 Update to Lua 5.4.6 2023-05-21 00:55:06 +01:00
Alex Orlenko 1ac98e7d16 - Allow downcasting error wrapped into Error::WithContext
- Overwrite error context when called multiple times
2023-05-08 23:39:53 +01:00
Alex Orlenko bbd2fe06e1 Use DeserializeOwned and remove lifetime from LuaSerdeExt trait 2023-05-06 23:07:34 +01:00
Alex Orlenko d951cb503f Add Value::NULL constant 2023-05-06 22:53:40 +01:00
Alex Orlenko bbd2488f79 Add OwnedAnyUserData::take() 2023-05-03 23:29:33 +01:00
Alex Orlenko 3253ae8f4a v0.9.0-beta.2 2023-04-27 23:13:32 +01:00
Alex Orlenko fdda0d3724 Update compile tests 2023-04-27 23:06:38 +01:00
Alex Orlenko a1d385c7b7 Add OwnedString 2023-04-26 23:17:27 +01:00
Alex Orlenko 21b834decc Update coverage calculation (exclude mlua-sys) 2023-04-26 22:22:11 +01:00
Alex Orlenko 4daa7de997 Various improvements for owned types, including:
- tests
- shortcuts for `OwnedFunction` and `OwnedAnyUserData`
2023-04-26 15:40:48 +01:00
Alex Orlenko bd68a155c0 Replace ManuallyDrop with MaybeUninit 2023-04-26 15:40:47 +01:00
Alex Orlenko 993aaf44c7 mlua-sys: v0.1.1 2023-04-24 11:25:22 +01:00
Alex Orlenko 54cbc62727 mlua-sys: use luau-src v0.5.6+luau573 2023-04-24 11:24:32 +01:00
Alex Orlenko 2d6a0fdf9c Check for reference leak detection only in mlua integratin tests.
This is not necessary an error and should not be enforced by default.
Fixes #268.
2023-04-23 10:56:26 +01:00
Alex Orlenko d0cbd32ad2 (clippy) mlua_derive: don't clone function name 2023-04-22 11:07:33 +01:00
Alex Orlenko be64706cff Missing doc for OwnedAnyUserData 2023-04-14 10:29:34 +01:00
Alex Orlenko c178bc0a55 cargo fmt 2023-04-14 10:29:12 +01:00
Alex Orlenko 0fccdfed5c Fix feature flags for owned types 2023-04-14 01:17:26 +01:00
Alex Orlenko aaf0a5e44a Remove usage of crate::ffi (clippy) 2023-04-14 00:36:31 +01:00
Alex Orlenko 98888883bc mlua-sys: clippy 2023-04-14 00:31:06 +01:00
Alex Orlenko 65e72f39ae Remove unstable feature flag from Function::wrap/wrap_mut/wrap_async 2023-04-14 00:21:18 +01:00
Alex Orlenko f9d1213c4b Don't set html_root_url (it's not recommended) 2023-04-13 22:44:40 +01:00
Alex Orlenko dc10c80e61 Add documentation url to mlua-sys 2023-04-13 22:32:27 +01:00
Alex Orlenko 23b9cefdca Add missing doc_cfg feature to mlua-sys 2023-04-13 21:55:37 +01:00
Alex Orlenko 15dc0e9f23 Move ffi module into mlua-sys crate 2023-04-12 23:23:34 +01:00
Alex Orlenko 0c53e09e30 Update tarpaulin settings 2023-04-12 01:03:33 +01:00
Alex Orlenko cdbf04f50c Add pretty-print to the Debug formatting to Value to Table.
This would allow dumping any Lua variable in human readable form.
2023-04-11 20:36:41 +01:00
Alex Orlenko ba324b4f54 Allow deserializing Lua null into unit(()) or unit struct. See #264 2023-04-10 01:49:51 +01:00
Alex Orlenko 3e83753466 Add Thread::set_hook() function 2023-04-08 23:53:48 +01:00
Alex Orlenko 288934c82c Refactor HookTriggers (make it const) 2023-04-05 01:02:27 +01:00
Alex Orlenko 483bc80fc4 Add must_use to AsyncThread 2023-04-05 00:23:13 +01:00
Alex Orlenko 354c69acc4 Update README 2023-03-30 23:22:01 +01:00
Alex Orlenko 6fa0a78ec0 Prepare v0.9.0-beta.1 2023-03-30 23:18:10 +01:00
Alex Orlenko f66932e8d2 Revert "Rename Owned*::to_ref() to as_ref()"
This reverts commit 5394faca16.
2023-03-30 22:35:43 +01:00
Alex Orlenko da6d5a93c9 Update rustyline dependency 2023-03-30 22:34:41 +01:00
Alex Orlenko 1be927bc5d Don't call error handler for memory errors in coroutines 2023-03-30 17:29:57 +01:00
Alex Orlenko 0848ddcdf7 Clippy 2023-03-28 20:53:22 +01:00
Alex Orlenko e182d474e0 More user-friendly error message about missing value for name attribute in module macro
Update the `[lua_module]` doc
2023-03-28 16:27:16 +01:00
Alex Orlenko 0f937b0a03 Switch to syn 2.0 2023-03-28 13:56:59 +01:00
Alex Orlenko cd9fc1d08f Update Error doc 2023-03-26 20:58:43 +01:00
Alex Orlenko d9aac08b81 Support setting memory limit for Lua 5.1/JIT/Luau
Other versions already support this feature.
Closes #119
2023-03-26 00:06:52 +00:00
Alex Orlenko 9c1669020b Clippy 2023-03-25 23:38:57 +00:00
Alex Orlenko 742307a267 Add &Lua to luau interrupt callback (fixes #197) 2023-03-25 16:30:31 +00:00
Alex Orlenko 781ded573a Seal LuaSerdeExt/TableExt/AnyUserDataExt 2023-03-19 02:38:21 +00:00
Alex Orlenko 6a647f58be Add Error::downcast_ref() method 2023-03-19 00:22:51 +00:00
Alex Orlenko 4bc6501d2e Tests for the ErrorContext trait 2023-03-17 01:31:07 +00:00
Alex Orlenko 8d80321738 Add ErrorContext extension trait to attach additional context to Error 2023-03-17 01:20:57 +00:00
Alex Orlenko a0d37fd182 Rename error to cause in Error::BadArgument 2023-03-16 23:48:54 +00:00
Alex Orlenko 03787668fd Improve error reporting when calling Rust functions from Lua.
In particular new error type `Error::BadArgument` added to help identify bad argument position or name (eg `self` for userdata).
2023-03-14 23:23:46 +00:00
Alex Orlenko 3059f82552 Update README and CHANGELOG 2023-03-12 23:54:19 +00:00
Alex Orlenko 22fbcfd9fb Update edition everywhere 2023-03-12 01:03:31 +00:00
Alex Orlenko 304880bfd1 Fast metatable check for Lua::push_userdata_ref 2023-03-10 10:34:41 +00:00
Alex Orlenko 33c276d0b4 Optimize userdata methods call when __index and fields_getters are nil 2023-03-04 11:58:34 +00:00
Alex Orlenko 87d027e8ac Rename rust_module in tests to test_module
This causes warning during compilation
2023-03-03 18:58:13 +00:00
Alex Orlenko d7db1b7f2e Add Waker lifetime to WakerGuard 2023-03-03 18:29:04 +00:00
Alex Orlenko 22c973af02 Async: Don't clone function when executing it from recycled thread 2023-03-03 18:22:39 +00:00
Alex Orlenko 8c18fa1764 Async: store pointer to Waker in extra data rather than in Lua ref thread 2023-03-03 15:19:54 +00:00
Alex Orlenko 5394faca16 Rename Owned*::to_ref() to as_ref() 2023-03-02 15:36:11 +00:00
Alex Orlenko 4a3f6d60ad Update exports in libs and prelude 2023-03-02 15:34:42 +00:00
Alex Orlenko 68b60e2a0a Add UserDataRef and UserDataRefMut types that implement FromLua
and can be used as accessors to underlying `AnyUserData` type.
2023-02-26 21:52:28 +00:00
Alex Orlenko 888bd77e60 Add AnyUserDataExt to prelude 2023-02-25 23:59:29 +00:00
Alex Orlenko 949906f9f7 Fix potential deadlock when trying to reuse dropped RegistryKey.
If no free registry id found, we call protect_lua! macro while keeping mutex guard to the unref list.
Protected calls can trigger garbage collection and if RegistryKey is placed in userdata being collected, this can lead to deadlock.
The solution is drop mutex guard as soon as possible.
Also this commit includes optimization in creating reference in Lua registry.
2023-02-22 20:15:40 +00:00
Alex Orlenko 94f01e597c Add AnyUserDataExt trait with auxiliary functions for AnyUserData 2023-02-18 23:40:28 +00:00
Alex Orlenko b8e3290f35 Update LuaRef/LuaOwnedRef
Move content of `Lua::make_owned_ref` into `LuaRef::into_owned`
Add crate-visible `Lua::clone` function (not trait)
2023-02-15 09:20:03 +00:00
Alex Orlenko 03ab828342 Return evicted value to Lua::set_app_data if it's already present.
Add "Panics" section to `Lua::*_app_data` docs.
2023-02-12 23:38:09 +00:00
Alex Orlenko b66bff9155 Drop Lua::async_scope as it's unsound 2023-02-12 22:25:10 +00:00
Alex Orlenko f52abf919e Add create_userdata_ref/create_userdata_ref_mut for scope (#206)
New methods would allow creating userdata objects from (mutable) reference
to a UserData of registered type.
2023-02-12 16:37:02 +00:00
Alex Orlenko b790b525c1 Fix clippy warnings 2023-02-07 22:43:48 +00:00
Alex Orlenko f5182e0584 Force protected mode for long enough strings 2023-02-06 23:46:31 +00:00
Alex Orlenko 47c8300ccf Allow registering and creating custom userdata types that don't necessary implement the UserData trait.
This is useful to register 3rd party types that cannot implement `UserData` due to Rust orphan rules.
See #206
2023-02-03 23:46:04 +00:00
Alex Orlenko 8339621f9c Rename to_lua_err -> into_lua_err 2023-01-06 21:35:15 +00:00
Alex Orlenko 16cbb442e5 Fix clippy warnings 2023-01-06 20:50:29 +00:00
Alex Orlenko 88da28a68d Allow deserializing values from serializable UserData using Lua::from_value() method.
Closes #240
2023-01-06 10:41:17 +00:00
Alex Orlenko a62061f453 Update README 2023-01-04 20:58:03 +00:00
Alex Orlenko cb3d58b1c7 Better Debug for String 2023-01-04 15:56:57 +00:00
Alex Orlenko fa46720f5a Add Table::clear() method 2023-01-02 15:50:21 +00:00
Alex Orlenko 72b9209ae9 Rename wrapped_failure/thread cache to pool 2022-12-30 21:48:12 +00:00
Alex Orlenko 0cb7058476 Rename MultiValue cache to pool 2022-12-30 14:50:39 +00:00
Alex Orlenko ac8b3424d1 Update criterion 2022-12-30 13:45:59 +00:00
Alex Orlenko 2cb4b49d0d Don't use unstable features in examples 2022-12-30 13:39:27 +00:00
Alex Orlenko d67332a3ac Refactor userdata::MetaMethod.
Take `impl AsRef<str>` as name of metamethod (instead of `Into<MetaMethod>`) in UserDataMethods/UserDataFields.
Change position of generic args in userdata.
2022-12-24 19:16:58 +00:00
Alex Orlenko 22c6e9a7e7 Enable unstable feature flag in CI 2022-12-22 16:30:14 +00:00
Alex Orlenko 1d4a135e8e Add Function::wrap/Function::wrap_mut/Function::wrap_async to wrap functions into a type that implements IntoLua trait.
This is useful to avoid calling `lua.create_function*` every time when `Function` handle is needed.
2022-12-22 16:24:35 +00:00
Alex Orlenko 9d28b790e7 Fix module tests 2022-12-21 21:05:23 +00:00
Alex Orlenko 61c285746c Remove FromLua impl for UserData+Clone 2022-12-20 23:30:18 +00:00
Alex Orlenko a13c188de3 Rename ToLua/ToLuaMulti -> IntoLua/IntoLuaMulti 2022-12-19 23:00:47 +00:00
Alex Orlenko c60f633a62 Add "unstable" feature flag.
Hide owned types under the new feature flag.
Drop OwnedString/OwnedThread types (unlikely they are useful).
2022-12-19 22:26:44 +00:00
Alex Orlenko 56abc4a700 Refactor AsChunk trait 2022-12-19 21:12:36 +00:00
Alex Orlenko 9b4e3a1598 Refactor LuaInner state
Add static_assertions to check for auto traits impl on compilation stage
Bump MSRV to 1.63 (required for `Ref::filter_map`)
2022-12-19 16:09:05 +00:00
Alex Orlenko 0aa30226df Check for invalid args when parsing #[lua_module(...)] proc macro 2022-12-18 00:35:41 +00:00
Alex Orlenko fdb5724053 Add ?Sized to PartialEq for Lua String 2022-12-18 00:35:41 +00:00
rise0chen b532f86674 feat: Support custom name of lua_module 2022-12-18 00:35:41 +00:00
Alex Orlenko c44463f030 Replace impl AsRef<str> with simple &str 2022-12-18 00:35:41 +00:00
Alex Orlenko de38e299fc Use impl AsRef<str> for userdata methods/fields instead of generic param.
Use `impl AsRef<str>` for module names and named registry values.
2022-12-18 00:35:41 +00:00
Alex Orlenko 7526773a90 Do not clear usevalues when taking value out of userdata.
It has big performance penalty.
Lua GC can collect uservalues when userdata is not referenced anymore.
2022-12-18 00:35:41 +00:00
Alex Orlenko bf79d9e75d Initial implementation of owned Lua types 2022-12-18 00:35:41 +00:00
Joel Natividad 02c08c6350 upgrade bstr from 0.2 to 1.0 2022-12-18 00:35:40 +00:00
Alex Orlenko 05eb20f9c6 Fix subtraction overflow when calculating index for MultiValue::get().
Return `None` instead of panic in debug mode.
Fixes #232
2022-12-15 21:10:31 +00:00
Alex Orlenko 9716918517 Fix lifetime of DebugNames in Debug::names() and DebugSource in Debug::source().
This can cause use-after-free if used wrong.
Now invalid code would not compile.
Reported in #230
2022-12-09 23:55:38 +00:00
Alex Orlenko c88417a3b6 Redefine _VERSION for Luau to include version number.
https://github.com/khvzak/mlua/discussions/228
2022-12-03 21:21:28 +00:00
Alex Orlenko 6e95386f30 Update Luau to 0.555 (luau-src 0.5.0)
This version includes inreased LUAI_MAXCSTACK limit (100000)
2022-12-03 17:55:26 +00:00
Alex Orlenko 6807dfa22e v0.8.6 2022-11-07 01:05:14 +00:00
Alex Orlenko f27c49f931 Fix bug when recycled Registry slot can be set to Nil.
This can result in allocating the same slot twice and rewriting old value.
Lua uses (registry) table length to find next free slot and having Nil in the middle of the table can impact length calculation.
With this fix we ensure that Nil values uses a special LUA_REFNIL slot.
2022-11-07 00:10:57 +00:00
Alex Orlenko 693a808b6e v0.8.5 2022-10-30 12:31:26 +00:00
Alex Orlenko a6ca65aa74 Better checks and tests when trying to modify a Luau readonly table 2022-10-30 11:41:09 +00:00
Alex Orlenko a7278cab78 Fix Table::raw_push for luau when readonly 2022-10-29 23:02:19 +01:00
Alex Orlenko e1bbd00a33 Fix getting caller information from Lua::load 2022-10-28 10:43:05 +01:00
Alex Orlenko e98998d6ac Add push/pop methods to Table 2022-10-23 23:27:17 +01:00
Alex Orlenko cca177df5b Minor serde optimizations 2022-10-23 03:13:24 +01:00
Alex Orlenko 65396a910f Optimize Lua::create_table to use reference thread if possible 2022-10-23 00:24:04 +01:00
Alex Orlenko de69d10d73 Optimize Lua::create_string to use reference thread if possible 2022-10-23 00:14:13 +01:00
Alex Orlenko 959e61b97c Optimize tables:
- Use reference thread directly for simple cases
- Fix issue when calling raw_set on readonly table (Luau)
- Add fasttrack methods for get/set/len when metatable is not set
2022-10-22 23:45:58 +01:00
Alex Orlenko 1040c0a347 Fix typo 2022-10-22 23:44:21 +01:00
Alex Orlenko 9045419586 Strings optimizations: get access to bytes directly from ref thread. 2022-10-22 23:43:08 +01:00
Alex Orlenko fcd162f3eb Replace Lua::ref_thread_exec 2022-10-22 23:39:49 +01:00
Alex Orlenko 0354703dbf Update formatting (Lua registry values) 2022-10-22 23:34:19 +01:00
Alex Orlenko 9831d0e397 Check that allocation does not exceed isize::MAX
See https://github.com/rust-lang/rust/issues/101899
2022-10-22 23:13:02 +01:00
Alex Orlenko 7f14d93c2b v0.8.4 2022-10-09 14:40:15 +01:00
Alex Orlenko 29c6c9cb58 Fix clippy warnings 2022-10-09 14:08:33 +01:00
Alex Orlenko e01af22bac Update push_userdata_ref 2022-10-09 13:56:30 +01:00
Alex Orlenko cfa959e599 Don't allocate extra byte for userdata (luau) 2022-10-09 13:56:06 +01:00
Alex Orlenko e523fb2c86 Update to Luau 0.548 (luau-src >=0.4) 2022-10-08 23:34:47 +01:00
Alex Orlenko 81a9998559 Update CI 2022-09-13 00:09:03 +01:00
Alex Orlenko 050ac4f5ad Add luaL_setfuncs to preserved functions 2022-09-05 23:13:06 +01:00
Alex Orlenko 967cbb53b0 Update compile tests 2022-08-23 23:12:37 +01:00
Alex Orlenko 44a8d86d8a Fix error message in tests (luau) 2022-08-23 23:08:00 +01:00
Alex Orlenko 33278d4a64 Check if chunk is empty in luaL_loadbufferx for Luau (fixes #200) 2022-08-23 11:35:08 +01:00
Trisha 20a16839aa Fix typo in doc comment 2022-08-23 01:47:17 +01:00
Alex Orlenko 83ed442bf9 v0.8.3 2022-08-02 12:43:15 +01:00
Alex Orlenko bf6708ba58 Fallback to Lua internal allocator if unable to create Lua VM with Rust one.
This should fix #194
2022-08-02 10:35:39 +01:00
Alex Orlenko 0cd724f63b Fix Lua assertion when inspecting another thread stack.
The thread can be dead and it's not safe to call __tostring metamethod (if present) on error object.
Fixes #195
2022-08-01 22:07:39 +01:00
Alex Orlenko 5330b900fd Check that Lua state is non-null in init_from_ptr() 2022-08-01 15:21:14 +01:00
Alex Orlenko ee32dc33f3 Check that lua_newstate() returns non-null Lua state 2022-08-01 12:56:36 +01:00
Alex Orlenko ab029b087d Close to-be-closed variables for Lua 5.4 when using call_async functions
Fixes #192
2022-07-31 15:33:02 +01:00
127 changed files with 13533 additions and 5492 deletions
+5 -5
View File
@@ -10,14 +10,14 @@ jobs:
options: --security-opt seccomp=unconfined options: --security-opt seccomp=unconfined
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v2 uses: actions/checkout@v3
- name: Generate code coverage - name: Generate coverage report
run: | run: |
cargo tarpaulin --verbose --features lua54,vendored,async,send,serialize,macros --out xml --exclude-files benches --exclude-files build --exclude-files mlua_derive --exclude-files src/ffi --exclude-files tests cargo tarpaulin --out xml --tests --exclude-files benches/* --exclude-files mlua-sys/src/*/*
- name: Upload to codecov.io - name: Upload report to codecov.io
uses: codecov/codecov-action@v1 uses: codecov/codecov-action@v3
with: with:
token: ${{secrets.CODECOV_TOKEN}} token: ${{secrets.CODECOV_TOKEN}}
fail_ci_if_error: false fail_ci_if_error: false
+168 -152
View File
@@ -7,35 +7,34 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
os: [ubuntu-20.04, macos-latest, windows-latest] os: [ubuntu-22.04, macos-latest, windows-latest]
rust: [stable] rust: [stable]
lua: [lua54, lua53, lua52, lua51, luajit, luau] lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4]
include: include:
- os: ubuntu-20.04 - os: ubuntu-22.04
target: x86_64-unknown-linux-gnu target: x86_64-unknown-linux-gnu
- os: macos-latest - os: macos-latest
target: x86_64-apple-darwin target: x86_64-apple-darwin
- os: windows-latest - os: windows-latest
target: x86_64-pc-windows-msvc target: x86_64-pc-windows-msvc
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v4
- uses: actions-rs/toolchain@v1 - uses: dtolnay/rust-toolchain@stable
with: with:
toolchain: ${{ matrix.rust }} toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }} target: ${{ matrix.target }}
override: true - uses: Swatinem/rust-cache@v2
- uses: Swatinem/rust-cache@v1 - name: Build ${{ matrix.lua }} vendored
- name: Build ${{ matrix.lua }} vendored run: |
run: | cargo build --features "${{ matrix.lua }},vendored"
cargo build --features "${{ matrix.lua }},vendored" cargo build --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot,unstable"
cargo build --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot" shell: bash
shell: bash - name: Build ${{ matrix.lua }} pkg-config
- name: Build ${{ matrix.lua }} pkg-config if: ${{ matrix.os == 'ubuntu-22.04' }}
if: ${{ matrix.os == 'ubuntu-20.04' && matrix.lua != 'lua54' }} run: |
run: | sudo apt-get update
sudo apt-get update sudo apt-get install -y --no-install-recommends liblua5.4-dev liblua5.3-dev liblua5.2-dev liblua5.1-0-dev libluajit-5.1-dev
sudo apt-get install -y --no-install-recommends liblua5.3-dev liblua5.2-dev liblua5.1-0-dev libluajit-5.1-dev cargo build --features "${{ matrix.lua }}"
cargo build --features "${{ matrix.lua }}"
build_aarch64_cross_macos: build_aarch64_cross_macos:
name: Cross-compile to aarch64-apple-darwin name: Cross-compile to aarch64-apple-darwin
@@ -45,60 +44,57 @@ jobs:
matrix: matrix:
lua: [lua54, lua53, lua52, lua51, luajit] lua: [lua54, lua53, lua52, lua51, luajit]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v4
- uses: actions-rs/toolchain@v1 - uses: dtolnay/rust-toolchain@stable
with: with:
toolchain: stable toolchain: stable
target: aarch64-apple-darwin target: aarch64-apple-darwin
override: true - name: Cross-compile
- name: Cross-compile run: cargo build --target aarch64-apple-darwin --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot,unstable"
run: cargo build --target aarch64-apple-darwin --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot"
build_aarch64_cross_ubuntu: build_aarch64_cross_ubuntu:
name: Cross-compile to aarch64-unknown-linux-gnu name: Cross-compile to aarch64-unknown-linux-gnu
runs-on: ubuntu-20.04 runs-on: ubuntu-22.04
needs: build needs: build
strategy: strategy:
matrix: matrix:
lua: [lua54, lua53, lua52, lua51, luajit] lua: [lua54, lua53, lua52, lua51, luajit]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v4
- uses: actions-rs/toolchain@v1 - uses: dtolnay/rust-toolchain@stable
with: with:
toolchain: stable toolchain: stable
target: aarch64-unknown-linux-gnu target: aarch64-unknown-linux-gnu
override: true - name: Install ARM compiler toolchain
- name: Install ARM compiler toolchain run: |
run: | sudo apt-get update
sudo apt-get update sudo apt-get install -y --no-install-recommends gcc-aarch64-linux-gnu libc6-dev-arm64-cross
sudo apt-get install -y --no-install-recommends gcc-aarch64-linux-gnu libc6-dev-arm64-cross shell: bash
shell: bash - name: Cross-compile
- name: Cross-compile run: cargo build --target aarch64-unknown-linux-gnu --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot,unstable"
run: cargo build --target aarch64-unknown-linux-gnu --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot" shell: bash
shell: bash
build_armv7_cross_ubuntu: build_armv7_cross_ubuntu:
name: Cross-compile to armv7-unknown-linux-gnueabihf name: Cross-compile to armv7-unknown-linux-gnueabihf
runs-on: ubuntu-20.04 runs-on: ubuntu-22.04
needs: build needs: build
strategy: strategy:
matrix: matrix:
lua: [lua54, lua53, lua52, lua51] lua: [lua54, lua53, lua52, lua51]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v4
- uses: actions-rs/toolchain@v1 - uses: dtolnay/rust-toolchain@stable
with: with:
toolchain: stable toolchain: stable
target: armv7-unknown-linux-gnueabihf target: armv7-unknown-linux-gnueabihf
override: true - name: Install ARM compiler toolchain
- name: Install ARM compiler toolchain run: |
run: | sudo apt-get update
sudo apt-get update sudo apt-get install -y --no-install-recommends gcc-arm-linux-gnueabihf libc-dev-armhf-cross
sudo apt-get install -y --no-install-recommends gcc-arm-linux-gnueabihf libc-dev-armhf-cross shell: bash
shell: bash - name: Cross-compile
- name: Cross-compile run: cargo build --target armv7-unknown-linux-gnueabihf --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot,unstable"
run: cargo build --target armv7-unknown-linux-gnueabihf --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot" shell: bash
shell: bash
test: test:
name: Test name: Test
@@ -106,35 +102,35 @@ jobs:
needs: build needs: build
strategy: strategy:
matrix: matrix:
os: [ubuntu-20.04, macos-latest, windows-latest] os: [ubuntu-22.04, macos-latest, windows-latest]
rust: [stable, nightly] rust: [stable, nightly]
lua: [lua54, lua53, lua52, lua51, luajit, luajit52, luau] lua: [lua54, lua53, lua52, lua51, luajit, luajit52, luau, luau-jit, luau-vector4]
include: include:
- os: ubuntu-20.04 - os: ubuntu-22.04
target: x86_64-unknown-linux-gnu target: x86_64-unknown-linux-gnu
- os: macos-latest - os: macos-latest
target: x86_64-apple-darwin target: x86_64-apple-darwin
- os: windows-latest - os: windows-latest
target: x86_64-pc-windows-msvc target: x86_64-pc-windows-msvc
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v4
- uses: actions-rs/toolchain@v1 - uses: dtolnay/rust-toolchain@stable
with: with:
toolchain: ${{ matrix.rust }} toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }} target: ${{ matrix.target }}
override: true - uses: Swatinem/rust-cache@v2
- uses: Swatinem/rust-cache@v1 - name: Run ${{ matrix.lua }} tests
- name: Run ${{ matrix.lua }} tests run: |
run: | cargo test --features "${{ matrix.lua }},vendored"
cargo test --features "${{ matrix.lua }},vendored" cargo test --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot"
cargo test --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot" cargo test --features "${{ matrix.lua }},vendored,async,serialize,macros,parking_lot,unstable"
shell: bash shell: bash
- name: Run compile tests (macos lua54) - name: Run compile tests (macos lua54)
if: ${{ matrix.os == 'macos-latest' && matrix.lua == 'lua54' }} if: ${{ matrix.os == 'macos-latest' && matrix.lua == 'lua54' }}
run: | run: |
TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored" -- --ignored TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored" -- --ignored
TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot" -- --ignored TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot,unstable" -- --ignored
shell: bash shell: bash
test_with_sanitizer: test_with_sanitizer:
name: Test with address sanitizer name: Test with address sanitizer
@@ -142,25 +138,25 @@ jobs:
needs: build needs: build
strategy: strategy:
matrix: matrix:
os: [ubuntu-20.04] os: [ubuntu-22.04]
rust: [nightly] rust: [nightly]
lua: [lua54, lua53, lua52, lua51, luajit, luau] lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4]
include: include:
- os: ubuntu-20.04 - os: ubuntu-22.04
target: x86_64-unknown-linux-gnu target: x86_64-unknown-linux-gnu
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v4
- uses: actions-rs/toolchain@v1 - uses: dtolnay/rust-toolchain@stable
with: with:
toolchain: ${{ matrix.rust }} toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }} target: ${{ matrix.target }}
override: true - uses: Swatinem/rust-cache@v2
- uses: Swatinem/rust-cache@v1 - name: Run ${{ matrix.lua }} tests with address sanitizer
- name: Run ${{ matrix.lua }} tests with address sanitizer run: |
run: | cargo test --tests --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot,unstable" --target x86_64-unknown-linux-gnu -- --skip test_too_many_recursions
RUSTFLAGS="-Z sanitizer=address" \ shell: bash
cargo test --tests --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot" --target x86_64-unknown-linux-gnu -- --skip test_too_many_recursions env:
shell: bash RUSTFLAGS: -Z sanitizer=address
test_modules: test_modules:
name: Test modules name: Test modules
@@ -168,27 +164,26 @@ jobs:
needs: build needs: build
strategy: strategy:
matrix: matrix:
os: [ubuntu-20.04, macos-latest] os: [ubuntu-22.04, macos-latest]
rust: [stable] rust: [stable]
lua: [lua54, lua53, lua52, lua51, luajit] lua: [lua54, lua53, lua52, lua51, luajit, luau]
include: include:
- os: ubuntu-20.04 - os: ubuntu-22.04
target: x86_64-unknown-linux-gnu target: x86_64-unknown-linux-gnu
- os: macos-latest - os: macos-latest
target: x86_64-apple-darwin target: x86_64-apple-darwin
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v4
- uses: actions-rs/toolchain@v1 - uses: dtolnay/rust-toolchain@stable
with: with:
toolchain: ${{ matrix.rust }} toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }} target: ${{ matrix.target }}
override: true - uses: Swatinem/rust-cache@v2
- uses: Swatinem/rust-cache@v1 - name: Run ${{ matrix.lua }} module tests
- name: Run ${{ matrix.lua }} module tests run: |
run: | (cd tests/module && cargo build --release --features "${{ matrix.lua }}")
(cd tests/module && cargo build --release --features "${{ matrix.lua }}") (cd tests/module/loader && cargo test --release --features "${{ matrix.lua }},vendored")
(cd tests/module/loader && cargo test --release --features "${{ matrix.lua }},vendored") shell: bash
shell: bash
test_modules_windows: test_modules_windows:
name: Test modules on Windows name: Test modules on Windows
@@ -201,42 +196,63 @@ jobs:
run: run:
shell: msys2 {0} shell: msys2 {0}
steps: steps:
- uses: msys2/setup-msys2@v2 - uses: msys2/setup-msys2@v2
- uses: actions/checkout@v2 - uses: actions/checkout@v4
- name: Install Rust & Lua - name: Install Rust & Lua
run: | 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 pacman -S --noconfirm mingw-w64-x86_64-rust mingw-w64-x86_64-lua mingw-w64-x86_64-luajit mingw-w64-x86_64-pkg-config
- name: Run ${{ matrix.lua }} module tests - name: Run ${{ matrix.lua }} module tests
run: | run: |
(cd tests/module && cargo build --release --features "${{ matrix.lua }}") (cd tests/module && cargo build --release --features "${{ matrix.lua }}")
(cd tests/module/loader && cargo test --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: rustfmt:
name: Rustfmt name: Rustfmt
runs-on: ubuntu-20.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v4
- uses: actions-rs/toolchain@v1 - uses: dtolnay/rust-toolchain@stable
with: with:
toolchain: stable toolchain: stable
components: rustfmt components: rustfmt
override: true - run: cargo fmt -- --check
- run: cargo fmt -- --check
clippy: clippy:
name: Clippy check name: Clippy
runs-on: ubuntu-20.04 runs-on: ubuntu-22.04
strategy: strategy:
matrix: matrix:
lua: [lua54, lua53, lua52, lua51, luajit, luau] lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v4
- uses: actions-rs/toolchain@v1 - uses: dtolnay/rust-toolchain@stable
with: with:
toolchain: nightly toolchain: nightly
components: clippy components: clippy
override: true - uses: giraffate/clippy-action@v1
- uses: actions-rs/clippy-check@v1
with: with:
token: ${{ secrets.GITHUB_TOKEN }} reporter: 'github-pr-review'
args: --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot" clippy_flags: --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot,unstable"
+1
View File
@@ -4,3 +4,4 @@ Cargo.lock
.vscode/ .vscode/
.DS_Store .DS_Store
.stignore
+213
View File
@@ -1,3 +1,216 @@
## v0.9.9
- Minimal Luau updated to 0.629
- Fixed bug when attempting to reset or resume already running coroutines (#416).
- Added `RegistryKey::id()` method to get the underlying Lua registry key id.
## v0.9.8
- Fixed serializing same table multiple times (#408)
- Use `mlua-sys` v0.6 (to support Luau 0.624+)
- Fixed cross compilation of windows dlls from unix (#394)
## v0.9.7
- Implemented `IntoLua` for `RegistryKey`
- Mark `__idiv` metamethod as available for luau
- Added `Function::deep_clone()` method (Luau)
- Added `SerializeOptions::detect_serde_json_arbitrary_precision` option
- Added `Lua::create_buffer()` method (Luau)
- Support serializing buffer type as a byte slice (Luau)
- Perf: Implemented `push_into_stack`/`from_stack` for `Option<T>`
- Added `Lua::create_ser_any_userdata()` method
## v0.9.6
- Added `to_pointer` function to `Function`/`Table`/`Thread`
- Implemented `IntoLua` for `&Value`
- Implemented `FromLua` for `RegistryKey`
- Faster (~5%) table array traversal during serialization
- Some performance improvements for bool/int types
## v0.9.5
- Minimal Luau updated to 0.609
- Luau max stack size increased to 1M (from 100K)
- Implemented `IntoLua` for refs to `String`/`Table`/`Function`/`AnyUserData`/`Thread` + `RegistryKey`
- Implemented `IntoLua` and `FromLua` for `OwnedThread`/`OwnedString`
- Fixed `FromLua` derive proc macro to cover more cases
## v0.9.4
- Fixed loading all-in-one modules under mixed states (eg. main state and coroutines)
## 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()`
- Switch to `DeserializeOwned`
- Overwrite error context when called multiple times
- New feature flag `luau-jit` to enable (experimental) Luau codegen backend
- Set `__name` field in userdata metatable
- Added `Value::to_string()` method similar to `luaL_tolstring`
- Lua 5.4.6
- Application data container now allows to mutably and immutably borrow different types at the same time
- Performance optimizations
- Support getting and setting environment for Lua functions.
- Added `UserDataFields::add_field()` method to add static fields to UserData
Breaking changes:
- Require environment to be a `Table` instead of `Value` in Chunks.
- `AsChunk::env()` renamed to `AsChunk::environment()`
## v0.9.0-beta.2
New features:
- Added `Thread::set_hook()` function to set hook on threads
- Added pretty print to the Debug formatting to Lua `Value` and `Table`
- ffi layer moved to `mlua-sys` crate
- Added OwnedString (unstable)
Breaking changes:
- Refactor `HookTriggers` (make it const)
## v0.9.0-beta.1
New features:
- Owned Lua types (unstable feature flag)
- New functions `Function::wrap`/`Function::wrap_mut`/`Function::wrap_async`
- `Lua::register_userdata_type()` to register a custom userdata types (without requiring `UserData` trait)
- `Lua::create_any_userdata()`
- Added `create_userdata_ref`/`create_userdata_ref_mut` for scopes
- Added `AnyUserDataExt` trait with auxiliary functions for `AnyUserData`
- Added `UserDataRef` and `UserDataRefMut` type wrapped that implement `FromLua`
- Improved error handling:
* Improved error reporting when calling Rust functions from Lua.
* Added `Error::BadArgument` to help identify bad argument position or name
* Added `ErrorContext` extension trait to attach additional context to `Error`
Breaking changes:
- Refactored `AsChunk` trait
- `ToLua`/`ToLuaMulti` renamed to `IntoLua`/`IntoLuaMulti`
- Renamed `to_lua_err` to `into_lua_err`
- Removed `FromLua` impl for `T: UserData+Clone`
- Removed `Lua::async_scope`
- Added `&Lua` arg to Luau interrupt callback
Other:
- Better Debug for String
- Allow deserializing values from serializable UserData using `Lua::from_value()` method
- Added `Table::clear()` method
- Added `Error::downcast_ref()` method
- Support setting memory limit for Lua 5.1/JIT/Luau
- 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.
- Optimize userdata methods call when __index and fields_getters are nil
## v0.8.7
- Minimum Luau updated to 0.555 (`LUAI_MAXCSTACK` limit increased to 100000)
- `_VERSION` in Luau now includes version number
- Fixed lifetime of `DebugNames` in `Debug::names()` and `DebugSource` in `Debug::source()`
- Fixed subtraction overflow when calculating index for `MultiValue::get()`
## v0.8.6
- Fixed bug when recycled Registry slot can be set to Nil
## v0.8.5
- Fixed potential unsoundness when using `Layout::from_size_align_unchecked` and Rust 1.65+
- Performance optimizations around string and table creation in standalone mode
- Added fast track path to Table `get`/`set`/`len` methods without metatable
- Added new methods `push`/`pop`/`raw_push`/`raw_pop` to Table
- Fix getting caller information from `Lua::load`
- Better checks and tests when trying to modify a Luau readonly table
## v0.8.4
- Minimal Luau updated to 0.548
## v0.8.3
- Close to-be-closed variables for Lua 5.4 when using call_async functions (#192)
- Fixed Lua assertion when inspecting another thread stack. (#195)
- Use more reliable way to create LuaJIT VM (which can fail if use Rust allocator on non-x86 platforms)
## v0.8.2 ## v0.8.2
- Performance optimizations in handling UserData - Performance optimizations in handling UserData
+49 -36
View File
@@ -1,7 +1,8 @@
[package] [package]
name = "mlua" name = "mlua"
version = "0.8.2" # remember to update html_root_url and mlua_derive version = "0.9.9" # remember to update mlua_derive
authors = ["Aleksandr Orlenko <zxteam@pm.me>", "kyren <catherine@chucklefish.org>"] authors = ["Aleksandr Orlenko <zxteam@pm.me>", "kyren <catherine@kyju.org>"]
rust-version = "1.71"
edition = "2021" edition = "2021"
repository = "https://github.com/khvzak/mlua" repository = "https://github.com/khvzak/mlua"
documentation = "https://docs.rs/mlua" documentation = "https://docs.rs/mlua"
@@ -9,76 +10,88 @@ readme = "README.md"
keywords = ["lua", "luajit", "luau", "async", "scripting"] keywords = ["lua", "luajit", "luau", "async", "scripting"]
categories = ["api-bindings", "asynchronous"] categories = ["api-bindings", "asynchronous"]
license = "MIT" license = "MIT"
links = "lua"
build = "build/main.rs"
description = """ description = """
High level bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Roblox Luau High level bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Roblox Luau
with async/await features and support of writing native Lua modules in Rust. with async/await features and support of writing native Lua modules in Rust.
""" """
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = ["lua54", "vendored", "async", "send", "serialize", "macros", "parking_lot"] features = ["lua54", "vendored", "async", "send", "serialize", "macros", "parking_lot", "unstable"]
rustdoc-args = ["--cfg", "docsrs"] rustdoc-args = ["--cfg", "docsrs"]
[workspace] [workspace]
members = [ members = [
"mlua_derive", "mlua_derive",
"mlua-sys",
] ]
[features] [features]
lua54 = [] lua54 = ["ffi/lua54"]
lua53 = [] lua53 = ["ffi/lua53"]
lua52 = [] lua52 = ["ffi/lua52"]
lua51 = [] lua51 = ["ffi/lua51"]
luajit = [] luajit = ["ffi/luajit"]
luajit52 = ["luajit"] luajit52 = ["luajit", "ffi/luajit52"]
luau = ["luau0-src"] luau = ["ffi/luau", "dep:libloading"]
vendored = ["lua-src", "luajit-src"] luau-jit = ["luau", "ffi/luau-codegen"]
module = ["mlua_derive"] luau-vector4 = ["luau", "ffi/luau-vector4"]
async = ["futures-core", "futures-task", "futures-util"] vendored = ["ffi/vendored"]
module = ["dep:mlua_derive", "ffi/module"]
async = ["dep:futures-util"]
send = [] send = []
serialize = ["serde", "erased-serde"] serialize = ["dep:serde", "dep:erased-serde", "dep:serde-value"]
macros = ["mlua_derive/macros"] macros = ["mlua_derive/macros"]
unstable = []
[dependencies] [dependencies]
mlua_derive = { version = "=0.8.0", optional = true, path = "mlua_derive" } mlua_derive = { version = "=0.9.3", optional = true, path = "mlua_derive" }
bstr = { version = "0.2", features = ["std"], default_features = false } bstr = { version = "1.0", features = ["std"], default-features = false }
once_cell = { version = "1.0" } once_cell = { version = "1.0" }
num-traits = { version = "0.2.14" } num-traits = { version = "0.2.14" }
rustc-hash = "1.0" rustc-hash = "2.0"
futures-core = { version = "0.3.5", optional = true } futures-util = { version = "0.3", optional = true, default-features = false, features = ["std"] }
futures-task = { version = "0.3.5", optional = true }
futures-util = { version = "0.3.5", optional = true }
serde = { version = "1.0", optional = true } 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 } parking_lot = { version = "0.12", optional = true }
[build-dependencies] ffi = { package = "mlua-sys", version = "0.6.1", path = "mlua-sys" }
cc = { version = "1.0" }
pkg-config = { version = "0.3.17" } [target.'cfg(unix)'.dependencies]
lua-src = { version = ">= 544.0.0, < 550.0.0", optional = true } libloading = { version = "0.8", optional = true }
luajit-src = { version = ">= 210.4.0, < 220.0.0", optional = true }
luau0-src = { version = "0.3.6", optional = true }
[dev-dependencies] [dev-dependencies]
rustyline = "10.0"
criterion = { version = "0.3.4", features = ["html_reports", "async_tokio"] }
trybuild = "1.0" trybuild = "1.0"
futures = "0.3.5" futures = "0.3.5"
hyper = { version = "0.14", features = ["client", "server"] } hyper = { version = "1.2", features = ["full"] }
reqwest = { version = "0.11", features = ["json"] } hyper-util = { version = "0.1.3", features = ["full"] }
tokio = { version = "1.0", features = ["full"] } http-body-util = "0.1.1"
futures-timer = "3.0" reqwest = { version = "0.12", features = ["json"] }
tokio = { version = "1.0", features = ["macros", "rt", "time"] }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0" serde_json = { version = "1.0", features = ["arbitrary_precision"] }
maplit = "1.0" maplit = "1.0"
tempfile = "3" tempfile = "3"
static_assertions = "1.0"
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
criterion = { version = "0.5", features = ["async_tokio"] }
rustyline = "14.0"
tokio = { version = "1.0", features = ["full"] }
[lints.rust]
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(tarpaulin_include)'] }
[[bench]] [[bench]]
name = "benchmark" name = "benchmark"
harness = false harness = false
required-features = ["async"] required-features = ["async"]
[[bench]]
name = "serde"
harness = false
required-features = ["serialize"]
[[example]] [[example]]
name = "async_http_client" name = "async_http_client"
required-features = ["async", "macros"] required-features = ["async", "macros"]
+37 -12
View File
@@ -7,9 +7,9 @@
[crates.io]: https://crates.io/crates/mlua [crates.io]: https://crates.io/crates/mlua
[API Documentation]: https://docs.rs/mlua/badge.svg [API Documentation]: https://docs.rs/mlua/badge.svg
[docs.rs]: https://docs.rs/mlua [docs.rs]: https://docs.rs/mlua
[Coverage Status]: https://codecov.io/gh/khvzak/mlua/branch/master/graph/badge.svg?token=99339FS1CG [Coverage Status]: https://codecov.io/gh/mlua-rs/mlua/branch/main/graph/badge.svg?token=99339FS1CG
[codecov.io]: https://codecov.io/gh/khvzak/mlua [codecov.io]: https://codecov.io/gh/mlua-rs/mlua
[MSRV]: https://img.shields.io/badge/rust-1.56+-brightgreen.svg?&logo=rust [MSRV]: https://img.shields.io/badge/rust-1.71+-brightgreen.svg?&logo=rust
[Guided Tour] | [Benchmarks] | [FAQ] [Guided Tour] | [Benchmarks] | [FAQ]
@@ -17,6 +17,10 @@
[Benchmarks]: https://github.com/khvzak/script-bench-rs [Benchmarks]: https://github.com/khvzak/script-bench-rs
[FAQ]: FAQ.md [FAQ]: FAQ.md
> **Note**
>
> See v0.9 [release notes](https://github.com/khvzak/mlua/blob/main/docs/release_notes/v0.9.md).
`mlua` is bindings to [Lua](https://www.lua.org) programming language for Rust with a goal to provide `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. _safe_ (as far as it's possible), high level, easy to use, practical and flexible API.
@@ -24,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). `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 [GitHub Actions]: https://github.com/khvzak/mlua/actions
[Roblox Luau]: https://luau-lang.org [Roblox Luau]: https://luau-lang.org
@@ -41,6 +47,8 @@ Below is a list of the available feature flags. By default `mlua` does not enabl
* `luajit`: activate [LuaJIT] support * `luajit`: activate [LuaJIT] support
* `luajit52`: activate [LuaJIT] support with partial compatibility with Lua 5.2 * `luajit52`: activate [LuaJIT] support with partial compatibility with Lua 5.2
* `luau`: activate [Luau] support (auto vendored mode) * `luau`: activate [Luau] support (auto vendored mode)
* `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 * `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) * `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]) * `async`: enable async/await support (any executor can be used, eg. [tokio] or [async-std])
@@ -48,6 +56,7 @@ Below is a list of the available feature flags. By default `mlua` does not enabl
* `serialize`: add serialization and deserialization support to `mlua` types using [serde] framework * `serialize`: add serialization and deserialization support to `mlua` types using [serde] framework
* `macros`: enable procedural macros (such as `chunk!`) * `macros`: enable procedural macros (such as `chunk!`)
* `parking_lot`: support UserData types wrapped in [parking_lot]'s primitives (`Arc<Mutex>` and `Arc<RwLock>`) * `parking_lot`: support UserData types wrapped in [parking_lot]'s primitives (`Arc<Mutex>` and `Arc<RwLock>`)
* `unstable`: enable **unstable** features. The public API of these features may break between releases.
[5.4]: https://www.lua.org/manual/5.4/manual.html [5.4]: https://www.lua.org/manual/5.4/manual.html
[5.3]: https://www.lua.org/manual/5.3/manual.html [5.3]: https://www.lua.org/manual/5.3/manual.html
@@ -75,6 +84,20 @@ This works using Lua [coroutines](https://www.lua.org/manual/5.3/manual.html#2.6
- [HTTP Server](examples/async_http_server.rs) - [HTTP Server](examples/async_http_server.rs)
- [TCP Server](examples/async_tcp_server.rs) - [TCP Server](examples/async_tcp_server.rs)
**shell command examples**:
```shell
# async http client (hyper)
cargo run --example async_http_client --features=lua54,async,macros
# async http client (reqwest)
cargo run --example async_http_reqwest --features=lua54,async,macros,serialize
# async http server
cargo run --example async_http_server --features=lua54,async,macros
curl -v http://localhost:3000
```
### Serialization (serde) support ### Serialization (serde) support
With `serialize` feature flag enabled, `mlua` allows you to serialize/deserialize any type that implements [`serde::Serialize`] and [`serde::Deserialize`] into/from [`mlua::Value`]. In addition `mlua` provides [`serde::Serialize`] trait implementation for it (including `UserData` support). With `serialize` feature flag enabled, `mlua` allows you to serialize/deserialize any type that implements [`serde::Serialize`] and [`serde::Deserialize`] into/from [`mlua::Value`]. In addition `mlua` provides [`serde::Serialize`] trait implementation for it (including `UserData` support).
@@ -91,12 +114,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. 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. 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). `LUA_LINK` is optional and may be `dylib` (a dynamic library) or `static` (a static library, `.a` archive).
An example how to use them: An example how to use them:
``` sh ``` 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 `mlua` also supports vendored lua/luajit using the auxiliary crates [lua-src](https://crates.io/crates/lua-src) and
@@ -110,7 +133,7 @@ Add to `Cargo.toml` :
``` toml ``` toml
[dependencies] [dependencies]
mlua = { version = "0.8", features = ["lua54", "vendored"] } mlua = { version = "0.9.9", features = ["lua54", "vendored"] }
``` ```
`main.rs` `main.rs`
@@ -145,7 +168,7 @@ Add to `Cargo.toml` :
crate-type = ["cdylib"] crate-type = ["cdylib"]
[dependencies] [dependencies]
mlua = { version = "0.8", features = ["lua54", "vendored", "module"] } mlua = { version = "0.9.9", features = ["lua54", "module"] }
``` ```
`lib.rs` : `lib.rs` :
@@ -190,20 +213,22 @@ rustflags = [
] ]
``` ```
On Linux you can build modules normally with `cargo build --release`. 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. On Windows the target module will be linked with `lua5x.dll` library (depending on your feature flags).
Easiest way is to use either MinGW64 (as part of [MSYS2](https://github.com/msys2/msys2) package) with `pkg-config` or Your main application should provide this library.
MSVC with `LUA_INC` / `LUA_LIB` / `LUA_LIB_NAME` environment variables.
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 ### Publishing to luarocks.org
There is a LuaRocks build backend for mlua modules [`luarocks-build-rust-mlua`]. There is a LuaRocks build backend for mlua modules [`luarocks-build-rust-mlua`].
Modules written in Rust and published to luarocks: Modules written in Rust and published to luarocks:
- [`decasify`](https://github.com/alerque/decasify)
- [`lua-ryaml`](https://github.com/khvzak/lua-ryaml) - [`lua-ryaml`](https://github.com/khvzak/lua-ryaml)
- [`tiktoken_core`](https://github.com/gptlang/lua-tiktoken)
- [`toml-edit`](https://github.com/vhyrro/toml-edit.lua)
- [`typst-lua`](https://github.com/rousbound/typst-lua)
[`luarocks-build-rust-mlua`]: https://luarocks.org/modules/khvzak/luarocks-build-rust-mlua [`luarocks-build-rust-mlua`]: https://luarocks.org/modules/khvzak/luarocks-build-rust-mlua
+270 -154
View File
@@ -1,5 +1,7 @@
use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::Duration; use std::time::Duration;
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
use tokio::runtime::Runtime; use tokio::runtime::Runtime;
use tokio::task; use tokio::task;
@@ -10,10 +12,10 @@ fn collect_gc_twice(lua: &Lua) {
lua.gc_collect().unwrap(); lua.gc_collect().unwrap();
} }
fn create_table(c: &mut Criterion) { fn table_create_empty(c: &mut Criterion) {
let lua = Lua::new(); let lua = Lua::new();
c.bench_function("create [table empty]", |b| { c.bench_function("table [create empty]", |b| {
b.iter_batched( b.iter_batched(
|| collect_gc_twice(&lua), || collect_gc_twice(&lua),
|_| { |_| {
@@ -24,71 +26,55 @@ fn create_table(c: &mut Criterion) {
}); });
} }
fn create_array(c: &mut Criterion) { fn table_create_array(c: &mut Criterion) {
let lua = Lua::new(); let lua = Lua::new();
c.bench_function("create [array] 10", |b| { c.bench_function("table [create array]", |b| {
b.iter_batched( b.iter_batched(
|| collect_gc_twice(&lua), || collect_gc_twice(&lua),
|_| { |_| {
let table = lua.create_table().unwrap(); lua.create_sequence_from(1..=10).unwrap();
for i in 1..=10 {
table.set(i, i).unwrap();
}
}, },
BatchSize::SmallInput, BatchSize::SmallInput,
); );
}); });
} }
fn create_string_table(c: &mut Criterion) { fn table_create_hash(c: &mut Criterion) {
let lua = Lua::new(); let lua = Lua::new();
c.bench_function("create [table string] 10", |b| { c.bench_function("table [create hash]", |b| {
b.iter_batched( b.iter_batched(
|| collect_gc_twice(&lua), || collect_gc_twice(&lua),
|_| { |_| {
let table = lua.create_table().unwrap(); lua.create_table_from(
for &s in &["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"] { ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]
let s = lua.create_string(s).unwrap(); .into_iter()
table.set(s.clone(), s).unwrap(); .map(|s| (s, s)),
} )
.unwrap();
}, },
BatchSize::SmallInput, BatchSize::SmallInput,
); );
}); });
} }
fn create_function(c: &mut Criterion) { fn table_get_set(c: &mut Criterion) {
let lua = Lua::new(); let lua = Lua::new();
c.bench_function("create [function] 10", |b| { c.bench_function("table [get and set]", |b| {
b.iter_batched( b.iter_batched(
|| collect_gc_twice(&lua),
|_| {
for i in 0..10 {
lua.create_function(move |_, ()| Ok(i)).unwrap();
}
},
BatchSize::SmallInput,
);
});
}
fn call_lua_function(c: &mut Criterion) {
let lua = Lua::new();
c.bench_function("call Lua function [sum] 3 10", |b| {
b.iter_batched_ref(
|| { || {
collect_gc_twice(&lua); collect_gc_twice(&lua);
lua.load("function(a, b, c) return a + b + c end") lua.create_table().unwrap()
.eval::<LuaFunction>()
.unwrap()
}, },
|function| { |table| {
for i in 0..10 { for (i, s) in ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]
let _result: i64 = function.call((i, i + 1, i + 2)).unwrap(); .into_iter()
.enumerate()
{
table.raw_set(s, i).unwrap();
assert_eq!(table.raw_get::<_, usize>(s).unwrap(), i);
} }
}, },
BatchSize::SmallInput, BatchSize::SmallInput,
@@ -96,197 +82,318 @@ fn call_lua_function(c: &mut Criterion) {
}); });
} }
fn call_sum_callback(c: &mut Criterion) { fn table_traversal_pairs(c: &mut Criterion) {
let lua = Lua::new(); let lua = Lua::new();
let callback = lua
.create_function(|_, (a, b, c): (i64, i64, i64)| Ok(a + b + c)) 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 function_create(c: &mut Criterion) {
let lua = Lua::new();
c.bench_function("function [create Rust]", |b| {
b.iter_batched(
|| collect_gc_twice(&lua),
|_| {
lua.create_function(|_, ()| Ok(123)).unwrap();
},
BatchSize::SmallInput,
);
});
}
fn function_call_sum(c: &mut Criterion) {
let lua = Lua::new();
let sum = lua
.create_function(|_, (a, b, c): (i64, i64, i64)| Ok(a + b - c))
.unwrap(); .unwrap();
lua.globals().set("callback", callback).unwrap();
c.bench_function("call Rust callback [sum] 3 10", |b| { c.bench_function("function [call Rust sum]", |b| {
b.iter_batched_ref( b.iter_batched(
|| { || collect_gc_twice(&lua),
collect_gc_twice(&lua); |_| {
lua.load("function() for i = 1,10 do callback(i, i+1, i+2) end end") assert_eq!(sum.call::<_, i64>((10, 20, 30)).unwrap(), 0);
.eval::<LuaFunction>()
.unwrap()
},
|function| {
function.call::<_, ()>(()).unwrap();
}, },
BatchSize::SmallInput, BatchSize::SmallInput,
); );
}); });
} }
fn call_async_sum_callback(c: &mut Criterion) { fn function_call_lua_sum(c: &mut Criterion) {
let options = LuaOptions::new().thread_cache_size(1024);
let lua = Lua::new_with(LuaStdLib::ALL_SAFE, options).unwrap();
let callback = lua
.create_async_function(|_, (a, b, c): (i64, i64, i64)| async move {
task::yield_now().await;
Ok(a + b + c)
})
.unwrap();
lua.globals().set("callback", callback).unwrap();
c.bench_function("call async Rust callback [sum] 3 10", |b| {
let rt = Runtime::new().unwrap();
b.to_async(rt).iter_batched(
|| {
collect_gc_twice(&lua);
lua.load("function() for i = 1,10 do callback(i, i+1, i+2) end end")
.eval::<LuaFunction>()
.unwrap()
},
|function| async move {
function.call_async::<_, ()>(()).await.unwrap();
},
BatchSize::SmallInput,
);
});
}
fn call_concat_callback(c: &mut Criterion) {
let lua = Lua::new(); let lua = Lua::new();
let callback = lua
let sum = lua
.load("function(a, b, c) return a + b - c end")
.eval::<LuaFunction>()
.unwrap();
c.bench_function("function [call Lua sum]", |b| {
b.iter_batched(
|| collect_gc_twice(&lua),
|_| {
assert_eq!(sum.call::<_, i64>((10, 20, 30)).unwrap(), 0);
},
BatchSize::SmallInput,
);
});
}
fn function_call_concat(c: &mut Criterion) {
let lua = Lua::new();
let concat = lua
.create_function(|_, (a, b): (LuaString, LuaString)| { .create_function(|_, (a, b): (LuaString, LuaString)| {
Ok(format!("{}{}", a.to_str()?, b.to_str()?)) Ok(format!("{}{}", a.to_str()?, b.to_str()?))
}) })
.unwrap(); .unwrap();
lua.globals().set("callback", callback).unwrap(); let i = AtomicUsize::new(0);
c.bench_function("call Rust callback [concat string] 10", |b| { c.bench_function("function [call Rust concat string]", |b| {
b.iter_batched_ref( b.iter_batched(
|| { || {
collect_gc_twice(&lua); collect_gc_twice(&lua);
lua.load("function() for i = 1,10 do callback('a', tostring(i)) end end") i.fetch_add(1, Ordering::Relaxed)
.eval::<LuaFunction>()
.unwrap()
}, },
|function| { |i| {
function.call::<_, ()>(()).unwrap(); assert_eq!(
concat.call::<_, LuaString>(("num:", i)).unwrap(),
format!("num:{i}")
);
}, },
BatchSize::SmallInput, BatchSize::SmallInput,
); );
}); });
} }
fn create_registry_values(c: &mut Criterion) { fn function_call_lua_concat(c: &mut Criterion) {
let lua = Lua::new(); let lua = Lua::new();
c.bench_function("create [registry value] 10", |b| { let concat = lua
.load("function(a, b) return a..b end")
.eval::<LuaFunction>()
.unwrap();
let i = AtomicUsize::new(0);
c.bench_function("function [call Lua concat string]", |b| {
b.iter_batched(
|| {
collect_gc_twice(&lua);
i.fetch_add(1, Ordering::Relaxed)
},
|i| {
assert_eq!(
concat.call::<_, LuaString>(("num:", i)).unwrap(),
format!("num:{i}")
);
},
BatchSize::SmallInput,
);
});
}
fn function_async_call_sum(c: &mut Criterion) {
let options = LuaOptions::new().thread_pool_size(1024);
let lua = Lua::new_with(LuaStdLib::ALL_SAFE, options).unwrap();
let sum = lua
.create_async_function(|_, (a, b, c): (i64, i64, i64)| async move {
task::yield_now().await;
Ok(a + b - c)
})
.unwrap();
c.bench_function("function [async call Rust sum]", |b| {
let rt = Runtime::new().unwrap();
b.to_async(rt).iter_batched(
|| collect_gc_twice(&lua),
|_| async {
assert_eq!(sum.call_async::<_, i64>((10, 20, 30)).await.unwrap(), 0);
},
BatchSize::SmallInput,
);
});
}
fn registry_value_create(c: &mut Criterion) {
let lua = Lua::new();
lua.gc_stop();
c.bench_function("registry value [create]", |b| {
b.iter_batched(
|| collect_gc_twice(&lua),
|_| lua.create_registry_value("hello").unwrap(),
BatchSize::SmallInput,
);
});
}
fn registry_value_get(c: &mut Criterion) {
let lua = Lua::new();
lua.gc_stop();
let value = lua.create_registry_value("hello").unwrap();
c.bench_function("registry value [get]", |b| {
b.iter_batched( b.iter_batched(
|| collect_gc_twice(&lua), || collect_gc_twice(&lua),
|_| { |_| {
for _ in 0..10 { assert_eq!(lua.registry_value::<LuaString>(&value).unwrap(), "hello");
lua.create_registry_value(lua.pack(true).unwrap()).unwrap();
}
lua.expire_registry_values();
}, },
BatchSize::SmallInput, BatchSize::SmallInput,
); );
}); });
} }
fn create_userdata(c: &mut Criterion) { fn userdata_create(c: &mut Criterion) {
struct UserData(i64); struct UserData(#[allow(unused)] i64);
impl LuaUserData for UserData {} impl LuaUserData for UserData {}
let lua = Lua::new(); let lua = Lua::new();
c.bench_function("create [table userdata] 10", |b| { c.bench_function("userdata [create]", |b| {
b.iter_batched( b.iter_batched(
|| collect_gc_twice(&lua), || collect_gc_twice(&lua),
|_| { |_| {
let table: LuaTable = lua.create_table().unwrap(); lua.create_userdata(UserData(123)).unwrap();
for i in 1..11 {
table.set(i, UserData(i)).unwrap();
}
}, },
BatchSize::SmallInput, BatchSize::SmallInput,
); );
}); });
} }
fn call_userdata_index(c: &mut Criterion) { fn userdata_call_index(c: &mut Criterion) {
struct UserData(i64); struct UserData(#[allow(unused)] i64);
impl LuaUserData for UserData { impl LuaUserData for UserData {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_method(LuaMetaMethod::Index, move |_, _, index: String| Ok(index)); methods.add_meta_method(LuaMetaMethod::Index, move |_, _, key: LuaString| Ok(key));
} }
} }
let lua = Lua::new(); let lua = Lua::new();
lua.globals().set("userdata", UserData(10)).unwrap(); let ud = lua.create_userdata(UserData(123)).unwrap();
let index = lua
.load("function(ud) return ud.test end")
.eval::<LuaFunction>()
.unwrap();
c.bench_function("call [userdata index] 10", |b| { c.bench_function("userdata [call index]", |b| {
b.iter_batched_ref( b.iter_batched(
|| { || collect_gc_twice(&lua),
collect_gc_twice(&lua); |_| {
lua.load("function() for i = 1,10 do local v = userdata.test end end") assert_eq!(index.call::<_, LuaString>(&ud).unwrap(), "test");
.eval::<LuaFunction>()
.unwrap()
},
|function| {
function.call::<_, ()>(()).unwrap();
}, },
BatchSize::SmallInput, BatchSize::SmallInput,
); );
}); });
} }
fn call_userdata_method(c: &mut Criterion) { fn userdata_call_method(c: &mut Criterion) {
struct UserData(i64); struct UserData(i64);
impl LuaUserData for UserData { impl LuaUserData for UserData {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_method("method", |_, this, ()| Ok(this.0)); methods.add_method("add", |_, this, i: i64| Ok(this.0 + i));
} }
} }
let lua = Lua::new(); let lua = Lua::new();
lua.globals().set("userdata", UserData(10)).unwrap(); let ud = lua.create_userdata(UserData(123)).unwrap();
let method = lua
.load("function(ud, i) return ud:add(i) end")
.eval::<LuaFunction>()
.unwrap();
let i = AtomicUsize::new(0);
c.bench_function("call [userdata method] 10", |b| { c.bench_function("userdata [call method]", |b| {
b.iter_batched_ref( b.iter_batched(
|| { || {
collect_gc_twice(&lua); collect_gc_twice(&lua);
lua.load("function() for i = 1,10 do userdata:method() end end") i.fetch_add(1, Ordering::Relaxed)
.eval::<LuaFunction>()
.unwrap()
}, },
|function| { |i| {
function.call::<_, ()>(()).unwrap(); assert_eq!(method.call::<_, usize>((&ud, i)).unwrap(), 123 + i);
}, },
BatchSize::SmallInput, BatchSize::SmallInput,
); );
}); });
} }
fn call_async_userdata_method(c: &mut Criterion) { fn userdata_async_call_method(c: &mut Criterion) {
#[derive(Clone, Copy)]
struct UserData(i64); struct UserData(i64);
impl LuaUserData for UserData { impl LuaUserData for UserData {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { 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("add", |_, this, i: i64| async move {
task::yield_now().await;
Ok(this.0 + i)
});
} }
} }
let options = LuaOptions::new().thread_cache_size(1024); let options = LuaOptions::new().thread_pool_size(1024);
let lua = Lua::new_with(LuaStdLib::ALL_SAFE, options).unwrap(); let lua = Lua::new_with(LuaStdLib::ALL_SAFE, options).unwrap();
lua.globals().set("userdata", UserData(10)).unwrap(); let ud = lua.create_userdata(UserData(123)).unwrap();
let method = lua
.load("function(ud, i) return ud:add(i) end")
.eval::<LuaFunction>()
.unwrap();
let i = AtomicUsize::new(0);
c.bench_function("call async [userdata method] 10", |b| { c.bench_function("userdata [async call method] 10", |b| {
let rt = Runtime::new().unwrap(); let rt = Runtime::new().unwrap();
b.to_async(rt).iter_batched( b.to_async(rt).iter_batched(
|| { || {
collect_gc_twice(&lua); collect_gc_twice(&lua);
lua.load("function() for i = 1,10 do userdata:method() end end") (
.eval::<LuaFunction>() method.clone(),
.unwrap() ud.clone(),
i.fetch_add(1, Ordering::Relaxed),
)
}, },
|function| async move { |(method, ud, i)| async move {
function.call_async::<_, ()>(()).await.unwrap(); assert_eq!(
method.call_async::<_, usize>((ud, i)).await.unwrap(),
123 + i
);
}, },
BatchSize::SmallInput, BatchSize::SmallInput,
); );
@@ -296,23 +403,32 @@ fn call_async_userdata_method(c: &mut Criterion) {
criterion_group! { criterion_group! {
name = benches; name = benches;
config = Criterion::default() config = Criterion::default()
.sample_size(300) .sample_size(500)
.measurement_time(Duration::from_secs(10)) .measurement_time(Duration::from_secs(10))
.noise_threshold(0.02); .noise_threshold(0.02);
targets = targets =
create_table, table_create_empty,
create_array, table_create_array,
create_string_table, table_create_hash,
create_function, table_get_set,
call_lua_function, table_traversal_pairs,
call_sum_callback, table_traversal_for_each,
call_async_sum_callback, table_traversal_sequence,
call_concat_callback,
create_registry_values, function_create,
create_userdata, function_call_sum,
call_userdata_index, function_call_lua_sum,
call_userdata_method, function_call_concat,
call_async_userdata_method, function_call_lua_concat,
function_async_call_sum,
registry_value_create,
registry_value_get,
userdata_create,
userdata_call_index,
userdata_call_method,
userdata_async_call_method,
} }
criterion_main!(benches); criterion_main!(benches);
+90
View File
@@ -0,0 +1,90 @@
use std::time::Duration;
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
use mlua::prelude::*;
fn collect_gc_twice(lua: &Lua) {
lua.gc_collect().unwrap();
lua.gc_collect().unwrap();
}
fn encode_json(c: &mut Criterion) {
let lua = Lua::new();
let encode = lua
.create_function(|_, t: LuaValue| Ok(serde_json::to_string(&t).unwrap()))
.unwrap();
let table = lua
.load(
r#"{
name = "Clark Kent",
address = {
city = "Smallville",
state = "Kansas",
country = "USA",
},
age = 22,
parents = {"Jonathan Kent", "Martha Kent"},
superman = true,
interests = {"flying", "saving the world", "kryptonite"},
}"#,
)
.eval::<LuaTable>()
.unwrap();
c.bench_function("serialize json", |b| {
b.iter_batched(
|| collect_gc_twice(&lua),
|_| {
encode.call::<_, LuaString>(&table).unwrap();
},
BatchSize::SmallInput,
);
});
}
fn decode_json(c: &mut Criterion) {
let lua = Lua::new();
let decode = lua
.create_function(|lua, s: String| {
lua.to_value(&serde_json::from_str::<serde_json::Value>(&s).unwrap())
})
.unwrap();
let json = r#"{
"name": "Clark Kent",
"address": {
"city": "Smallville",
"state": "Kansas",
"country": "USA"
},
"age": 22,
"parents": ["Jonathan Kent", "Martha Kent"],
"superman": true,
"interests": ["flying", "saving the world", "kryptonite"]
}"#;
c.bench_function("deserialize json", |b| {
b.iter_batched(
|| collect_gc_twice(&lua),
|_| {
decode.call::<_, LuaTable>(json).unwrap();
},
BatchSize::SmallInput,
);
});
}
criterion_group! {
name = benches;
config = Criterion::default()
.sample_size(500)
.measurement_time(Duration::from_secs(10))
.noise_threshold(0.02);
targets =
encode_json,
decode_json,
}
criterion_main!(benches);
-5
View File
@@ -1,5 +0,0 @@
use std::path::PathBuf;
pub fn probe_lua() -> Option<PathBuf> {
None
}
-93
View File
@@ -1,93 +0,0 @@
#![allow(dead_code)]
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() -> 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");
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=";
};
println!("cargo:rustc-link-search=native={}", lib_dir);
println!("cargo:rustc-link-lib={}{}", link_lib, lua_lib);
}
return Some(PathBuf::from(include_dir));
}
// Find using `pkg-config`
#[cfg(feature = "lua54")]
let (incl_bound, excl_bound, alt_probe, ver) = ("5.4", "5.5", "lua5.4", "5.4");
#[cfg(feature = "lua53")]
let (incl_bound, excl_bound, alt_probe, ver) = ("5.3", "5.4", "lua5.3", "5.3");
#[cfg(feature = "lua52")]
let (incl_bound, excl_bound, alt_probe, ver) = ("5.2", "5.3", "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 {} using `pkg-config`", ver))
.include_paths
.get(0)
.cloned()
}
#[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");
lua.expect("cannot find LuaJIT using `pkg-config`")
.include_paths
.get(0)
.cloned()
}
}
-115
View File
@@ -1,115 +0,0 @@
#[cfg_attr(
any(
feature = "luau",
all(
feature = "vendored",
any(
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "lua51",
feature = "luajit"
)
)
),
path = "find_vendored.rs"
)]
#[cfg_attr(
all(
not(feature = "vendored"),
any(
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "lua51",
feature = "luajit"
)
),
path = "find_normal.rs"
)]
#[cfg_attr(
not(any(
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "lua51",
feature = "luajit",
feature = "luau"
)),
path = "find_dummy.rs"
)]
mod find;
fn main() {
#[cfg(not(any(
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "lua51",
feature = "luajit",
feature = "luau"
)))]
compile_error!(
"You must enable one of the features: lua54, lua53, lua52, lua51, luajit, luajit52, luau"
);
#[cfg(all(
feature = "lua54",
any(
feature = "lua53",
feature = "lua52",
feature = "lua51",
feature = "luajit",
feature = "luau"
)
))]
compile_error!(
"You can enable only one of the features: lua54, lua53, lua52, lua51, luajit, luajit52, luau"
);
#[cfg(all(
feature = "lua53",
any(
feature = "lua52",
feature = "lua51",
feature = "luajit",
feature = "luau"
)
))]
compile_error!(
"You can enable only one of the features: lua54, lua53, lua52, lua51, luajit, luajit52, luau"
);
#[cfg(all(
feature = "lua52",
any(feature = "lua51", feature = "luajit", feature = "luau")
))]
compile_error!(
"You can enable only one of the features: lua54, lua53, lua52, lua51, luajit, luajit52, luau"
);
#[cfg(all(feature = "lua51", any(feature = "luajit", feature = "luau")))]
compile_error!(
"You can enable only one of the features: lua54, lua53, lua52, lua51, luajit, luajit52, luau"
);
#[cfg(all(feature = "luajit", feature = "luau"))]
compile_error!(
"You can enable only one of the features: lua54, lua53, lua52, lua51, luajit, luajit52, luau"
);
// 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"))]
compile_error!("Luau does not support module mode");
#[cfg(any(not(feature = "module"), target_os = "windows"))]
find::probe_lua();
println!("cargo:rerun-if-changed=build");
}
+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/main/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.
+21 -18
View File
@@ -1,33 +1,36 @@
use std::collections::HashMap; use std::collections::HashMap;
use hyper::body::{Body as HyperBody, HttpBody as _}; use http_body_util::BodyExt as _;
use hyper::Client as HyperClient; use hyper::body::Incoming;
use hyper_util::client::legacy::Client as HyperClient;
use hyper_util::rt::TokioExecutor;
use mlua::{chunk, AnyUserData, ExternalResult, Lua, Result, UserData, UserDataMethods}; use mlua::{chunk, ExternalResult, Lua, Result, UserData, UserDataMethods};
struct BodyReader(HyperBody); struct BodyReader(Incoming);
impl UserData for BodyReader { impl UserData for BodyReader {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_async_function("read", |lua, reader: AnyUserData| async move { // Every call returns a next chunk
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 { if let Some(bytes) = reader.0.frame().await {
let bytes = bytes.to_lua_err()?; if let Some(bytes) = bytes.into_lua_err()?.data_ref() {
return Some(lua.create_string(&bytes)).transpose(); return Some(lua.create_string(&bytes)).transpose();
}
} }
Ok(None) Ok(None)
}); });
} }
} }
#[tokio::main] #[tokio::main(flavor = "current_thread")]
async fn main() -> Result<()> { async fn main() -> Result<()> {
let lua = Lua::new(); let lua = Lua::new();
let fetch_url = lua.create_async_function(|lua, uri: String| async move { let fetch_url = lua.create_async_function(|lua, uri: String| async move {
let client = HyperClient::new(); let client = HyperClient::builder(TokioExecutor::new()).build_http::<String>();
let uri = uri.parse().to_lua_err()?; let uri = uri.parse().into_lua_err()?;
let resp = client.get(uri).await.to_lua_err()?; let resp = client.get(uri).await.into_lua_err()?;
let lua_resp = lua.create_table()?; let lua_resp = lua.create_table()?;
lua_resp.set("status", resp.status().as_u16())?; lua_resp.set("status", resp.status().as_u16())?;
@@ -37,7 +40,7 @@ async fn main() -> Result<()> {
headers headers
.entry(key.as_str()) .entry(key.as_str())
.or_insert(Vec::new()) .or_insert(Vec::new())
.push(value.to_str().to_lua_err()?); .push(value.to_str().into_lua_err()?);
} }
lua_resp.set("headers", headers)?; lua_resp.set("headers", headers)?;
@@ -56,11 +59,11 @@ async fn main() -> Result<()> {
end end
end end
repeat repeat
local body = res.body:read() local chunk = res.body:read()
if body then if chunk then
print(body) print(chunk)
end end
until not body until not chunk
}) })
.into_function()?; .into_function()?;
+10 -16
View File
@@ -1,33 +1,27 @@
use mlua::{chunk, ExternalResult, Lua, LuaSerdeExt, Result}; use mlua::{chunk, ExternalResult, Lua, LuaSerdeExt, Result, Value};
#[tokio::main] #[tokio::main(flavor = "current_thread")]
async fn main() -> Result<()> { async fn main() -> Result<()> {
let lua = Lua::new(); let lua = Lua::new();
let null = lua.null();
let fetch_json = lua.create_async_function(|lua, uri: String| async move { let fetch_json = lua.create_async_function(|lua, uri: String| async move {
let resp = reqwest::get(&uri) let resp = reqwest::get(&uri)
.await .await
.and_then(|resp| resp.error_for_status()) .and_then(|resp| resp.error_for_status())
.to_lua_err()?; .into_lua_err()?;
let json = resp.json::<serde_json::Value>().await.to_lua_err()?; let json = resp.json::<serde_json::Value>().await.into_lua_err()?;
lua.to_value(&json) lua.to_value(&json)
})?; })?;
let dbg = lua.create_function(|_, value: Value| {
println!("{value:#?}");
Ok(())
})?;
let f = lua let f = lua
.load(chunk! { .load(chunk! {
function print_r(t, indent)
local indent = indent or ""
for k, v in pairs(t) do
io.write(indent, tostring(k))
if type(v) == "table" then io.write(":\n") print_r(v, indent.." ")
else io.write(": ", v == $null and "null" or tostring(v), "\n") end
end
end
local res = $fetch_json(...) local res = $fetch_json(...)
print_r(res) $dbg(res)
}) })
.into_function()?; .into_function()?;
+74 -54
View File
@@ -1,43 +1,63 @@
use std::convert::Infallible;
use std::future::Future; use std::future::Future;
use std::net::SocketAddr; use std::net::SocketAddr;
use std::pin::Pin;
use std::rc::Rc; use std::rc::Rc;
use std::task::{Context, Poll};
use hyper::server::conn::AddrStream; use futures::future::LocalBoxFuture;
use hyper::service::Service; use http_body_util::{combinators::BoxBody, BodyExt as _, Empty, Full};
use hyper::{Body, Request, Response, Server}; use hyper::body::{Bytes, Incoming};
use hyper::{Request, Response};
use hyper_util::rt::TokioIo;
use hyper_util::server::conn::auto::Builder as ServerConnBuilder;
use tokio::net::TcpListener;
use tokio::task::LocalSet;
use mlua::{ use mlua::{
chunk, Error as LuaError, Function, Lua, String as LuaString, Table, UserData, UserDataMethods, chunk, Error as LuaError, Function, Lua, RegistryKey, String as LuaString, Table, UserData,
UserDataMethods,
}; };
struct LuaRequest(SocketAddr, Request<Body>); /// Wrapper around incoming request that implements UserData
struct LuaRequest(SocketAddr, Request<Incoming>);
impl UserData for LuaRequest { impl UserData for LuaRequest {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_method("remote_addr", |_lua, req, ()| Ok((req.0).to_string())); methods.add_method("remote_addr", |_, req, ()| Ok((req.0).to_string()));
methods.add_method("method", |_lua, req, ()| Ok((req.1).method().to_string())); methods.add_method("method", |_, req, ()| Ok((req.1).method().to_string()));
methods.add_method("path", |_, req, ()| Ok(req.1.uri().path().to_string()));
} }
} }
pub struct Svc(Rc<Lua>, SocketAddr); /// Service that handles incoming requests
#[derive(Clone)]
pub struct Svc {
lua: Rc<Lua>,
handler: Rc<RegistryKey>,
peer_addr: SocketAddr,
}
impl Service<Request<Body>> for Svc { impl Svc {
type Response = Response<Body>; pub fn new(lua: Rc<Lua>, handler: Rc<RegistryKey>, peer_addr: SocketAddr) -> Self {
type Error = LuaError; Self {
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>; lua,
handler,
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { peer_addr,
Poll::Ready(Ok(())) }
} }
}
fn call(&mut self, req: Request<Body>) -> Self::Future { impl hyper::service::Service<Request<Incoming>> for Svc {
type Response = Response<BoxBody<Bytes, Infallible>>;
type Error = LuaError;
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
fn call(&self, req: Request<Incoming>) -> Self::Future {
// If handler returns an error then generate 5xx response // If handler returns an error then generate 5xx response
let lua = self.0.clone(); let lua = self.lua.clone();
let lua_req = LuaRequest(self.1, req); let handler_key = self.handler.clone();
let lua_req = LuaRequest(self.peer_addr, req);
Box::pin(async move { Box::pin(async move {
let handler: Function = lua.named_registry_value("http_handler")?; let handler: Function = lua.registry_value(&handler_key)?;
match handler.call_async::<_, Table>(lua_req).await { match handler.call_async::<_, Table>(lua_req).await {
Ok(lua_resp) => { Ok(lua_resp) => {
let status = lua_resp.get::<_, Option<u16>>("status")?.unwrap_or(200); let status = lua_resp.get::<_, Option<u16>>("status")?.unwrap_or(200);
@@ -51,10 +71,11 @@ impl Service<Request<Body>> for Svc {
} }
} }
// Set body
let body = lua_resp let body = lua_resp
.get::<_, Option<LuaString>>("body")? .get::<_, Option<LuaString>>("body")?
.map(|b| Body::from(b.as_bytes().to_vec())) .map(|b| Full::new(Bytes::copy_from_slice(b.as_bytes())).boxed())
.unwrap_or_else(Body::empty); .unwrap_or_else(|| Empty::<Bytes>::new().boxed());
Ok(resp.body(body).unwrap()) Ok(resp.body(body).unwrap())
} }
@@ -62,7 +83,7 @@ impl Service<Request<Body>> for Svc {
eprintln!("{}", err); eprintln!("{}", err);
Ok(Response::builder() Ok(Response::builder()
.status(500) .status(500)
.body(Body::from("Internal Server Error")) .body(Full::new(Bytes::from("Internal Server Error")).boxed())
.unwrap()) .unwrap())
} }
} }
@@ -75,13 +96,14 @@ async fn main() {
let lua = Rc::new(Lua::new()); let lua = Rc::new(Lua::new());
// Create Lua handler function // Create Lua handler function
let handler: Function = lua let handler: RegistryKey = lua
.load(chunk! { .load(chunk! {
function(req) function(req)
return { return {
status = 200, status = 200,
headers = { headers = {
["X-Req-Method"] = req:method(), ["X-Req-Method"] = req:method(),
["X-Req-Path"] = req:path(),
["X-Remote-Addr"] = req:remote_addr(), ["X-Remote-Addr"] = req:remote_addr(),
}, },
body = "Hello from Lua!\n" body = "Hello from Lua!\n"
@@ -89,37 +111,35 @@ async fn main() {
end end
}) })
.eval() .eval()
.expect("cannot create Lua handler"); .expect("Failed to create Lua handler");
let handler = Rc::new(handler);
// Store it in the Registry let listen_addr = "127.0.0.1:3000";
lua.set_named_registry_value("http_handler", handler) let listener = TcpListener::bind(listen_addr).await.unwrap();
.expect("cannot store Lua handler"); println!("Listening on http://{listen_addr}");
let addr = ([127, 0, 0, 1], 3000).into(); let local = LocalSet::new();
let server = Server::bind(&addr).executor(LocalExec).serve(MakeSvc(lua)); loop {
let (stream, peer_addr) = match listener.accept().await {
Ok(x) => x,
Err(err) => {
eprintln!("Failed to accept connection: {err}");
continue;
}
};
println!("Listening on http://{}", addr); let svc = Svc::new(lua.clone(), handler.clone(), peer_addr);
local
// Create `LocalSet` to spawn !Send futures .run_until(async move {
let local = tokio::task::LocalSet::new(); let result = ServerConnBuilder::new(LocalExec)
local.run_until(server).await.expect("cannot run server") .http1()
} .serve_connection(TokioIo::new(stream), svc)
.await;
struct MakeSvc(Rc<Lua>); if let Err(err) = result {
eprintln!("Error serving connection: {err:?}");
impl Service<&AddrStream> for MakeSvc { }
type Response = Svc; })
type Error = hyper::Error; .await;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, stream: &AddrStream) -> Self::Future {
let lua = self.0.clone();
let remote_addr = stream.remote_addr();
Box::pin(async move { Ok(Svc(lua, remote_addr)) })
} }
} }
@@ -128,7 +148,7 @@ struct LocalExec;
impl<F> hyper::rt::Executor<F> for LocalExec impl<F> hyper::rt::Executor<F> for LocalExec
where where
F: std::future::Future + 'static, // not requiring `Send` F: Future + 'static, // not requiring `Send`
{ {
fn execute(&self, fut: F) { fn execute(&self, fut: F) {
tokio::task::spawn_local(fut); tokio::task::spawn_local(fut);
+12 -23
View File
@@ -6,9 +6,7 @@ use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream}; use tokio::net::{TcpListener, TcpStream};
use tokio::task; use tokio::task;
use mlua::{ use mlua::{chunk, Function, Lua, RegistryKey, String as LuaString, UserData, UserDataMethods};
chunk, AnyUserData, Function, Lua, RegistryKey, String as LuaString, UserData, UserDataMethods,
};
struct LuaTcpStream(TcpStream); struct LuaTcpStream(TcpStream);
@@ -18,28 +16,19 @@ impl UserData for LuaTcpStream {
Ok(this.0.peer_addr()?.to_string()) Ok(this.0.peer_addr()?.to_string())
}); });
methods.add_async_function( methods.add_async_method_mut("read", |lua, this, size| async move {
"read", let mut buf = vec![0; size];
|lua, (this, size): (AnyUserData, usize)| async move { let n = this.0.read(&mut buf).await?;
let mut this = this.borrow_mut::<Self>()?; buf.truncate(n);
let mut buf = vec![0; size]; lua.create_string(&buf)
let n = this.0.read(&mut buf).await?; });
buf.truncate(n);
lua.create_string(&buf)
},
);
methods.add_async_function( methods.add_async_method_mut("write", |_, this, data: LuaString| async move {
"write", let n = this.0.write(&data.as_bytes()).await?;
|_, (this, data): (AnyUserData, LuaString)| async move { Ok(n)
let mut this = this.borrow_mut::<Self>()?; });
let n = this.0.write(&data.as_bytes()).await?;
Ok(n)
},
);
methods.add_async_function("close", |_, this: AnyUserData| async move { methods.add_async_method_mut("close", |_, this, ()| async move {
let mut this = this.borrow_mut::<Self>()?;
this.0.shutdown().await?; this.0.shutdown().await?;
Ok(()) Ok(())
}); });
+17 -5
View File
@@ -1,7 +1,9 @@
use std::f32; use std::f32;
use std::iter::FromIterator; use std::iter::FromIterator;
use mlua::{chunk, Function, Lua, MetaMethod, Result, UserData, UserDataMethods, Variadic}; use mlua::{
chunk, FromLua, Function, Lua, MetaMethod, Result, UserData, UserDataMethods, Value, Variadic,
};
fn main() -> Result<()> { fn main() -> Result<()> {
// You can create a new Lua state with `Lua::new()`. This loads the default Lua std library // You can create a new Lua state with `Lua::new()`. This loads the default Lua std library
@@ -22,7 +24,7 @@ fn main() -> Result<()> {
// You can load and evaluate Lua code. The returned type of `Lua::load` is a builder // You can load and evaluate Lua code. The returned type of `Lua::load` is a builder
// that allows you to change settings before running Lua code. Here, we are using it to set // that allows you to change settings before running Lua code. Here, we are using it to set
// the name of the laoded chunk to "example code", which will be used when Lua error // the name of the loaded chunk to "example code", which will be used when Lua error
// messages are printed. // messages are printed.
lua.load( lua.load(
@@ -30,7 +32,7 @@ fn main() -> Result<()> {
global = 'foo'..'bar' global = 'foo'..'bar'
"#, "#,
) )
.set_name("example code")? .set_name("example code")
.exec()?; .exec()?;
assert_eq!(globals.get::<_, String>("global")?, "foobar"); assert_eq!(globals.get::<_, String>("global")?, "foobar");
@@ -87,7 +89,7 @@ fn main() -> Result<()> {
let print: Function = globals.get("print")?; let print: Function = globals.get("print")?;
print.call::<_, ()>("hello from rust")?; print.call::<_, ()>("hello from rust")?;
// This API generally handles variadics using tuples. This is one way to call a function with // This API generally handles variadic using tuples. This is one way to call a function with
// multiple parameters: // multiple parameters:
print.call::<_, ()>(("hello", "again", "from", "rust"))?; print.call::<_, ()>(("hello", "again", "from", "rust"))?;
@@ -98,7 +100,7 @@ fn main() -> Result<()> {
["hello", "yet", "again", "from", "rust"].iter().cloned(), ["hello", "yet", "again", "from", "rust"].iter().cloned(),
))?; ))?;
// You can bind rust functions to Lua as well. Callbacks receive the Lua state inself as their // You can bind rust functions to Lua as well. Callbacks receive the Lua state itself as their
// first parameter, and the arguments given to the function as the second parameter. The type // first parameter, and the arguments given to the function as the second parameter. The type
// of the arguments can be anything that is convertible from the parameters given by Lua, in // of the arguments can be anything that is convertible from the parameters given by Lua, in
// this case, the function expects two string sequences. // this case, the function expects two string sequences.
@@ -151,6 +153,16 @@ fn main() -> Result<()> {
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
struct Vec2(f32, f32); struct Vec2(f32, f32);
// We can implement `FromLua` trait for our `Vec2` to return a copy
impl<'lua> FromLua<'lua> for Vec2 {
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<Self> {
match value {
Value::UserData(ud) => Ok(*ud.borrow::<Self>()?),
_ => unreachable!(),
}
}
}
impl UserData for Vec2 { impl UserData for Vec2 {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_method("magnitude", |_, vec, ()| { methods.add_method("magnitude", |_, vec, ()| {
+1 -1
View File
@@ -2,7 +2,7 @@
name = "rust_module" name = "rust_module"
version = "0.0.0" version = "0.0.0"
authors = ["Aleksandr Orlenko <zxteam@pm.me>"] authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
edition = "2018" edition = "2021"
[lib] [lib]
crate-type = ["cdylib"] crate-type = ["cdylib"]
+4 -4
View File
@@ -1,11 +1,11 @@
//! This example shows a simple read-evaluate-print-loop (REPL). //! This example shows a simple read-evaluate-print-loop (REPL).
use mlua::{Error, Lua, MultiValue}; use mlua::{Error, Lua, MultiValue};
use rustyline::Editor; use rustyline::DefaultEditor;
fn main() { fn main() {
let lua = Lua::new(); 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 { loop {
let mut prompt = "> "; let mut prompt = "> ";
@@ -19,12 +19,12 @@ fn main() {
match lua.load(&line).eval::<MultiValue>() { match lua.load(&line).eval::<MultiValue>() {
Ok(values) => { Ok(values) => {
editor.add_history_entry(line); editor.add_history_entry(line).unwrap();
println!( println!(
"{}", "{}",
values values
.iter() .iter()
.map(|value| format!("{:?}", value)) .map(|value| format!("{:#?}", value))
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join("\t") .join("\t")
); );
+46
View File
@@ -0,0 +1,46 @@
[package]
name = "mlua-sys"
version = "0.6.2"
authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
rust-version = "1.71"
edition = "2021"
repository = "https://github.com/khvzak/mlua"
documentation = "https://docs.rs/mlua-sys"
readme = "README.md"
categories = ["external-ffi-bindings"]
license = "MIT"
links = "lua"
build = "build/main.rs"
description = """
Low level (FFI) bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Roblox Luau
"""
[package.metadata.docs.rs]
features = ["lua54", "vendored"]
rustdoc-args = ["--cfg", "docsrs"]
[features]
lua54 = []
lua53 = []
lua52 = []
lua51 = []
luajit = []
luajit52 = ["luajit"]
luau = ["luau0-src"]
luau-codegen = ["luau"]
luau-vector4 = ["luau"]
vendored = ["lua-src", "luajit-src"]
module = []
[dependencies]
[build-dependencies]
cc = "1.0"
cfg-if = "1.0"
pkg-config = "0.3.17"
lua-src = { version = ">= 547.0.0, < 547.1.0", optional = true }
luajit-src = { version = ">= 210.5.0, < 210.6.0", optional = true }
luau0-src = { version = "0.10.0", optional = true }
[lints.rust]
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(raw_dylib)'] }
+8
View File
@@ -0,0 +1,8 @@
# mlua-sys
Low level (FFI) bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Roblox [Luau].
Intended to be consumed by the [mlua] crate.
[Luau]: https://github.com/Roblox/luau
[mlua]: https://crates.io/crates/mlua
+58
View File
@@ -0,0 +1,58 @@
#![allow(dead_code)]
use std::env;
use std::ops::Bound;
pub fn probe_lua() {
let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
if target_arch == "wasm32" && cfg!(not(feature = "vendored")) {
panic!("Please enable `vendored` feature to build for wasm32");
}
let lib_dir = env::var("LUA_LIB").unwrap_or_default();
let lua_lib = env::var("LUA_LIB_NAME").unwrap_or_default();
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");
if !lua_lib.is_empty() {
if !lib_dir.is_empty() {
println!("cargo:rustc-link-search=native={lib_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", Some("lua5.4"), "5.4");
#[cfg(feature = "lua53")]
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", Some("lua5.2"), "5.2");
#[cfg(feature = "lua51")]
let (incl_bound, excl_bound, alt_probe, ver) = ("5.1", "5.2", Some("lua5.1"), "5.1");
#[cfg(feature = "luajit")]
let (incl_bound, excl_bound, alt_probe, ver) = ("2.0.4", "2.2", None, "JIT");
#[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}"));
}
@@ -1,28 +1,29 @@
#![allow(dead_code)] #![allow(dead_code)]
use std::path::PathBuf; pub fn probe_lua() {
pub fn probe_lua() -> Option<PathBuf> {
#[cfg(feature = "lua54")] #[cfg(feature = "lua54")]
let artifacts = lua_src::Build::new().build(lua_src::Lua54); let artifacts = lua_src::Build::new().build(lua_src::Lua54);
#[cfg(feature = "lua53")] #[cfg(feature = "lua53")]
let artifacts = lua_src::Build::new().build(lua_src::Lua53); let artifacts = lua_src::Build::new().build(lua_src::Lua53);
#[cfg(feature = "lua52")] #[cfg(feature = "lua52")]
let artifacts = lua_src::Build::new().build(lua_src::Lua52); let artifacts = lua_src::Build::new().build(lua_src::Lua52);
#[cfg(feature = "lua51")] #[cfg(feature = "lua51")]
let artifacts = lua_src::Build::new().build(lua_src::Lua51); let artifacts = lua_src::Build::new().build(lua_src::Lua51);
#[cfg(feature = "luajit")] #[cfg(feature = "luajit")]
let artifacts = { let artifacts = luajit_src::Build::new()
let mut builder = luajit_src::Build::new(); .lua52compat(cfg!(feature = "luajit52"))
if cfg!(feature = "luajit52") { .build();
builder.lua52compat(true);
}
builder.build()
};
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
let artifacts = luau0_src::Build::new().build(); let artifacts = luau0_src::Build::new()
.enable_codegen(cfg!(feature = "luau-codegen"))
.set_max_cstack_size(1000000)
.set_vector_size(if cfg!(feature = "luau-vector4") { 4 } else { 3 })
.build();
artifacts.print_cargo_metadata(); artifacts.print_cargo_metadata();
Some(artifacts.include_dir().to_owned())
} }
+19
View File
@@ -0,0 +1,19 @@
cfg_if::cfg_if! {
if #[cfg(all(feature = "lua54", not(any(feature = "lua53", feature = "lua52", feature = "lua51", feature = "luajit", feature = "luau"))))] {
include!("main_inner.rs");
} else if #[cfg(all(feature = "lua53", not(any(feature = "lua54", feature = "lua52", feature = "lua51", feature = "luajit", feature = "luau"))))] {
include!("main_inner.rs");
} else if #[cfg(all(feature = "lua52", not(any(feature = "lua54", feature = "lua53", feature = "lua51", feature = "luajit", feature = "luau"))))] {
include!("main_inner.rs");
} else if #[cfg(all(feature = "lua51", not(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit", feature = "luau"))))] {
include!("main_inner.rs");
} else if #[cfg(all(feature = "luajit", not(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "lua51", feature = "luau"))))] {
include!("main_inner.rs");
} else if #[cfg(all(feature = "luau", not(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "lua51", feature = "luajit"))))] {
include!("main_inner.rs");
} else {
fn main() {
compile_error!("You can enable only one of the features: lua54, lua53, lua52, lua51, luajit, luajit52, luau");
}
}
}
+35
View File
@@ -0,0 +1,35 @@
use std::env;
cfg_if::cfg_if! {
if #[cfg(any(feature = "luau", feature = "vendored"))] {
#[path = "find_vendored.rs"]
mod find;
} else {
#[path = "find_normal.rs"]
mod find;
}
}
fn main() {
#[cfg(all(feature = "luau", feature = "module", windows))]
compile_error!("Luau does not support `module` mode on Windows");
#[cfg(all(feature = "module", feature = "vendored"))]
compile_error!("`vendored` and `module` features are mutually exclusive");
println!("cargo:rerun-if-changed=build");
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
if target_os == "windows" && cfg!(feature = "module") {
if !std::env::var("LUA_LIB_NAME").unwrap_or_default().is_empty() {
// Don't use raw-dylib linking
find::probe_lua();
return;
}
println!("cargo:rustc-cfg=raw_dylib");
}
#[cfg(not(feature = "module"))]
find::probe_lua();
}
+108
View File
@@ -0,0 +1,108 @@
//! Low level bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Roblox Luau.
#![allow(non_camel_case_types, non_snake_case, dead_code)]
#![allow(clippy::missing_safety_doc)]
#![doc(test(attr(deny(warnings))))]
#![cfg_attr(docsrs, feature(doc_cfg))]
use std::os::raw::c_int;
#[cfg(any(feature = "lua54", doc))]
pub use lua54::*;
#[cfg(any(feature = "lua53", doc))]
pub use lua53::*;
#[cfg(any(feature = "lua52", doc))]
pub use lua52::*;
#[cfg(any(feature = "lua51", feature = "luajit", doc))]
pub use lua51::*;
#[cfg(any(feature = "luau", doc))]
pub use luau::*;
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
#[doc(hidden)]
pub const LUA_MAX_UPVALUES: c_int = 255;
#[cfg(any(feature = "lua51", feature = "luajit"))]
#[doc(hidden)]
pub const LUA_MAX_UPVALUES: c_int = 60;
#[cfg(feature = "luau")]
#[doc(hidden)]
pub const LUA_MAX_UPVALUES: c_int = 200;
// I believe `luaL_traceback` < 5.4 requires this much free stack to not error.
// 5.4 uses `luaL_Buffer`
#[doc(hidden)]
pub const LUA_TRACEBACK_STACK: c_int = 11;
// Copied from https://github.com/rust-lang/rust/blob/master/library/std/src/sys/pal/common/alloc.rs
// The minimum alignment guaranteed by the architecture. This value is used to
// add fast paths for low alignment values.
#[cfg(any(
target_arch = "x86",
target_arch = "arm",
target_arch = "m68k",
target_arch = "csky",
target_arch = "mips",
target_arch = "mips32r6",
target_arch = "powerpc",
target_arch = "powerpc64",
target_arch = "sparc",
target_arch = "wasm32",
target_arch = "hexagon",
all(
target_arch = "riscv32",
not(any(target_os = "espidf", target_os = "zkvm"))
),
all(target_arch = "xtensa", not(target_os = "espidf")),
))]
#[doc(hidden)]
pub const SYS_MIN_ALIGN: usize = 8;
#[cfg(any(
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "arm64ec",
target_arch = "loongarch64",
target_arch = "mips64",
target_arch = "mips64r6",
target_arch = "s390x",
target_arch = "sparc64",
target_arch = "riscv64",
target_arch = "wasm64",
))]
#[doc(hidden)]
pub const SYS_MIN_ALIGN: usize = 16;
// The allocator on the esp-idf and zkvm platforms guarantee 4 byte alignment.
#[cfg(any(
all(target_arch = "riscv32", any(target_os = "espidf", target_os = "zkvm")),
all(target_arch = "xtensa", target_os = "espidf"),
))]
#[doc(hidden)]
pub const SYS_MIN_ALIGN: usize = 4;
#[macro_use]
mod macros;
#[cfg(any(feature = "lua54", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "lua54")))]
pub mod lua54;
#[cfg(any(feature = "lua53", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "lua53")))]
pub mod lua53;
#[cfg(any(feature = "lua52", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "lua52")))]
pub mod lua52;
#[cfg(any(feature = "lua51", feature = "luajit", doc))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua51", feature = "luajit"))))]
pub mod lua51;
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub mod luau;
@@ -2,7 +2,6 @@
//! //!
//! Based on github.com/keplerproject/lua-compat-5.3 //! Based on github.com/keplerproject/lua-compat-5.3
use std::convert::TryInto;
use std::mem; use std::mem;
use std::os::raw::{c_char, c_int, c_void}; use std::os::raw::{c_char, c_int, c_void};
use std::ptr; use std::ptr;
@@ -486,10 +485,10 @@ pub unsafe fn luaL_traceback(
lua_concat(L, lua_gettop(L) - top); lua_concat(L, lua_gettop(L) - top);
} }
pub unsafe fn luaL_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) -> *const c_char { pub unsafe fn luaL_tolstring(L: *mut lua_State, mut idx: c_int, len: *mut usize) -> *const c_char {
idx = lua_absindex(L, idx);
if luaL_callmeta(L, idx, cstr!("__tostring")) == 0 { if luaL_callmeta(L, idx, cstr!("__tostring")) == 0 {
let t = lua_type(L, idx); match lua_type(L, idx) {
match t {
LUA_TNIL => { LUA_TNIL => {
lua_pushliteral(L, "nil"); lua_pushliteral(L, "nil");
} }
@@ -503,7 +502,7 @@ pub unsafe fn luaL_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) ->
lua_pushliteral(L, "true"); lua_pushliteral(L, "true");
} }
} }
_ => { t => {
let tt = luaL_getmetafield(L, idx, cstr!("__name")); let tt = luaL_getmetafield(L, idx, cstr!("__name"));
let name = if tt == LUA_TSTRING { let name = if tt == LUA_TSTRING {
lua_tostring(L, -1) lua_tostring(L, -1)
@@ -512,7 +511,7 @@ pub unsafe fn luaL_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) ->
}; };
lua_pushfstring(L, cstr!("%s: %p"), name, lua_topointer(L, idx)); lua_pushfstring(L, cstr!("%s: %p"), name, lua_topointer(L, idx));
if tt != LUA_TNIL { if tt != LUA_TNIL {
lua_replace(L, -2); lua_replace(L, -2); // remove '__name'
} }
} }
}; };
@@ -14,7 +14,8 @@ pub struct luaL_Reg {
pub func: lua_CFunction, 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); pub fn luaL_register(L: *mut lua_State, libname: *const c_char, l: *const luaL_Reg);
#[link_name = "luaL_getmetafield"] #[link_name = "luaL_getmetafield"]
pub fn luaL_getmetafield_(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int; 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_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_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( pub fn luaL_checkoption(
L: *mut lua_State, L: *mut lua_State,
@@ -56,7 +57,8 @@ extern "C" {
pub const LUA_NOREF: c_int = -2; pub const LUA_NOREF: c_int = -2;
pub const LUA_REFNIL: c_int = -1; 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_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); pub fn luaL_unref(L: *mut lua_State, t: c_int, r#ref: c_int);
@@ -73,23 +73,23 @@ pub type lua_Integer = i32;
pub type lua_Integer = i64; pub type lua_Integer = i64;
/// Type for native C functions that can be passed to Lua. /// 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 // Type for functions that read/write blocks when loading/dumping Lua chunks
#[rustfmt::skip]
pub type lua_Reader = 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 = 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 /// Type for memory-allocation functions
pub type lua_Alloc = unsafe extern "C" fn( #[rustfmt::skip]
ud: *mut c_void, pub type lua_Alloc =
ptr: *mut c_void, unsafe extern "C-unwind" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *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 // State manipulation
// //
@@ -97,9 +97,6 @@ extern "C" {
pub fn lua_close(L: *mut lua_State); pub fn lua_close(L: *mut lua_State);
pub fn lua_newthread(L: *mut lua_State) -> *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; 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_GCSETPAUSE: c_int = 6;
pub const LUA_GCSETSTEPMUL: c_int = 7; 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; pub fn lua_gc(L: *mut lua_State, what: c_int, data: c_int) -> c_int;
} }
// //
// Miscellaneous functions // Miscellaneous functions
// //
extern "C" { #[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))]
pub fn lua_error(L: *mut lua_State) -> !; 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_next(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_concat(L: *mut lua_State, n: 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_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); 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) // Some useful macros (implemented as Rust functions)
// //
@@ -319,6 +328,14 @@ pub unsafe fn lua_getglobal_(L: *mut lua_State, var: *const c_char) {
lua_getfield_(L, LUA_GLOBALSINDEX, var) lua_getfield_(L, LUA_GLOBALSINDEX, var)
} }
#[inline(always)]
pub unsafe fn lua_tolightuserdata(L: *mut lua_State, idx: c_int) -> *mut c_void {
if lua_islightuserdata(L, idx) != 0 {
return lua_touserdata(L, idx);
}
ptr::null_mut()
}
#[inline(always)] #[inline(always)]
pub unsafe fn lua_tostring(L: *mut lua_State, i: c_int) -> *const c_char { pub unsafe fn lua_tostring(L: *mut lua_State, i: c_int) -> *const c_char {
lua_tolstring(L, i, ptr::null_mut()) lua_tolstring(L, i, ptr::null_mut())
@@ -351,9 +368,10 @@ pub const LUA_MASKLINE: c_int = 1 << (LUA_HOOKLINE as usize);
pub const LUA_MASKCOUNT: c_int = 1 << (LUA_HOOKCOUNT as usize); pub const LUA_MASKCOUNT: c_int = 1 << (LUA_HOOKCOUNT as usize);
/// Type for functions to be called on debug events. /// 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_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_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; pub fn lua_getlocal(L: *mut lua_State, ar: *const lua_Debug, n: c_int) -> *const c_char;
@@ -20,7 +20,8 @@ pub const LUA_JITLIBNAME: &str = "jit";
#[cfg(feature = "luajit")] #[cfg(feature = "luajit")]
pub const LUA_FFILIBNAME: &str = "ffi"; 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_base(L: *mut lua_State) -> c_int;
pub fn luaopen_table(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; pub fn luaopen_io(L: *mut lua_State) -> c_int;
@@ -2,7 +2,6 @@
//! //!
//! Based on github.com/keplerproject/lua-compat-5.3 //! Based on github.com/keplerproject/lua-compat-5.3
use std::convert::TryInto;
use std::os::raw::{c_char, c_int, c_void}; use std::os::raw::{c_char, c_int, c_void};
use std::ptr; use std::ptr;
@@ -205,10 +204,10 @@ pub unsafe fn luaL_newmetatable(L: *mut lua_State, tname: *const c_char) -> c_in
} }
} }
pub unsafe fn luaL_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) -> *const c_char { pub unsafe fn luaL_tolstring(L: *mut lua_State, mut idx: c_int, len: *mut usize) -> *const c_char {
idx = lua_absindex(L, idx);
if luaL_callmeta(L, idx, cstr!("__tostring")) == 0 { if luaL_callmeta(L, idx, cstr!("__tostring")) == 0 {
let t = lua_type(L, idx); match lua_type(L, idx) {
match t {
LUA_TNIL => { LUA_TNIL => {
lua_pushliteral(L, "nil"); lua_pushliteral(L, "nil");
} }
@@ -222,7 +221,7 @@ pub unsafe fn luaL_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) ->
lua_pushliteral(L, "true"); lua_pushliteral(L, "true");
} }
} }
_ => { t => {
let tt = luaL_getmetafield(L, idx, cstr!("__name")); let tt = luaL_getmetafield(L, idx, cstr!("__name"));
let name = if tt == LUA_TSTRING { let name = if tt == LUA_TSTRING {
lua_tostring(L, -1) lua_tostring(L, -1)
@@ -231,7 +230,7 @@ pub unsafe fn luaL_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) ->
}; };
lua_pushfstring(L, cstr!("%s: %p"), name, lua_topointer(L, idx)); lua_pushfstring(L, cstr!("%s: %p"), name, lua_topointer(L, idx));
if tt != LUA_TNIL { if tt != LUA_TNIL {
lua_replace(L, -2); lua_replace(L, -2); // remove '__name'
} }
} }
}; };
@@ -14,7 +14,8 @@ pub struct luaL_Reg {
pub func: lua_CFunction, 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); pub fn luaL_checkversion_(L: *mut lua_State, ver: lua_Number);
#[link_name = "luaL_getmetafield"] #[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_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_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( pub fn luaL_checkoption(
L: *mut lua_State, L: *mut lua_State,
@@ -65,7 +66,8 @@ extern "C" {
pub const LUA_NOREF: c_int = -2; pub const LUA_NOREF: c_int = -2;
pub const LUA_REFNIL: c_int = -1; 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_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); 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()) 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( pub fn luaL_loadbufferx(
L: *mut lua_State, L: *mut lua_State,
buff: *const c_char, buff: *const c_char,
@@ -78,23 +78,23 @@ pub type lua_Integer = i64;
pub type lua_Unsigned = c_uint; pub type lua_Unsigned = c_uint;
/// Type for native C functions that can be passed to Lua /// 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 // Type for functions that read/write blocks when loading/dumping Lua chunks
#[rustfmt::skip]
pub type lua_Reader = 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 = 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 /// Type for memory-allocation functions
pub type lua_Alloc = unsafe extern "C" fn( #[rustfmt::skip]
ud: *mut c_void, pub type lua_Alloc =
ptr: *mut c_void, unsafe extern "C-unwind" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *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 // State manipulation
// //
@@ -155,20 +155,19 @@ pub const LUA_OPMOD: c_int = 4;
pub const LUA_OPPOW: c_int = 5; pub const LUA_OPPOW: c_int = 5;
pub const LUA_OPUNM: c_int = 6; 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_OPEQ: c_int = 0;
pub const LUA_OPLT: c_int = 1; pub const LUA_OPLT: c_int = 1;
pub const LUA_OPLE: c_int = 2; 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_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; 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) // 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) 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 // Coroutine functions
// //
@@ -298,15 +298,18 @@ pub const LUA_GCISRUNNING: c_int = 9;
pub const LUA_GCGEN: c_int = 10; pub const LUA_GCGEN: c_int = 10;
pub const LUA_GCINC: c_int = 11; 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; 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 // 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_next(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_concat(L: *mut lua_State, n: c_int); pub fn lua_concat(L: *mut lua_State, n: c_int);
pub fn lua_len(L: *mut lua_State, idx: 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); 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) // Some useful macros (implemented as Rust functions)
// //
@@ -405,6 +417,14 @@ pub unsafe fn lua_pushglobaltable(L: *mut lua_State) {
lua_rawgeti_(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS as _) lua_rawgeti_(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS as _)
} }
#[inline(always)]
pub unsafe fn lua_tolightuserdata(L: *mut lua_State, idx: c_int) -> *mut c_void {
if lua_islightuserdata(L, idx) != 0 {
return lua_touserdata(L, idx);
}
ptr::null_mut()
}
#[inline(always)] #[inline(always)]
pub unsafe fn lua_tostring(L: *mut lua_State, i: c_int) -> *const c_char { pub unsafe fn lua_tostring(L: *mut lua_State, i: c_int) -> *const c_char {
lua_tolstring(L, i, ptr::null_mut()) lua_tolstring(L, i, ptr::null_mut())
@@ -437,9 +457,10 @@ pub const LUA_MASKLINE: c_int = 1 << (LUA_HOOKLINE as usize);
pub const LUA_MASKCOUNT: c_int = 1 << (LUA_HOOKCOUNT as usize); pub const LUA_MASKCOUNT: c_int = 1 << (LUA_HOOKCOUNT as usize);
/// Type for functions to be called on debug events. /// 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_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_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; pub fn lua_getlocal(L: *mut lua_State, ar: *const lua_Debug, n: c_int) -> *const c_char;
@@ -450,7 +471,12 @@ extern "C" {
pub fn lua_upvalueid(L: *mut lua_State, fidx: c_int, n: c_int) -> *mut c_void; 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_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_gethook(L: *mut lua_State) -> Option<lua_Hook>;
pub fn lua_gethookmask(L: *mut lua_State) -> c_int; pub fn lua_gethookmask(L: *mut lua_State) -> c_int;
pub fn lua_gethookcount(L: *mut lua_State) -> c_int; pub fn lua_gethookcount(L: *mut lua_State) -> c_int;
@@ -14,7 +14,8 @@ pub const LUA_MATHLIBNAME: &str = "math";
pub const LUA_DBLIBNAME: &str = "debug"; pub const LUA_DBLIBNAME: &str = "debug";
pub const LUA_LOADLIBNAME: &str = "package"; 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_base(L: *mut lua_State) -> c_int;
pub fn luaopen_coroutine(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_table(L: *mut lua_State) -> c_int;
@@ -1,4 +1,4 @@
//! MLua compatibility layer for Lua 5.2 //! MLua compatibility layer for Lua 5.3
use std::os::raw::c_int; use std::os::raw::c_int;
@@ -20,12 +20,14 @@ pub struct luaL_Reg {
pub func: lua_CFunction, 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_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; pub fn luaL_getmetafield(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int;
pub fn luaL_callmeta(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int; pub fn luaL_callmeta(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int;
pub fn luaL_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) -> *const c_char; #[link_name = "luaL_tolstring"]
pub fn luaL_tolstring_(L: *mut lua_State, idx: c_int, len: *mut usize) -> *const c_char;
pub fn luaL_argerror(L: *mut lua_State, arg: c_int, extramsg: *const c_char) -> c_int; pub fn luaL_argerror(L: *mut lua_State, arg: c_int, extramsg: *const c_char) -> c_int;
pub fn luaL_checklstring(L: *mut lua_State, arg: c_int, l: *mut usize) -> *const c_char; pub fn luaL_checklstring(L: *mut lua_State, arg: c_int, l: *mut usize) -> *const c_char;
pub fn luaL_optlstring( pub fn luaL_optlstring(
@@ -49,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_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_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( pub fn luaL_checkoption(
L: *mut lua_State, L: *mut lua_State,
@@ -66,7 +68,8 @@ extern "C" {
pub const LUA_NOREF: c_int = -2; pub const LUA_NOREF: c_int = -2;
pub const LUA_REFNIL: c_int = -1; 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_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); pub fn luaL_unref(L: *mut lua_State, t: c_int, r#ref: c_int);
@@ -79,7 +82,8 @@ pub unsafe fn luaL_loadfile(L: *mut lua_State, f: *const c_char) -> c_int {
luaL_loadfilex(L, f, ptr::null()) 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( pub fn luaL_loadbufferx(
L: *mut lua_State, L: *mut lua_State,
buff: *const c_char, buff: *const c_char,
@@ -167,6 +171,11 @@ pub unsafe fn luaL_getmetatable(L: *mut lua_State, n: *const c_char) {
lua::lua_getfield(L, lua::LUA_REGISTRYINDEX, n); lua::lua_getfield(L, lua::LUA_REGISTRYINDEX, n);
} }
#[inline(always)]
pub unsafe fn luaL_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) -> *const c_char {
luaL_tolstring_(L, lua::lua_absindex(L, idx), len)
}
// luaL_opt would be implemented here but it is undocumented, so it's omitted // luaL_opt would be implemented here but it is undocumented, so it's omitted
#[inline(always)] #[inline(always)]
@@ -82,27 +82,27 @@ pub type lua_Unsigned = u64;
pub type lua_KContext = isize; pub type lua_KContext = isize;
/// Type for native C functions that can be passed to Lua /// 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 /// Type for continuation functions
pub type lua_KFunction = 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 // Type for functions that read/write blocks when loading/dumping Lua chunks
#[rustfmt::skip]
pub type lua_Reader = 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 = 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 /// Type for memory-allocation functions
pub type lua_Alloc = unsafe extern "C" fn( #[rustfmt::skip]
ud: *mut c_void, pub type lua_Alloc =
ptr: *mut c_void, unsafe extern "C-unwind" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *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 // State manipulation
// //
@@ -167,20 +167,19 @@ pub const LUA_OPSHR: c_int = 11;
pub const LUA_OPUNM: c_int = 12; pub const LUA_OPUNM: c_int = 12;
pub const LUA_OPBNOT: c_int = 13; 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_OPEQ: c_int = 0;
pub const LUA_OPLT: c_int = 1; pub const LUA_OPLT: c_int = 1;
pub const LUA_OPLE: c_int = 2; 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_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; 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) // 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) 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 // Coroutine functions
// //
@@ -304,15 +304,18 @@ pub const LUA_GCSETPAUSE: c_int = 6;
pub const LUA_GCSETSTEPMUL: c_int = 7; pub const LUA_GCSETSTEPMUL: c_int = 7;
pub const LUA_GCISRUNNING: c_int = 9; 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; 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 // 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_next(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_concat(L: *mut lua_State, n: c_int); pub fn lua_concat(L: *mut lua_State, n: c_int);
pub fn lua_len(L: *mut lua_State, idx: 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); 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) // Some useful macros (implemented as Rust functions)
// //
@@ -412,6 +424,14 @@ pub unsafe fn lua_pushglobaltable(L: *mut lua_State) -> c_int {
lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS) lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS)
} }
#[inline(always)]
pub unsafe fn lua_tolightuserdata(L: *mut lua_State, idx: c_int) -> *mut c_void {
if lua_islightuserdata(L, idx) != 0 {
return lua_touserdata(L, idx);
}
ptr::null_mut()
}
#[inline(always)] #[inline(always)]
pub unsafe fn lua_tostring(L: *mut lua_State, i: c_int) -> *const c_char { pub unsafe fn lua_tostring(L: *mut lua_State, i: c_int) -> *const c_char {
lua_tolstring(L, i, ptr::null_mut()) lua_tolstring(L, i, ptr::null_mut())
@@ -461,9 +481,10 @@ pub const LUA_MASKLINE: c_int = 1 << (LUA_HOOKLINE as usize);
pub const LUA_MASKCOUNT: c_int = 1 << (LUA_HOOKCOUNT as usize); pub const LUA_MASKCOUNT: c_int = 1 << (LUA_HOOKCOUNT as usize);
/// Type for functions to be called on debug events. /// 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_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_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; pub fn lua_getlocal(L: *mut lua_State, ar: *const lua_Debug, n: c_int) -> *const c_char;
@@ -15,7 +15,8 @@ pub const LUA_MATHLIBNAME: &str = "math";
pub const LUA_DBLIBNAME: &str = "debug"; pub const LUA_DBLIBNAME: &str = "debug";
pub const LUA_LOADLIBNAME: &str = "package"; 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_base(L: *mut lua_State) -> c_int;
pub fn luaopen_coroutine(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_table(L: *mut lua_State) -> c_int;
@@ -20,7 +20,8 @@ pub struct luaL_Reg {
pub func: lua_CFunction, 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_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; 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_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_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( pub fn luaL_checkoption(
L: *mut lua_State, L: *mut lua_State,
@@ -66,7 +67,8 @@ extern "C" {
pub const LUA_NOREF: c_int = -2; pub const LUA_NOREF: c_int = -2;
pub const LUA_REFNIL: c_int = -1; 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_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); 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()) 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( pub fn luaL_loadbufferx(
L: *mut lua_State, L: *mut lua_State,
buff: *const c_char, buff: *const c_char,
@@ -81,38 +81,41 @@ pub type lua_Unsigned = u64;
pub type lua_KContext = isize; pub type lua_KContext = isize;
/// Type for native C functions that can be passed to Lua /// 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 /// Type for continuation functions
pub type lua_KFunction = 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 // Type for functions that read/write blocks when loading/dumping Lua chunks
#[rustfmt::skip]
pub type lua_Reader = 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 = 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 /// Type for memory-allocation functions
pub type lua_Alloc = unsafe extern "C" fn( #[rustfmt::skip]
ud: *mut c_void, pub type lua_Alloc =
ptr: *mut c_void, unsafe extern "C-unwind" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void;
osize: usize,
nsize: usize,
) -> *mut c_void;
/// Type for warning functions /// Type for warning functions
pub type lua_WarnFunction = 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 // State manipulation
// //
pub fn lua_newstate(f: lua_Alloc, ud: *mut c_void) -> *mut lua_State; pub fn lua_newstate(f: lua_Alloc, ud: *mut c_void) -> *mut lua_State;
pub fn lua_close(L: *mut lua_State); pub fn lua_close(L: *mut lua_State);
pub fn lua_newthread(L: *mut lua_State) -> *mut lua_State; pub fn lua_newthread(L: *mut lua_State) -> *mut lua_State;
// Deprecated in Lua 5.4.6
pub fn lua_resetthread(L: *mut lua_State) -> c_int; pub fn lua_resetthread(L: *mut lua_State) -> c_int;
#[cfg(feature = "vendored")]
pub fn lua_closethread(L: *mut lua_State, from: *mut lua_State) -> c_int;
pub fn lua_atpanic(L: *mut lua_State, panicf: lua_CFunction) -> lua_CFunction; pub fn lua_atpanic(L: *mut lua_State, panicf: lua_CFunction) -> lua_CFunction;
@@ -146,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_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_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_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_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_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_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; 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 // Comparison and arithmetic functions
// //
@@ -171,20 +182,19 @@ pub const LUA_OPSHR: c_int = 11;
pub const LUA_OPUNM: c_int = 12; pub const LUA_OPUNM: c_int = 12;
pub const LUA_OPBNOT: c_int = 13; 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_OPEQ: c_int = 0;
pub const LUA_OPLT: c_int = 1; pub const LUA_OPLT: c_int = 1;
pub const LUA_OPLE: c_int = 2; 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_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; 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) // Push functions (C -> stack)
// //
@@ -274,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) 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 // Coroutine functions
// //
@@ -302,7 +313,8 @@ pub unsafe fn lua_yield(L: *mut lua_State, n: c_int) -> c_int {
// //
// Warning-related functions // 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_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); pub fn lua_warning(L: *mut lua_State, msg: *const c_char, tocont: c_int);
} }
@@ -322,15 +334,18 @@ pub const LUA_GCISRUNNING: c_int = 9;
pub const LUA_GCGEN: c_int = 10; pub const LUA_GCGEN: c_int = 10;
pub const LUA_GCINC: c_int = 11; 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; 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 // 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_next(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_concat(L: *mut lua_State, n: c_int); pub fn lua_concat(L: *mut lua_State, n: c_int);
pub fn lua_len(L: *mut lua_State, idx: c_int); pub fn lua_len(L: *mut lua_State, idx: c_int);
@@ -342,6 +357,15 @@ extern "C" {
pub fn lua_closeslot(L: *mut lua_State, idx: c_int); 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) // Some useful macros (implemented as Rust functions)
// //
@@ -433,6 +457,14 @@ pub unsafe fn lua_pushglobaltable(L: *mut lua_State) -> c_int {
lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS) lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS)
} }
#[inline(always)]
pub unsafe fn lua_tolightuserdata(L: *mut lua_State, idx: c_int) -> *mut c_void {
if lua_islightuserdata(L, idx) != 0 {
return lua_touserdata(L, idx);
}
ptr::null_mut()
}
#[inline(always)] #[inline(always)]
pub unsafe fn lua_tostring(L: *mut lua_State, i: c_int) -> *const c_char { pub unsafe fn lua_tostring(L: *mut lua_State, i: c_int) -> *const c_char {
lua_tolstring(L, i, ptr::null_mut()) lua_tolstring(L, i, ptr::null_mut())
@@ -497,9 +529,10 @@ pub const LUA_MASKLINE: c_int = 1 << (LUA_HOOKLINE as usize);
pub const LUA_MASKCOUNT: c_int = 1 << (LUA_HOOKCOUNT as usize); pub const LUA_MASKCOUNT: c_int = 1 << (LUA_HOOKCOUNT as usize);
/// Type for functions to be called on debug events. /// 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_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_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; pub fn lua_getlocal(L: *mut lua_State, ar: *const lua_Debug, n: c_int) -> *const c_char;
@@ -14,7 +14,8 @@ pub const LUA_MATHLIBNAME: &str = "math";
pub const LUA_DBLIBNAME: &str = "debug"; pub const LUA_DBLIBNAME: &str = "debug";
pub const LUA_LOADLIBNAME: &str = "package"; 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_base(L: *mut lua_State) -> c_int;
pub fn luaopen_coroutine(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_table(L: *mut lua_State) -> c_int;
@@ -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 { pub unsafe fn luaL_newmetatable(L: *mut lua_State, tname: *const c_char) -> c_int {
if luaL_newmetatable_(L, tname) != 0 { if luaL_newmetatable_(L, tname) != 0 {
lua_pushstring(L, tname); lua_pushstring(L, tname);
lua_setfield(L, -2, cstr!("__name")); lua_setfield(L, -2, cstr!("__type"));
1 1
} else { } else {
0 0
@@ -341,7 +341,7 @@ pub unsafe fn luaL_loadbufferx(
fn free(p: *mut c_void); fn free(p: *mut c_void);
} }
let chunk_is_text = (*data as u8) >= b'\n'; let chunk_is_text = size == 0 || (*data as u8) >= b'\t';
if !mode.is_null() { if !mode.is_null() {
let modeb = CStr::from_ptr(mode).to_bytes(); let modeb = CStr::from_ptr(mode).to_bytes();
if !chunk_is_text && !modeb.contains(&b'b') { if !chunk_is_text && !modeb.contains(&b'b') {
@@ -423,7 +423,7 @@ pub unsafe fn luaL_traceback(
level = numlevels - COMPAT53_LEVELS2; // and skip to last ones level = numlevels - COMPAT53_LEVELS2; // and skip to last ones
} else { } else {
lua_getinfo(L1, level, cstr!("sln"), &mut ar); lua_getinfo(L1, level, cstr!("sln"), &mut ar);
lua_pushfstring(L, cstr!("\n\t%s:"), ar.short_src.as_ptr()); lua_pushfstring(L, cstr!("\n\t%s:"), ar.short_src);
if ar.currentline > 0 { if ar.currentline > 0 {
lua_pushfstring(L, cstr!("%d:"), ar.currentline); lua_pushfstring(L, cstr!("%d:"), ar.currentline);
} }
@@ -436,10 +436,10 @@ pub unsafe fn luaL_traceback(
lua_concat(L, lua_gettop(L) - top); lua_concat(L, lua_gettop(L) - top);
} }
pub unsafe fn luaL_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) -> *const c_char { pub unsafe fn luaL_tolstring(L: *mut lua_State, mut idx: c_int, len: *mut usize) -> *const c_char {
idx = lua_absindex(L, idx);
if luaL_callmeta(L, idx, cstr!("__tostring")) == 0 { if luaL_callmeta(L, idx, cstr!("__tostring")) == 0 {
let t = lua_type(L, idx); match lua_type(L, idx) {
match t {
LUA_TNIL => { LUA_TNIL => {
lua_pushliteral(L, "nil"); lua_pushliteral(L, "nil");
} }
@@ -453,8 +453,8 @@ pub unsafe fn luaL_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) ->
lua_pushliteral(L, "true"); lua_pushliteral(L, "true");
} }
} }
_ => { t => {
let tt = luaL_getmetafield(L, idx, cstr!("__name")); let tt = luaL_getmetafield(L, idx, cstr!("__type"));
let name = if tt == LUA_TSTRING { let name = if tt == LUA_TSTRING {
lua_tostring(L, -1) lua_tostring(L, -1)
} else { } else {
@@ -462,7 +462,7 @@ pub unsafe fn luaL_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) ->
}; };
lua_pushfstring(L, cstr!("%s: %p"), name, lua_topointer(L, idx)); lua_pushfstring(L, cstr!("%s: %p"), name, lua_topointer(L, idx));
if tt != LUA_TNIL { if tt != LUA_TNIL {
lua_replace(L, -2); lua_replace(L, -2); // remove '__type'
} }
} }
}; };
@@ -13,7 +13,7 @@ pub struct luaL_Reg {
pub func: lua_CFunction, 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); pub fn luaL_register(L: *mut lua_State, libname: *const c_char, l: *const luaL_Reg);
#[link_name = "luaL_getmetafield"] #[link_name = "luaL_getmetafield"]
pub fn luaL_getmetafield_(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int; 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_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_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); pub fn luaL_where(L: *mut lua_State, lvl: c_int);
#[link_name = "luaL_errorL"] #[link_name = "luaL_errorL"]
@@ -142,9 +144,12 @@ pub unsafe fn luaL_sandbox(L: *mut lua_State, enabled: c_int) {
// set all builtin metatables to read-only // set all builtin metatables to read-only
lua_pushliteral(L, ""); lua_pushliteral(L, "");
lua_getmetatable(L, -1); if lua_getmetatable(L, -1) != 0 {
lua_setreadonly(L, -1, enabled); lua_setreadonly(L, -1, enabled);
lua_pop(L, 2); lua_pop(L, 2);
} else {
lua_pop(L, 1);
}
// set globals to readonly and activate safeenv since the env is immutable // set globals to readonly and activate safeenv since the env is immutable
lua_setreadonly(L, LUA_GLOBALSINDEX, enabled); lua_setreadonly(L, LUA_GLOBALSINDEX, enabled);
@@ -152,5 +157,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);
}
@@ -2,17 +2,26 @@
use std::marker::{PhantomData, PhantomPinned}; use std::marker::{PhantomData, PhantomPinned};
use std::os::raw::{c_char, c_double, c_float, c_int, c_uint, c_void}; 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' // Option for multiple returns in 'lua_pcall' and 'lua_call'
pub const LUA_MULTRET: c_int = -1; pub const LUA_MULTRET: c_int = -1;
// Max number of Lua stack slots
const LUAI_MAXCSTACK: c_int = 1000000;
// Number of valid Lua userdata tags
const LUA_UTAG_LIMIT: c_int = 128;
// Number of valid Lua lightuserdata tags
const LUA_LUTAG_LIMIT: c_int = 128;
// //
// Pseudo-indices // Pseudo-indices
// //
pub const LUA_REGISTRYINDEX: c_int = -10000; pub const LUA_REGISTRYINDEX: c_int = -LUAI_MAXCSTACK - 2000;
pub const LUA_ENVIRONINDEX: c_int = -10001; pub const LUA_ENVIRONINDEX: c_int = -LUAI_MAXCSTACK - 2001;
pub const LUA_GLOBALSINDEX: c_int = -10002; pub const LUA_GLOBALSINDEX: c_int = -LUAI_MAXCSTACK - 2002;
pub const fn lua_upvalueindex(i: c_int) -> c_int { pub const fn lua_upvalueindex(i: c_int) -> c_int {
LUA_GLOBALSINDEX - i LUA_GLOBALSINDEX - i
@@ -52,6 +61,7 @@ pub const LUA_TTABLE: c_int = 6;
pub const LUA_TFUNCTION: c_int = 7; pub const LUA_TFUNCTION: c_int = 7;
pub const LUA_TUSERDATA: c_int = 8; pub const LUA_TUSERDATA: c_int = 8;
pub const LUA_TTHREAD: c_int = 9; 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. /// Guaranteed number of Lua stack slots available to a C function.
pub const LUA_MINSTACK: c_int = 20; pub const LUA_MINSTACK: c_int = 20;
@@ -66,21 +76,27 @@ pub type lua_Integer = c_int;
pub type lua_Unsigned = c_uint; pub type lua_Unsigned = c_uint;
/// Type for native C functions that can be passed to Lua. /// 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;
pub type lua_Continuation = unsafe extern "C" fn(L: *mut lua_State, status: c_int) -> 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. /// Type for userdata destructor functions.
pub type lua_Udestructor = unsafe extern "C" fn(*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. /// 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, ud: *mut c_void,
ptr: *mut c_void, ptr: *mut c_void,
osize: usize, osize: usize,
nsize: usize, nsize: usize,
) -> *mut c_void; ) -> *mut c_void;
extern "C" { /// Returns Luau release version (eg. `0.xxx`).
pub const fn luau_version() -> Option<&'static str> {
option_env!("LUAU_VERSION")
}
extern "C-unwind" {
// //
// State manipulation // State manipulation
// //
@@ -134,10 +150,13 @@ extern "C" {
pub fn lua_objlen(L: *mut lua_State, idx: c_int) -> usize; pub fn lua_objlen(L: *mut lua_State, idx: c_int) -> usize;
pub fn lua_tocfunction(L: *mut lua_State, idx: c_int) -> Option<lua_CFunction>; pub fn lua_tocfunction(L: *mut lua_State, idx: c_int) -> Option<lua_CFunction>;
pub fn lua_tolightuserdata(L: *mut lua_State, idx: c_int) -> *mut c_void; pub fn lua_tolightuserdata(L: *mut lua_State, idx: c_int) -> *mut c_void;
pub fn lua_tolightuserdatatagged(L: *mut lua_State, idx: c_int, tag: c_int) -> *mut c_void;
pub fn lua_touserdata(L: *mut lua_State, idx: c_int) -> *mut c_void; pub fn lua_touserdata(L: *mut lua_State, idx: c_int) -> *mut c_void;
pub fn lua_touserdatatagged(L: *mut lua_State, idx: c_int, tag: c_int) -> *mut c_void; 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_userdatatag(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_lightuserdatatag(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_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; pub fn lua_topointer(L: *mut lua_State, idx: c_int) -> *const c_void;
// //
@@ -147,7 +166,10 @@ extern "C" {
pub fn lua_pushnumber(L: *mut lua_State, n: lua_Number); 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_pushinteger(L: *mut lua_State, n: lua_Integer);
pub fn lua_pushunsigned(L: *mut lua_State, n: lua_Unsigned); 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); 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"] #[link_name = "lua_pushlstring"]
pub fn lua_pushlstring_(L: *mut lua_State, s: *const c_char, l: usize); pub fn lua_pushlstring_(L: *mut lua_State, s: *const c_char, l: usize);
#[link_name = "lua_pushstring"] #[link_name = "lua_pushstring"]
@@ -165,10 +187,12 @@ extern "C" {
pub fn lua_pushboolean(L: *mut lua_State, b: c_int); pub fn lua_pushboolean(L: *mut lua_State, b: c_int);
pub fn lua_pushthread(L: *mut lua_State) -> c_int; pub fn lua_pushthread(L: *mut lua_State) -> c_int;
pub fn lua_pushlightuserdata(L: *mut lua_State, p: *mut c_void); pub fn lua_pushlightuserdatatagged(L: *mut lua_State, p: *mut c_void, tag: c_int);
pub fn lua_newuserdatatagged(L: *mut lua_State, sz: usize, tag: c_int) -> *mut c_void; 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_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) // Get functions (Lua -> stack)
// //
@@ -239,14 +263,14 @@ pub const LUA_GCSETGOAL: c_int = 7;
pub const LUA_GCSETSTEPMUL: c_int = 8; pub const LUA_GCSETSTEPMUL: c_int = 8;
pub const LUA_GCSETSTEPSIZE: c_int = 9; 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; pub fn lua_gc(L: *mut lua_State, what: c_int, data: c_int) -> c_int;
} }
// //
// Memory statistics // Memory statistics
// //
extern "C" { extern "C-unwind" {
pub fn lua_setmemcat(L: *mut lua_State, category: c_int); pub fn lua_setmemcat(L: *mut lua_State, category: c_int);
pub fn lua_totalbytes(L: *mut lua_State, category: c_int) -> usize; pub fn lua_totalbytes(L: *mut lua_State, category: c_int) -> usize;
} }
@@ -254,19 +278,23 @@ extern "C" {
// //
// Miscellaneous functions // Miscellaneous functions
// //
extern "C" { extern "C-unwind" {
pub fn lua_error(L: *mut lua_State) -> !; pub fn lua_error(L: *mut lua_State) -> !;
pub fn lua_next(L: *mut lua_State, idx: c_int) -> c_int; 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;
pub fn lua_concat(L: *mut lua_State, n: c_int); pub fn lua_concat(L: *mut lua_State, n: c_int);
// TODO: lua_encodepointer // TODO: lua_encodepointer
pub fn lua_clock() -> c_double; pub fn lua_clock() -> c_double;
pub fn lua_setuserdatatag(L: *mut lua_State, idx: c_int, tag: c_int); pub fn lua_setuserdatatag(L: *mut lua_State, idx: c_int, tag: c_int);
pub fn lua_setuserdatadtor( pub fn lua_setuserdatadtor(L: *mut lua_State, tag: c_int, dtor: Option<lua_Destructor>);
L: *mut lua_State, pub fn lua_getuserdatadtor(L: *mut lua_State, tag: c_int) -> Option<lua_Destructor>;
tag: c_int, pub fn lua_setuserdatametatable(L: *mut lua_State, tag: c_int, idx: c_int);
dtor: Option<unsafe extern "C" fn(*mut lua_State, *mut c_void)>, pub fn lua_getuserdatametatable(L: *mut lua_State, tag: c_int);
); pub fn lua_setlightuserdataname(L: *mut lua_State, tag: c_int, name: *const c_char);
pub fn lua_getlightuserdataname(L: *mut lua_State, tag: c_int) -> *const c_char;
pub fn lua_clonefunction(L: *mut lua_State, idx: c_int); 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;
} }
// //
@@ -275,7 +303,7 @@ extern "C" {
pub const LUA_NOREF: c_int = -1; pub const LUA_NOREF: c_int = -1;
pub const LUA_REFNIL: c_int = 0; 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_ref(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_unref(L: *mut lua_State, r#ref: c_int); pub fn lua_unref(L: *mut lua_State, r#ref: c_int);
} }
@@ -314,6 +342,15 @@ pub unsafe fn lua_newuserdata(L: *mut lua_State, sz: usize) -> *mut c_void {
lua_newuserdatatagged(L, sz, 0) 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 // TODO: lua_strlen
#[inline(always)] #[inline(always)]
@@ -351,6 +388,11 @@ pub unsafe fn lua_isthread(L: *mut lua_State, n: c_int) -> c_int {
(lua_type(L, n) == LUA_TTHREAD) as 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)] #[inline(always)]
pub unsafe fn lua_isnone(L: *mut lua_State, n: c_int) -> c_int { pub unsafe fn lua_isnone(L: *mut lua_State, n: c_int) -> c_int {
(lua_type(L, n) == LUA_TNONE) as c_int (lua_type(L, n) == LUA_TNONE) as c_int
@@ -368,18 +410,22 @@ pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static str) {
lua_pushlstring_(L, c_str.as_ptr(), c_str.as_bytes().len()) lua_pushlstring_(L, c_str.as_ptr(), c_str.as_bytes().len())
} }
#[inline(always)]
pub unsafe fn lua_pushcfunction(L: *mut lua_State, f: lua_CFunction) { pub unsafe fn lua_pushcfunction(L: *mut lua_State, f: lua_CFunction) {
lua_pushcclosurek(L, f, ptr::null(), 0, None) lua_pushcclosurek(L, f, ptr::null(), 0, None)
} }
#[inline(always)]
pub unsafe fn lua_pushcfunctiond(L: *mut lua_State, f: lua_CFunction, debugname: *const c_char) { pub unsafe fn lua_pushcfunctiond(L: *mut lua_State, f: lua_CFunction, debugname: *const c_char) {
lua_pushcclosurek(L, f, debugname, 0, None) lua_pushcclosurek(L, f, debugname, 0, None)
} }
#[inline(always)]
pub unsafe fn lua_pushcclosure(L: *mut lua_State, f: lua_CFunction, nup: c_int) { pub unsafe fn lua_pushcclosure(L: *mut lua_State, f: lua_CFunction, nup: c_int) {
lua_pushcclosurek(L, f, ptr::null(), nup, None) lua_pushcclosurek(L, f, ptr::null(), nup, None)
} }
#[inline(always)]
pub unsafe fn lua_pushcclosured( pub unsafe fn lua_pushcclosured(
L: *mut lua_State, L: *mut lua_State,
f: lua_CFunction, f: lua_CFunction,
@@ -389,6 +435,11 @@ pub unsafe fn lua_pushcclosured(
lua_pushcclosurek(L, f, debugname, nup, None) lua_pushcclosurek(L, f, debugname, nup, None)
} }
#[inline(always)]
pub unsafe fn lua_pushlightuserdata(L: *mut lua_State, p: *mut c_void) {
lua_pushlightuserdatatagged(L, p, 0)
}
#[inline(always)] #[inline(always)]
pub unsafe fn lua_setglobal(L: *mut lua_State, var: *const c_char) { pub unsafe fn lua_setglobal(L: *mut lua_State, var: *const c_char) {
lua_setfield(L, LUA_GLOBALSINDEX, var) lua_setfield(L, LUA_GLOBALSINDEX, var)
@@ -412,9 +463,9 @@ pub unsafe fn lua_tostring(L: *mut lua_State, i: c_int) -> *const c_char {
const LUA_IDSIZE: usize = 256; const LUA_IDSIZE: usize = 256;
/// Type for functions to be called on debug events. /// 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, context: *mut c_void,
function: *const c_char, function: *const c_char,
linedefined: c_int, linedefined: c_int,
@@ -423,7 +474,7 @@ pub type lua_Coverage = unsafe extern "C" fn(
size: usize, size: usize,
); );
extern "C" { extern "C-unwind" {
pub fn lua_stackdepth(L: *mut lua_State) -> c_int; pub fn lua_stackdepth(L: *mut lua_State) -> c_int;
pub fn lua_getinfo( pub fn lua_getinfo(
L: *mut lua_State, L: *mut lua_State,
@@ -460,13 +511,14 @@ pub struct lua_Debug {
pub name: *const c_char, pub name: *const c_char,
pub what: *const c_char, pub what: *const c_char,
pub source: *const c_char, pub source: *const c_char,
pub short_src: *const c_char,
pub linedefined: c_int, pub linedefined: c_int,
pub currentline: c_int, pub currentline: c_int,
pub nupvals: u8, pub nupvals: u8,
pub nparams: u8, pub nparams: u8,
pub isvararg: c_char, pub isvararg: c_char,
pub short_src: [c_char; LUA_IDSIZE],
pub userdata: *mut c_void, pub userdata: *mut c_void,
pub ssbuf: [c_char; LUA_IDSIZE],
} }
// //
@@ -475,30 +527,36 @@ pub struct lua_Debug {
// //
#[repr(C)] #[repr(C)]
#[non_exhaustive]
pub struct lua_Callbacks { pub struct lua_Callbacks {
/// arbitrary userdata pointer that is never overwritten by Luau /// arbitrary userdata pointer that is never overwritten by Luau
pub userdata: *mut c_void, pub userdata: *mut c_void,
/// gets called at safepoints (loop back edges, call/ret, gc) if set /// 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) /// 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) /// 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 /// 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 /// 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 /// 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 /// 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 /// 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" { extern "C" {
pub fn lua_callbacks(L: *mut lua_State) -> *mut lua_Callbacks; pub fn lua_callbacks(L: *mut lua_State) -> *mut lua_Callbacks;
} }
// Functions from customization lib
extern "C" {
pub fn luau_setfflag(name: *const c_char, value: c_int) -> c_int;
}
@@ -1,19 +1,39 @@
//! Contains definitions from `luacode.h`. //! Contains definitions from `luacode.h`.
use std::os::raw::{c_char, c_int, c_void}; use std::os::raw::{c_char, c_int, c_void};
use std::slice; use std::{ptr, slice};
#[repr(C)] #[repr(C)]
#[non_exhaustive]
pub struct lua_CompileOptions { pub struct lua_CompileOptions {
pub optimizationLevel: c_int, pub optimizationLevel: c_int,
pub debugLevel: c_int, pub debugLevel: c_int,
pub typeInfoLevel: c_int,
pub coverageLevel: c_int, pub coverageLevel: c_int,
pub vectorLib: *const c_char, pub vectorLib: *const c_char,
pub vectorCtor: *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,
pub userdataTypes: *const *const c_char,
} }
extern "C" { impl Default for lua_CompileOptions {
fn default() -> Self {
Self {
optimizationLevel: 1,
debugLevel: 1,
typeInfoLevel: 0,
coverageLevel: 0,
vectorLib: ptr::null(),
vectorCtor: ptr::null(),
vectorType: ptr::null(),
mutableGlobals: ptr::null(),
userdataTypes: ptr::null(),
}
}
}
extern "C-unwind" {
#[link_name = "luau_compile"] #[link_name = "luau_compile"]
pub fn luau_compile_( pub fn luau_compile_(
source: *const c_char, source: *const c_char,
@@ -21,7 +41,9 @@ extern "C" {
options: *mut lua_CompileOptions, options: *mut lua_CompileOptions,
outsize: *mut usize, outsize: *mut usize,
) -> *mut c_char; ) -> *mut c_char;
}
extern "C" {
fn free(p: *mut c_void); fn free(p: *mut c_void);
} }
@@ -33,6 +55,7 @@ pub unsafe fn luau_compile(source: &[u8], mut options: lua_CompileOptions) -> Ve
&mut options, &mut options,
&mut outsize, &mut outsize,
); );
assert!(!data_ptr.is_null(), "luau_compile failed");
let data = slice::from_raw_parts(data_ptr as *mut u8, outsize).to_vec(); let data = slice::from_raw_parts(data_ptr as *mut u8, outsize).to_vec();
free(data_ptr as *mut c_void); free(data_ptr as *mut c_void);
data data
+11
View File
@@ -0,0 +1,11 @@
//! Contains definitions from `luacodegen.h`.
use std::os::raw::c_int;
use super::lua::lua_State;
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);
}
@@ -9,17 +9,19 @@ pub const LUA_TABLIBNAME: &str = "table";
pub const LUA_OSLIBNAME: &str = "os"; pub const LUA_OSLIBNAME: &str = "os";
pub const LUA_STRLIBNAME: &str = "string"; pub const LUA_STRLIBNAME: &str = "string";
pub const LUA_BITLIBNAME: &str = "bit32"; pub const LUA_BITLIBNAME: &str = "bit32";
pub const LUA_BUFFERLIBNAME: &str = "buffer";
pub const LUA_UTF8LIBNAME: &str = "utf8"; pub const LUA_UTF8LIBNAME: &str = "utf8";
pub const LUA_MATHLIBNAME: &str = "math"; pub const LUA_MATHLIBNAME: &str = "math";
pub const LUA_DBLIBNAME: &str = "debug"; pub const LUA_DBLIBNAME: &str = "debug";
extern "C" { extern "C-unwind" {
pub fn luaopen_base(L: *mut lua_State) -> c_int; pub fn luaopen_base(L: *mut lua_State) -> c_int;
pub fn luaopen_coroutine(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_table(L: *mut lua_State) -> c_int;
pub fn luaopen_os(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_string(L: *mut lua_State) -> c_int;
pub fn luaopen_bit32(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_utf8(L: *mut lua_State) -> c_int;
pub fn luaopen_math(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; pub fn luaopen_debug(L: *mut lua_State) -> c_int;
@@ -4,10 +4,12 @@ pub use compat::*;
pub use lauxlib::*; pub use lauxlib::*;
pub use lua::*; pub use lua::*;
pub use luacode::*; pub use luacode::*;
pub use luacodegen::*;
pub use lualib::*; pub use lualib::*;
pub mod compat; pub mod compat;
pub mod lauxlib; pub mod lauxlib;
pub mod lua; pub mod lua;
pub mod luacode; pub mod luacode;
pub mod luacodegen;
pub mod lualib; pub mod lualib;
+7
View File
@@ -0,0 +1,7 @@
#[allow(unused_macros)]
macro_rules! cstr {
($s:expr) => {
concat!($s, "\0") as *const str as *const [::std::os::raw::c_char]
as *const ::std::os::raw::c_char
};
}
+4 -4
View File
@@ -1,8 +1,8 @@
[package] [package]
name = "mlua_derive" name = "mlua_derive"
version = "0.8.0" version = "0.9.3"
authors = ["Aleksandr Orlenko <zxteam@pm.me>"] authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
edition = "2018" edition = "2021"
description = "Procedural macros for the mlua crate." description = "Procedural macros for the mlua crate."
repository = "https://github.com/khvzak/mlua" repository = "https://github.com/khvzak/mlua"
keywords = ["lua", "mlua"] keywords = ["lua", "mlua"]
@@ -18,7 +18,7 @@ macros = ["proc-macro-error", "itertools", "regex", "once_cell"]
quote = "1.0" quote = "1.0"
proc-macro2 = { version = "1.0", features = ["span-locations"] } proc-macro2 = { version = "1.0", features = ["span-locations"] }
proc-macro-error = { version = "1.0", optional = true } proc-macro-error = { version = "1.0", optional = true }
syn = { version = "1.0", features = ["full"] } syn = { version = "2.0", features = ["full"] }
itertools = { version = "0.10", optional = true } itertools = { version = "0.12", optional = true }
regex = { version = "1.4", optional = true } regex = { version = "1.4", optional = true }
once_cell = { version = "1.0", optional = true } once_cell = { version = "1.0", optional = true }
+33
View File
@@ -0,0 +1,33 @@
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 ident_str = ident.to_string();
let (impl_generics, ty_generics, _) = generics.split_for_impl();
let where_clause = match &generics.where_clause {
Some(where_clause) => quote! { #where_clause, Self: 'static + Clone },
None => quote! { where Self: 'static + Clone },
};
quote! {
impl #impl_generics ::mlua::FromLua<'_> for #ident #ty_generics #where_clause {
#[inline]
fn from_lua(value: ::mlua::Value<'_>, _: &'_ ::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()
}
+74 -34
View File
@@ -1,7 +1,8 @@
use proc_macro::TokenStream; use proc_macro::TokenStream;
use proc_macro2::{Ident, Span}; use proc_macro2::{Ident, Span};
use quote::quote; use quote::quote;
use syn::{parse_macro_input, AttributeArgs, Error, ItemFn}; use syn::meta::ParseNestedMeta;
use syn::{parse_macro_input, ItemFn, LitStr, Result};
#[cfg(feature = "macros")] #[cfg(feature = "macros")]
use { use {
@@ -9,30 +10,63 @@ use {
proc_macro_error::proc_macro_error, proc_macro_error::proc_macro_error,
}; };
#[derive(Default)]
struct ModuleAttributes {
name: Option<Ident>,
skip_memory_check: bool,
}
impl ModuleAttributes {
fn parse(&mut self, meta: ParseNestedMeta) -> Result<()> {
if meta.path.is_ident("name") {
match meta.value() {
Ok(value) => {
self.name = Some(value.parse::<LitStr>()?.parse()?);
}
Err(_) => {
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"));
}
Ok(())
}
}
#[proc_macro_attribute] #[proc_macro_attribute]
pub fn lua_module(attr: TokenStream, item: TokenStream) -> TokenStream { pub fn lua_module(attr: TokenStream, item: TokenStream) -> TokenStream {
let args = parse_macro_input!(attr as AttributeArgs); let mut args = ModuleAttributes::default();
let func = parse_macro_input!(item as ItemFn); if !attr.is_empty() {
let args_parser = syn::meta::parser(|meta| args.parse(meta));
if !args.is_empty() { parse_macro_input!(attr with args_parser);
let err = Error::new(Span::call_site(), "the macro does not support arguments")
.to_compile_error();
return err.into();
} }
let func_name = func.sig.ident.clone(); let func = parse_macro_input!(item as ItemFn);
let ext_entrypoint_name = Ident::new(&format!("luaopen_{}", func_name), Span::call_site()); 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 = if args.skip_memory_check {
quote! { lua.skip_memory_check(true); }
} else {
quote! {}
};
let wrapped = quote! { let wrapped = quote! {
::mlua::require_module_feature!(); mlua::require_module_feature!();
#func #func
#[no_mangle] #[no_mangle]
unsafe extern "C" fn #ext_entrypoint_name(state: *mut ::mlua::lua_State) -> ::std::os::raw::c_int { unsafe extern "C-unwind" fn #ext_entrypoint_name(state: *mut mlua::lua_State) -> ::std::os::raw::c_int {
::mlua::Lua::init_from_ptr(state) let lua = mlua::Lua::init_from_ptr(state);
.entrypoint1(#func_name) #skip_memory_check
.expect("cannot initialize module") lua.entrypoint1(state, #func_name)
} }
}; };
@@ -61,30 +95,22 @@ pub fn chunk(input: TokenStream) -> TokenStream {
}); });
let wrapped_code = quote! {{ let wrapped_code = quote! {{
use ::mlua::{AsChunk, ChunkMode, Lua, Result, Value}; use mlua::{AsChunk, ChunkMode, Lua, Result, Table};
use ::std::borrow::Cow; use ::std::borrow::Cow;
use ::std::cell::Cell;
use ::std::io::Result as IoResult; use ::std::io::Result as IoResult;
use ::std::marker::PhantomData; use ::std::marker::PhantomData;
use ::std::sync::Mutex;
fn annotate<'a, F: FnOnce(&'a Lua) -> Result<Value<'a>>>(f: F) -> F { f } struct InnerChunk<'lua, F: FnOnce(&'lua Lua) -> Result<Table<'lua>>>(Cell<Option<F>>, PhantomData<&'lua ()>);
struct InnerChunk<'a, F: FnOnce(&'a Lua) -> Result<Value<'a>>>(Mutex<Option<F>>, PhantomData<&'a ()>); impl<'lua, F> AsChunk<'lua, 'static> for InnerChunk<'lua, F>
impl<'lua, F> AsChunk<'lua> for InnerChunk<'lua, F>
where where
F: FnOnce(&'lua Lua) -> Result<Value<'lua>>, F: FnOnce(&'lua Lua) -> Result<Table<'lua>>,
{ {
fn source(&self) -> IoResult<Cow<[u8]>> { fn environment(&self, lua: &'lua Lua) -> Result<Option<Table<'lua>>> {
Ok(Cow::Borrowed((#source).as_bytes()))
}
fn env(&self, lua: &'lua Lua) -> Result<Option<Value<'lua>>> {
if #caps_len > 0 { if #caps_len > 0 {
if let Ok(mut make_env) = self.0.lock() { if let Some(make_env) = self.0.take() {
if let Some(make_env) = make_env.take() { return make_env(lua).map(Some);
return make_env(lua).map(Some);
}
} }
} }
Ok(None) Ok(None)
@@ -93,9 +119,15 @@ pub fn chunk(input: TokenStream) -> TokenStream {
fn mode(&self) -> Option<ChunkMode> { fn mode(&self) -> Option<ChunkMode> {
Some(ChunkMode::Text) Some(ChunkMode::Text)
} }
fn source(self) -> IoResult<Cow<'static, [u8]>> {
Ok(Cow::Borrowed((#source).as_bytes()))
}
} }
let make_env = annotate(move |lua: &Lua| -> Result<Value> { 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(); let globals = lua.globals();
let env = lua.create_table()?; let env = lua.create_table()?;
let meta = lua.create_table()?; let meta = lua.create_table()?;
@@ -106,16 +138,24 @@ pub fn chunk(input: TokenStream) -> TokenStream {
#(#caps)* #(#caps)*
env.set_metatable(Some(meta)); env.set_metatable(Some(meta));
Ok(Value::Table(env)) Ok(env)
}); });
&InnerChunk(Mutex::new(Some(make_env)), PhantomData) InnerChunk(Cell::new(Some(make_env)), PhantomData)
}}; }};
wrapped_code.into() 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")] #[cfg(feature = "macros")]
mod chunk; mod chunk;
#[cfg(feature = "macros")] #[cfg(feature = "macros")]
mod from_lua;
#[cfg(feature = "macros")]
mod token; mod token;
+1 -2
View File
@@ -1,7 +1,6 @@
use std::{ use std::{
cmp::{Eq, PartialEq}, cmp::{Eq, PartialEq},
fmt::{self, Display, Formatter}, fmt::{self, Display, Formatter},
iter::IntoIterator,
vec::IntoIter, vec::IntoIter,
}; };
@@ -59,7 +58,7 @@ fn parse_pos(span: &Span) -> Option<(usize, usize)> {
static RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"bytes\(([0-9]+)\.\.([0-9]+)\)").unwrap()); static RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"bytes\(([0-9]+)\.\.([0-9]+)\)").unwrap());
match RE.captures(&format!("{:?}", span)) { match RE.captures(&format!("{span:?}")) {
Some(caps) => match (caps.get(1), caps.get(2)) { Some(caps) => match (caps.get(1), caps.get(2)) {
(Some(start), Some(end)) => Some(( (Some(start), Some(end)) => Some((
match start.as_str().parse() { match start.as_str().parse() {
+166 -123
View File
@@ -5,23 +5,17 @@ use std::io::Result as IoResult;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::string::String as StdString; use std::string::String as StdString;
use crate::error::{Error, Result}; use crate::error::{Error, ErrorContext, Result};
use crate::ffi;
use crate::function::Function; use crate::function::Function;
use crate::lua::Lua; use crate::lua::Lua;
use crate::value::{FromLuaMulti, ToLua, ToLuaMulti, Value}; use crate::table::Table;
use crate::value::{FromLuaMulti, IntoLua, IntoLuaMulti};
#[cfg(feature = "async")]
use {futures_core::future::LocalBoxFuture, futures_util::future};
/// Trait for types [loadable by Lua] and convertible to a [`Chunk`] /// 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 /// [loadable by Lua]: https://www.lua.org/manual/5.4/manual.html#3.3.2
/// [`Chunk`]: crate::Chunk /// [`Chunk`]: crate::Chunk
pub trait AsChunk<'lua> { pub trait AsChunk<'lua, 'a> {
/// Returns chunk data (can be text or binary)
fn source(&self) -> IoResult<Cow<[u8]>>;
/// Returns optional chunk name /// Returns optional chunk name
fn name(&self) -> Option<StdString> { fn name(&self) -> Option<StdString> {
None None
@@ -30,7 +24,8 @@ pub trait AsChunk<'lua> {
/// Returns optional chunk [environment] /// Returns optional chunk [environment]
/// ///
/// [environment]: https://www.lua.org/manual/5.4/manual.html#2.2 /// [environment]: https://www.lua.org/manual/5.4/manual.html#2.2
fn env(&self, _lua: &'lua Lua) -> Result<Option<Value<'lua>>> { fn environment(&self, lua: &'lua Lua) -> Result<Option<Table<'lua>>> {
let _lua = lua; // suppress warning
Ok(None) Ok(None)
} }
@@ -38,50 +33,65 @@ pub trait AsChunk<'lua> {
fn mode(&self) -> Option<ChunkMode> { fn mode(&self) -> Option<ChunkMode> {
None None
} }
/// Returns chunk data (can be text or binary)
fn source(self) -> IoResult<Cow<'a, [u8]>>;
} }
impl<'lua> AsChunk<'lua> for str { impl<'a> AsChunk<'_, 'a> for &'a str {
fn source(&self) -> IoResult<Cow<[u8]>> { fn source(self) -> IoResult<Cow<'a, [u8]>> {
Ok(Cow::Borrowed(self.as_ref())) Ok(Cow::Borrowed(self.as_ref()))
} }
} }
impl<'lua> AsChunk<'lua> for StdString { impl AsChunk<'_, 'static> for StdString {
fn source(&self) -> IoResult<Cow<[u8]>> { fn source(self) -> IoResult<Cow<'static, [u8]>> {
Ok(Cow::Owned(self.into_bytes()))
}
}
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] {
fn source(self) -> IoResult<Cow<'a, [u8]>> {
Ok(Cow::Borrowed(self))
}
}
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> {
fn source(self) -> IoResult<Cow<'a, [u8]>> {
Ok(Cow::Borrowed(self.as_ref())) Ok(Cow::Borrowed(self.as_ref()))
} }
} }
impl<'lua> AsChunk<'lua> for [u8] { impl AsChunk<'_, 'static> for &Path {
fn source(&self) -> IoResult<Cow<[u8]>> {
Ok(Cow::Borrowed(self))
}
}
impl<'lua> AsChunk<'lua> for Vec<u8> {
fn source(&self) -> IoResult<Cow<[u8]>> {
Ok(Cow::Borrowed(self))
}
}
impl<'lua> AsChunk<'lua> for Path {
fn source(&self) -> IoResult<Cow<[u8]>> {
std::fs::read(self).map(Cow::Owned)
}
fn name(&self) -> Option<StdString> { fn name(&self) -> Option<StdString> {
Some(format!("@{}", self.display())) Some(format!("@{}", self.display()))
} }
}
impl<'lua> AsChunk<'lua> for PathBuf { fn source(self) -> IoResult<Cow<'static, [u8]>> {
fn source(&self) -> IoResult<Cow<[u8]>> {
std::fs::read(self).map(Cow::Owned) std::fs::read(self).map(Cow::Owned)
} }
}
impl AsChunk<'_, 'static> for PathBuf {
fn name(&self) -> Option<StdString> { fn name(&self) -> Option<StdString> {
Some(format!("@{}", self.display())) Some(format!("@{}", self.display()))
} }
fn source(self) -> IoResult<Cow<'static, [u8]>> {
std::fs::read(self).map(Cow::Owned)
}
} }
/// Returned from [`Lua::load`] and is used to finalize loading and executing Lua main chunks. /// Returned from [`Lua::load`] and is used to finalize loading and executing Lua main chunks.
@@ -90,10 +100,10 @@ impl<'lua> AsChunk<'lua> for PathBuf {
#[must_use = "`Chunk`s do nothing unless one of `exec`, `eval`, `call`, or `into_function` are called on them"] #[must_use = "`Chunk`s do nothing unless one of `exec`, `eval`, `call`, or `into_function` are called on them"]
pub struct Chunk<'lua, 'a> { pub struct Chunk<'lua, 'a> {
pub(crate) lua: &'lua Lua, pub(crate) lua: &'lua Lua,
pub(crate) source: IoResult<Cow<'a, [u8]>>, pub(crate) name: StdString,
pub(crate) name: Option<StdString>, pub(crate) env: Result<Option<Table<'lua>>>,
pub(crate) env: Result<Option<Value<'lua>>>,
pub(crate) mode: Option<ChunkMode>, pub(crate) mode: Option<ChunkMode>,
pub(crate) source: IoResult<Cow<'a, [u8]>>,
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
pub(crate) compiler: Option<Compiler>, pub(crate) compiler: Option<Compiler>,
} }
@@ -112,32 +122,38 @@ pub enum ChunkMode {
pub struct Compiler { pub struct Compiler {
optimization_level: u8, optimization_level: u8,
debug_level: u8, debug_level: u8,
type_info_level: u8,
coverage_level: u8, coverage_level: u8,
vector_lib: Option<String>, vector_lib: Option<String>,
vector_ctor: Option<String>, vector_ctor: Option<String>,
vector_type: Option<String>,
mutable_globals: Vec<String>, mutable_globals: Vec<String>,
userdata_types: Vec<String>,
} }
#[cfg(any(feature = "luau", doc))] #[cfg(any(feature = "luau", doc))]
impl Default for Compiler { impl Default for Compiler {
fn default() -> Self { fn default() -> Self {
// Defaults are taken from luacode.h Self::new()
Compiler {
optimization_level: 1,
debug_level: 1,
coverage_level: 0,
vector_lib: None,
vector_ctor: None,
mutable_globals: Vec::new(),
}
} }
} }
#[cfg(any(feature = "luau", doc))] #[cfg(any(feature = "luau", doc))]
impl Compiler { impl Compiler {
/// Creates Luau compiler instance with default options /// Creates Luau compiler instance with default options
pub fn new() -> Self { pub const fn new() -> Self {
Compiler::default() // Defaults are taken from luacode.h
Compiler {
optimization_level: 1,
debug_level: 1,
type_info_level: 0,
coverage_level: 0,
vector_lib: None,
vector_ctor: None,
vector_type: None,
mutable_globals: Vec::new(),
userdata_types: Vec::new(),
}
} }
/// Sets Luau compiler optimization level. /// Sets Luau compiler optimization level.
@@ -146,7 +162,8 @@ impl Compiler {
/// * 0 - no optimization /// * 0 - no optimization
/// * 1 - baseline optimization level that doesn't prevent debuggability (default) /// * 1 - baseline optimization level that doesn't prevent debuggability (default)
/// * 2 - includes optimizations that harm debuggability such as inlining /// * 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.optimization_level = level;
self self
} }
@@ -157,42 +174,71 @@ impl Compiler {
/// * 0 - no debugging support /// * 0 - no debugging support
/// * 1 - line info & function names only; sufficient for backtraces (default) /// * 1 - line info & function names only; sufficient for backtraces (default)
/// * 2 - full debug info with local & upvalue names; necessary for debugger /// * 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.debug_level = level;
self self
} }
/// Sets Luau type information level used to guide native code generation decisions.
///
/// Possible values:
/// * 0 - generate for native modules (default)
/// * 1 - generate for all modules
pub const fn set_type_info_level(mut self, level: u8) -> Self {
self.type_info_level = level;
self
}
/// Sets Luau compiler code coverage level. /// Sets Luau compiler code coverage level.
/// ///
/// Possible values: /// Possible values:
/// * 0 - no code coverage support (default) /// * 0 - no code coverage support (default)
/// * 1 - statement coverage /// * 1 - statement coverage
/// * 2 - statement and expression coverage (verbose) /// * 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.coverage_level = level;
self self
} }
#[doc(hidden)] #[doc(hidden)]
pub fn set_vector_lib(mut self, lib: Option<String>) -> Self { #[must_use]
self.vector_lib = lib; pub fn set_vector_lib(mut self, lib: impl Into<String>) -> Self {
self.vector_lib = Some(lib.into());
self self
} }
#[doc(hidden)] #[doc(hidden)]
pub fn set_vector_ctor(mut self, ctor: Option<String>) -> Self { #[must_use]
self.vector_ctor = ctor; pub fn set_vector_ctor(mut self, ctor: impl Into<String>) -> Self {
self.vector_ctor = Some(ctor.into());
self
}
#[doc(hidden)]
#[must_use]
pub fn set_vector_type(mut self, r#type: impl Into<String>) -> Self {
self.vector_type = Some(r#type.into());
self self
} }
/// Sets a list of globals that are mutable. /// Sets a list of globals that are mutable.
/// ///
/// It disables the import optimization for fields accessed through these. /// It disables the import optimization for fields accessed through these.
#[must_use]
pub fn set_mutable_globals(mut self, globals: Vec<String>) -> Self { pub fn set_mutable_globals(mut self, globals: Vec<String>) -> Self {
self.mutable_globals = globals; self.mutable_globals = globals;
self self
} }
/// Sets a list of userdata types that will be included in the type information.
#[must_use]
pub fn set_userdata_types(mut self, types: Vec<String>) -> Self {
self.userdata_types = types;
self
}
/// Compiles the `source` into bytecode. /// Compiles the `source` into bytecode.
pub fn compile(&self, source: impl AsRef<[u8]>) -> Vec<u8> { pub fn compile(&self, source: impl AsRef<[u8]>) -> Vec<u8> {
use std::os::raw::c_int; use std::os::raw::c_int;
@@ -204,32 +250,41 @@ impl Compiler {
let vector_ctor = self.vector_ctor.clone(); let vector_ctor = self.vector_ctor.clone();
let vector_ctor = vector_ctor.and_then(|ctor| CString::new(ctor).ok()); let vector_ctor = vector_ctor.and_then(|ctor| CString::new(ctor).ok());
let vector_ctor = vector_ctor.as_ref(); 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 macro_rules! vec2cstring_ptr {
.mutable_globals ($name:ident, $name_ptr:ident) => {
.iter() let $name = self
.map(|name| CString::new(name.clone()).ok()) .$name
.collect::<Option<Vec<_>>>() .iter()
.unwrap_or_default(); .map(|name| CString::new(name.clone()).ok())
let mut mutable_globals = mutable_globals .collect::<Option<Vec<_>>>()
.iter() .unwrap_or_default();
.map(|s| s.as_ptr()) let mut $name = $name.iter().map(|s| s.as_ptr()).collect::<Vec<_>>();
.collect::<Vec<_>>(); let mut $name_ptr = ptr::null();
let mut mutable_globals_ptr = ptr::null_mut(); if !$name.is_empty() {
if !mutable_globals.is_empty() { $name.push(ptr::null());
mutable_globals.push(ptr::null()); $name_ptr = $name.as_ptr();
mutable_globals_ptr = mutable_globals.as_mut_ptr(); }
};
} }
vec2cstring_ptr!(mutable_globals, mutable_globals_ptr);
vec2cstring_ptr!(userdata_types, userdata_types_ptr);
unsafe { unsafe {
let options = ffi::lua_CompileOptions { let mut options = ffi::lua_CompileOptions::default();
optimizationLevel: self.optimization_level as c_int, options.optimizationLevel = self.optimization_level as c_int;
debugLevel: self.debug_level as c_int, options.debugLevel = self.debug_level as c_int;
coverageLevel: self.coverage_level as c_int, options.typeInfoLevel = self.type_info_level as c_int;
vectorLib: vector_lib.map_or(ptr::null(), |s| s.as_ptr()), options.coverageLevel = self.coverage_level as c_int;
vectorCtor: vector_ctor.map_or(ptr::null(), |s| s.as_ptr()), options.vectorLib = vector_lib.map_or(ptr::null(), |s| s.as_ptr());
mutableGlobals: mutable_globals_ptr, options.vectorCtor = vector_ctor.map_or(ptr::null(), |s| s.as_ptr());
}; options.vectorType = vector_type.map_or(ptr::null(), |s| s.as_ptr());
options.mutableGlobals = mutable_globals_ptr;
options.userdataTypes = userdata_types_ptr;
ffi::luau_compile(source.as_ref(), options) ffi::luau_compile(source.as_ref(), options)
} }
} }
@@ -237,16 +292,14 @@ impl Compiler {
impl<'lua, 'a> Chunk<'lua, 'a> { impl<'lua, 'a> Chunk<'lua, 'a> {
/// Sets the name of this chunk, which results in more informative error traces. /// Sets the name of this chunk, which results in more informative error traces.
pub fn set_name(mut self, name: impl AsRef<str>) -> Result<Self> { pub fn set_name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.as_ref().to_string()); self.name = name.into();
// Do extra validation self
let _ = self.convert_name()?;
Ok(self)
} }
/// Sets the first upvalue (`_ENV`) of the loaded chunk to the given value. /// Sets the environment of the loaded chunk to the given value.
/// ///
/// Lua main chunks always have exactly one upvalue, and this upvalue is used as the `_ENV` /// In Lua >=5.2 main chunks always have exactly one upvalue, and this upvalue is used as the `_ENV`
/// variable inside the chunk. By default this value is set to the global environment. /// variable inside the chunk. By default this value is set to the global environment.
/// ///
/// Calling this method changes the `_ENV` upvalue to the value provided, and variables inside /// Calling this method changes the `_ENV` upvalue to the value provided, and variables inside
@@ -255,10 +308,12 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
/// All global variables (including the standard library!) are looked up in `_ENV`, so it may be /// All global variables (including the standard library!) are looked up in `_ENV`, so it may be
/// necessary to populate the environment in order for scripts using custom environments to be /// necessary to populate the environment in order for scripts using custom environments to be
/// useful. /// useful.
pub fn set_environment<V: ToLua<'lua>>(mut self, env: V) -> Result<Self> { pub fn set_environment<V: IntoLua<'lua>>(mut self, env: V) -> Self {
// Prefer to propagate errors here and wrap to `Ok` self.env = env
self.env = Ok(Some(env.to_lua(self.lua)?)); .into_lua(self.lua)
Ok(self) .and_then(|val| self.lua.unpack(val))
.context("bad environment value");
self
} }
/// Sets whether the chunk is text or binary (autodetected by default). /// Sets whether the chunk is text or binary (autodetected by default).
@@ -286,7 +341,7 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
/// ///
/// This is equivalent to calling the chunk function with no arguments and no return values. /// This is equivalent to calling the chunk function with no arguments and no return values.
pub fn exec(self) -> Result<()> { pub fn exec(self) -> Result<()> {
self.call(())?; self.call::<_, ()>(())?;
Ok(()) Ok(())
} }
@@ -299,11 +354,8 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
/// [`exec`]: #method.exec /// [`exec`]: #method.exec
#[cfg(feature = "async")] #[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))] #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn exec_async<'fut>(self) -> LocalBoxFuture<'fut, Result<()>> pub async fn exec_async(self) -> Result<()> {
where self.call_async(()).await
'lua: 'fut,
{
self.call_async(())
} }
/// Evaluate the chunk as either an expression or block. /// Evaluate the chunk as either an expression or block.
@@ -334,24 +386,23 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
/// [`eval`]: #method.eval /// [`eval`]: #method.eval
#[cfg(feature = "async")] #[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(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 where
'lua: 'fut, R: FromLuaMulti<'lua> + 'lua,
R: FromLuaMulti<'lua> + 'fut,
{ {
if self.detect_mode() == ChunkMode::Binary { if self.detect_mode() == ChunkMode::Binary {
self.call_async(()) self.call_async(()).await
} else if let Ok(function) = self.to_expression() { } else if let Ok(function) = self.to_expression() {
function.call_async(()) function.call_async(()).await
} else { } else {
self.call_async(()) self.call_async(()).await
} }
} }
/// Load the chunk function and call it with the given arguments. /// Load the chunk function and call it with the given arguments.
/// ///
/// This is equivalent to `into_function` and calling the resulting function. /// This is equivalent to `into_function` and calling the resulting function.
pub fn call<A: ToLuaMulti<'lua>, R: FromLuaMulti<'lua>>(self, args: A) -> Result<R> { pub fn call<A: IntoLuaMulti<'lua>, R: FromLuaMulti<'lua>>(self, args: A) -> Result<R> {
self.into_function()?.call(args) self.into_function()?.call(args)
} }
@@ -364,16 +415,12 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
/// [`call`]: #method.call /// [`call`]: #method.call
#[cfg(feature = "async")] #[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(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 where
'lua: 'fut, A: IntoLuaMulti<'lua>,
A: ToLuaMulti<'lua>, R: FromLuaMulti<'lua> + 'lua,
R: FromLuaMulti<'lua> + 'fut,
{ {
match self.into_function() { self.into_function()?.call_async(args).await
Ok(func) => func.call_async(args),
Err(e) => Box::pin(future::err(e)),
}
} }
/// Load this chunk into a regular `Function`. /// Load this chunk into a regular `Function`.
@@ -387,9 +434,9 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
self.compile(); self.compile();
} }
let name = self.convert_name()?; let name = Self::convert_name(self.name)?;
self.lua self.lua
.load_chunk(self.source?.as_ref(), name.as_deref(), self.env?, self.mode) .load_chunk(Some(&name), self.env?, self.mode, self.source?.as_ref())
} }
/// Compiles the chunk and changes mode to binary. /// Compiles the chunk and changes mode to binary.
@@ -408,7 +455,7 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
self.mode = Some(ChunkMode::Binary); self.mode = Some(ChunkMode::Binary);
} }
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
if let Ok(func) = self.lua.load_chunk(source.as_ref(), None, None, None) { if let Ok(func) = self.lua.load_chunk(None, None, None, source.as_ref()) {
let data = func.dump(false); let data = func.dump(false);
self.source = Ok(Cow::Owned(data)); self.source = Ok(Cow::Owned(data));
self.mode = Some(ChunkMode::Binary); self.mode = Some(ChunkMode::Binary);
@@ -448,7 +495,7 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
} else { } else {
let mut cache = ChunksCache(HashMap::new()); let mut cache = ChunksCache(HashMap::new());
cache.0.insert(text_source, binary_source.as_ref().to_vec()); cache.0.insert(text_source, binary_source.as_ref().to_vec());
self.lua.set_app_data(cache); let _ = self.lua.try_set_app_data(cache);
} }
} }
} }
@@ -460,7 +507,7 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
fn to_expression(&self) -> Result<Function<'lua>> { fn to_expression(&self) -> Result<Function<'lua>> {
// We assume that mode is Text // We assume that mode is Text
let source = self.source.as_ref(); 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); let source = Self::expression_source(source);
// We don't need to compile source if no compiler options set // We don't need to compile source if no compiler options set
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
@@ -470,9 +517,9 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
.map(|c| c.compile(&source)) .map(|c| c.compile(&source))
.unwrap_or(source); .unwrap_or(source);
let name = self.convert_name()?; let name = Self::convert_name(self.name.clone())?;
self.lua self.lua
.load_chunk(&source, name.as_deref(), self.env.clone()?, None) .load_chunk(Some(&name), self.env.clone()?, None, &source)
} }
fn detect_mode(&self) -> ChunkMode { fn detect_mode(&self) -> ChunkMode {
@@ -493,12 +540,8 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
} }
} }
fn convert_name(&self) -> Result<Option<CString>> { fn convert_name(name: String) -> Result<CString> {
self.name CString::new(name).map_err(|err| Error::runtime(format!("invalid name: {err}")))
.clone()
.map(CString::new)
.transpose()
.map_err(|err| Error::RuntimeError(format!("invalid name: {err}")))
} }
fn expression_source(source: &[u8]) -> Vec<u8> { fn expression_source(source: &[u8]) -> Vec<u8> {
+528 -108
View File
@@ -1,11 +1,10 @@
#![allow(clippy::wrong_self_convention)]
use std::borrow::Cow; use std::borrow::Cow;
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::convert::TryInto;
use std::ffi::{CStr, CString}; use std::ffi::{CStr, CString};
use std::hash::{BuildHasher, Hash}; use std::hash::{BuildHasher, Hash};
use std::os::raw::c_int;
use std::string::String as StdString; use std::string::String as StdString;
use std::{slice, str};
use bstr::{BStr, BString}; use bstr::{BStr, BString};
use num_traits::cast; use num_traits::cast;
@@ -16,17 +15,35 @@ use crate::lua::Lua;
use crate::string::String; use crate::string::String;
use crate::table::Table; use crate::table::Table;
use crate::thread::Thread; use crate::thread::Thread;
use crate::types::{LightUserData, MaybeSend}; use crate::types::{LightUserData, MaybeSend, RegistryKey};
use crate::userdata::{AnyUserData, UserData}; use crate::userdata::{AnyUserData, UserData, UserDataRef, UserDataRefMut};
use crate::value::{FromLua, Nil, ToLua, Value}; use crate::value::{FromLua, IntoLua, Nil, Value};
impl<'lua> ToLua<'lua> for Value<'lua> { #[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))]
use crate::{
function::OwnedFunction, string::OwnedString, table::OwnedTable, thread::OwnedThread,
userdata::OwnedAnyUserData,
};
impl<'lua> IntoLua<'lua> for Value<'lua> {
#[inline] #[inline]
fn to_lua(self, _: &'lua Lua) -> Result<Value<'lua>> { fn into_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
Ok(self) Ok(self)
} }
} }
impl<'lua> IntoLua<'lua> for &Value<'lua> {
#[inline]
fn into_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
Ok(self.clone())
}
#[inline]
unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> {
lua.push_value_ref(self)
}
}
impl<'lua> FromLua<'lua> for Value<'lua> { impl<'lua> FromLua<'lua> for Value<'lua> {
#[inline] #[inline]
fn from_lua(lua_value: Value<'lua>, _: &'lua Lua) -> Result<Self> { fn from_lua(lua_value: Value<'lua>, _: &'lua Lua) -> Result<Self> {
@@ -34,13 +51,26 @@ impl<'lua> FromLua<'lua> for Value<'lua> {
} }
} }
impl<'lua> ToLua<'lua> for String<'lua> { impl<'lua> IntoLua<'lua> for String<'lua> {
#[inline] #[inline]
fn to_lua(self, _: &'lua Lua) -> Result<Value<'lua>> { fn into_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::String(self)) Ok(Value::String(self))
} }
} }
impl<'lua> IntoLua<'lua> for &String<'lua> {
#[inline]
fn into_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::String(self.clone()))
}
#[inline]
unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> {
lua.push_ref(&self.0);
Ok(())
}
}
impl<'lua> FromLua<'lua> for String<'lua> { impl<'lua> FromLua<'lua> for String<'lua> {
#[inline] #[inline]
fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result<String<'lua>> { fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result<String<'lua>> {
@@ -48,19 +78,65 @@ impl<'lua> FromLua<'lua> for String<'lua> {
lua.coerce_string(value)? lua.coerce_string(value)?
.ok_or_else(|| Error::FromLuaConversionError { .ok_or_else(|| Error::FromLuaConversionError {
from: ty, from: ty,
to: "String", to: "string",
message: Some("expected string or number".to_string()), message: Some("expected string or number".to_string()),
}) })
} }
} }
impl<'lua> ToLua<'lua> for Table<'lua> { #[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))]
impl<'lua> IntoLua<'lua> for OwnedString {
#[inline] #[inline]
fn to_lua(self, _: &'lua Lua) -> Result<Value<'lua>> { fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::String(String(lua.adopt_owned_ref(self.0))))
}
}
#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))]
impl<'lua> IntoLua<'lua> for &OwnedString {
#[inline]
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
OwnedString::into_lua(self.clone(), lua)
}
#[inline]
unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> {
lua.push_owned_ref(&self.0);
Ok(())
}
}
#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))]
impl<'lua> FromLua<'lua> for OwnedString {
#[inline]
fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result<OwnedString> {
String::from_lua(value, lua).map(|s| s.into_owned())
}
}
impl<'lua> IntoLua<'lua> for Table<'lua> {
#[inline]
fn into_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Table(self)) Ok(Value::Table(self))
} }
} }
impl<'lua> IntoLua<'lua> for &Table<'lua> {
#[inline]
fn into_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Table(self.clone()))
}
#[inline]
unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> {
lua.push_ref(&self.0);
Ok(())
}
}
impl<'lua> FromLua<'lua> for Table<'lua> { impl<'lua> FromLua<'lua> for Table<'lua> {
#[inline] #[inline]
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<Table<'lua>> { fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<Table<'lua>> {
@@ -75,13 +151,59 @@ impl<'lua> FromLua<'lua> for Table<'lua> {
} }
} }
impl<'lua> ToLua<'lua> for Function<'lua> { #[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))]
impl<'lua> IntoLua<'lua> for OwnedTable {
#[inline] #[inline]
fn to_lua(self, _: &'lua Lua) -> Result<Value<'lua>> { fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Table(Table(lua.adopt_owned_ref(self.0))))
}
}
#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))]
impl<'lua> IntoLua<'lua> for &OwnedTable {
#[inline]
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
OwnedTable::into_lua(self.clone(), lua)
}
#[inline]
unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> {
lua.push_owned_ref(&self.0);
Ok(())
}
}
#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))]
impl<'lua> FromLua<'lua> for OwnedTable {
#[inline]
fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result<OwnedTable> {
Table::from_lua(value, lua).map(|s| s.into_owned())
}
}
impl<'lua> IntoLua<'lua> for Function<'lua> {
#[inline]
fn into_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Function(self)) Ok(Value::Function(self))
} }
} }
impl<'lua> IntoLua<'lua> for &Function<'lua> {
#[inline]
fn into_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Function(self.clone()))
}
#[inline]
unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> {
lua.push_ref(&self.0);
Ok(())
}
}
impl<'lua> FromLua<'lua> for Function<'lua> { impl<'lua> FromLua<'lua> for Function<'lua> {
#[inline] #[inline]
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<Function<'lua>> { fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<Function<'lua>> {
@@ -96,13 +218,59 @@ impl<'lua> FromLua<'lua> for Function<'lua> {
} }
} }
impl<'lua> ToLua<'lua> for Thread<'lua> { #[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))]
impl<'lua> IntoLua<'lua> for OwnedFunction {
#[inline] #[inline]
fn to_lua(self, _: &'lua Lua) -> Result<Value<'lua>> { fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Function(Function(lua.adopt_owned_ref(self.0))))
}
}
#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))]
impl<'lua> IntoLua<'lua> for &OwnedFunction {
#[inline]
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
OwnedFunction::into_lua(self.clone(), lua)
}
#[inline]
unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> {
lua.push_owned_ref(&self.0);
Ok(())
}
}
#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))]
impl<'lua> FromLua<'lua> for OwnedFunction {
#[inline]
fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result<OwnedFunction> {
Function::from_lua(value, lua).map(|s| s.into_owned())
}
}
impl<'lua> IntoLua<'lua> for Thread<'lua> {
#[inline]
fn into_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Thread(self)) Ok(Value::Thread(self))
} }
} }
impl<'lua> IntoLua<'lua> for &Thread<'lua> {
#[inline]
fn into_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Thread(self.clone()))
}
#[inline]
unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> {
lua.push_ref(&self.0);
Ok(())
}
}
impl<'lua> FromLua<'lua> for Thread<'lua> { impl<'lua> FromLua<'lua> for Thread<'lua> {
#[inline] #[inline]
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<Thread<'lua>> { fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<Thread<'lua>> {
@@ -117,13 +285,59 @@ impl<'lua> FromLua<'lua> for Thread<'lua> {
} }
} }
impl<'lua> ToLua<'lua> for AnyUserData<'lua> { #[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))]
impl<'lua> IntoLua<'lua> for OwnedThread {
#[inline] #[inline]
fn to_lua(self, _: &'lua Lua) -> Result<Value<'lua>> { fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Thread(Thread(lua.adopt_owned_ref(self.0), self.1)))
}
}
#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))]
impl<'lua> IntoLua<'lua> for &OwnedThread {
#[inline]
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
OwnedThread::into_lua(self.clone(), lua)
}
#[inline]
unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> {
lua.push_owned_ref(&self.0);
Ok(())
}
}
#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))]
impl<'lua> FromLua<'lua> for OwnedThread {
#[inline]
fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result<OwnedThread> {
Thread::from_lua(value, lua).map(|s| s.into_owned())
}
}
impl<'lua> IntoLua<'lua> for AnyUserData<'lua> {
#[inline]
fn into_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::UserData(self)) Ok(Value::UserData(self))
} }
} }
impl<'lua> IntoLua<'lua> for &AnyUserData<'lua> {
#[inline]
fn into_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::UserData(self.clone()))
}
#[inline]
unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> {
lua.push_ref(&self.0);
Ok(())
}
}
impl<'lua> FromLua<'lua> for AnyUserData<'lua> { impl<'lua> FromLua<'lua> for AnyUserData<'lua> {
#[inline] #[inline]
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<AnyUserData<'lua>> { fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<AnyUserData<'lua>> {
@@ -138,30 +352,66 @@ impl<'lua> FromLua<'lua> for AnyUserData<'lua> {
} }
} }
impl<'lua, T: 'static + MaybeSend + UserData> ToLua<'lua> for T { #[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))]
impl<'lua> IntoLua<'lua> for OwnedAnyUserData {
#[inline] #[inline]
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> { fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::UserData(AnyUserData(
lua.adopt_owned_ref(self.0),
self.1,
)))
}
}
#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))]
impl<'lua> IntoLua<'lua> for &OwnedAnyUserData {
#[inline]
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
OwnedAnyUserData::into_lua(self.clone(), lua)
}
#[inline]
unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> {
lua.push_owned_ref(&self.0);
Ok(())
}
}
#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))]
impl<'lua> FromLua<'lua> for OwnedAnyUserData {
#[inline]
fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result<OwnedAnyUserData> {
AnyUserData::from_lua(value, lua).map(|s| s.into_owned())
}
}
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)?)) Ok(Value::UserData(lua.create_userdata(self)?))
} }
} }
impl<'lua, T: 'static + UserData + Clone> FromLua<'lua> for T { impl<'lua, T: 'static> FromLua<'lua> for UserDataRef<'lua, T> {
#[inline] #[inline]
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<T> { fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<Self> {
match value { Self::from_value(value)
Value::UserData(ud) => Ok(ud.borrow::<T>()?.clone()),
_ => Err(Error::FromLuaConversionError {
from: value.type_name(),
to: "userdata",
message: None,
}),
}
} }
} }
impl<'lua> ToLua<'lua> for Error { impl<'lua, T: 'static> FromLua<'lua> for UserDataRefMut<'lua, T> {
#[inline] #[inline]
fn to_lua(self, _: &'lua Lua) -> Result<Value<'lua>> { fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<Self> {
Self::from_value(value)
}
}
impl<'lua> IntoLua<'lua> for Error {
#[inline]
fn into_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Error(self)) Ok(Value::Error(self))
} }
} }
@@ -171,7 +421,7 @@ impl<'lua> FromLua<'lua> for Error {
fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result<Error> { fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result<Error> {
match value { match value {
Value::Error(err) => Ok(err), Value::Error(err) => Ok(err),
val => Ok(Error::RuntimeError( val => Ok(Error::runtime(
lua.coerce_string(val)? lua.coerce_string(val)?
.and_then(|s| Some(s.to_str().ok()?.to_owned())) .and_then(|s| Some(s.to_str().ok()?.to_owned()))
.unwrap_or_else(|| "<unprintable error>".to_owned()), .unwrap_or_else(|| "<unprintable error>".to_owned()),
@@ -180,11 +430,57 @@ impl<'lua> FromLua<'lua> for Error {
} }
} }
impl<'lua> ToLua<'lua> for bool { impl<'lua> IntoLua<'lua> for RegistryKey {
#[inline] #[inline]
fn to_lua(self, _: &'lua Lua) -> Result<Value<'lua>> { fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
lua.registry_value(&self)
}
#[inline]
unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> {
<&RegistryKey>::push_into_stack(&self, lua)
}
}
impl<'lua> IntoLua<'lua> for &RegistryKey {
#[inline]
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
lua.registry_value(self)
}
unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> {
if !lua.owns_registry_value(self) {
return Err(Error::MismatchedRegistryKey);
}
match self.id() {
ffi::LUA_REFNIL => ffi::lua_pushnil(lua.state()),
id => {
ffi::lua_rawgeti(lua.state(), ffi::LUA_REGISTRYINDEX, id as _);
}
}
Ok(())
}
}
impl<'lua> FromLua<'lua> for RegistryKey {
#[inline]
fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result<RegistryKey> {
lua.create_registry_value(value)
}
}
impl<'lua> IntoLua<'lua> for bool {
#[inline]
fn into_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Boolean(self)) Ok(Value::Boolean(self))
} }
#[inline]
unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> {
ffi::lua_pushboolean(lua.state(), self as c_int);
Ok(())
}
} }
impl<'lua> FromLua<'lua> for bool { impl<'lua> FromLua<'lua> for bool {
@@ -196,11 +492,16 @@ impl<'lua> FromLua<'lua> for bool {
_ => Ok(true), _ => Ok(true),
} }
} }
#[inline]
unsafe fn from_stack(idx: c_int, lua: &'lua Lua) -> Result<Self> {
Ok(ffi::lua_toboolean(lua.state(), idx) != 0)
}
} }
impl<'lua> ToLua<'lua> for LightUserData { impl<'lua> IntoLua<'lua> for LightUserData {
#[inline] #[inline]
fn to_lua(self, _: &'lua Lua) -> Result<Value<'lua>> { fn into_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::LightUserData(self)) Ok(Value::LightUserData(self))
} }
} }
@@ -219,11 +520,39 @@ impl<'lua> FromLua<'lua> for LightUserData {
} }
} }
impl<'lua> ToLua<'lua> for StdString { #[cfg(feature = "luau")]
impl<'lua> IntoLua<'lua> for crate::types::Vector {
#[inline] #[inline]
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> { 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)?)) 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 { impl<'lua> FromLua<'lua> for StdString {
@@ -240,25 +569,49 @@ impl<'lua> FromLua<'lua> for StdString {
.to_str()? .to_str()?
.to_owned()) .to_owned())
} }
}
impl<'lua> ToLua<'lua> for &str {
#[inline] #[inline]
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> { unsafe fn from_stack(idx: c_int, lua: &'lua Lua) -> Result<Self> {
Ok(Value::String(lua.create_string(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> ToLua<'lua> for Cow<'_, str> { impl<'lua> IntoLua<'lua> for &str {
#[inline] #[inline]
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> { 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> {
#[inline]
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::String(lua.create_string(self.as_bytes())?)) Ok(Value::String(lua.create_string(self.as_bytes())?))
} }
} }
impl<'lua> ToLua<'lua> for Box<str> { impl<'lua> IntoLua<'lua> for Box<str> {
#[inline] #[inline]
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> { fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::String(lua.create_string(&*self)?)) Ok(Value::String(lua.create_string(&*self)?))
} }
} }
@@ -280,9 +633,9 @@ impl<'lua> FromLua<'lua> for Box<str> {
} }
} }
impl<'lua> ToLua<'lua> for CString { impl<'lua> IntoLua<'lua> for CString {
#[inline] #[inline]
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> { fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::String(lua.create_string(self.as_bytes())?)) Ok(Value::String(lua.create_string(self.as_bytes())?))
} }
} }
@@ -310,56 +663,101 @@ impl<'lua> FromLua<'lua> for CString {
} }
} }
impl<'lua> ToLua<'lua> for &CStr { impl<'lua> IntoLua<'lua> for &CStr {
#[inline] #[inline]
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> { fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::String(lua.create_string(self.to_bytes())?)) Ok(Value::String(lua.create_string(self.to_bytes())?))
} }
} }
impl<'lua> ToLua<'lua> for Cow<'_, CStr> { impl<'lua> IntoLua<'lua> for Cow<'_, CStr> {
#[inline] #[inline]
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> { fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::String(lua.create_string(self.to_bytes())?)) Ok(Value::String(lua.create_string(self.to_bytes())?))
} }
} }
impl<'lua> ToLua<'lua> for BString { impl<'lua> IntoLua<'lua> for BString {
#[inline] #[inline]
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> { fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::String(lua.create_string(&self)?)) Ok(Value::String(lua.create_string(&self)?))
} }
} }
impl<'lua> FromLua<'lua> for BString { impl<'lua> FromLua<'lua> for BString {
#[inline]
fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result<Self> { fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result<Self> {
let ty = value.type_name(); let ty = value.type_name();
Ok(BString::from( match value {
lua.coerce_string(value)? Value::String(s) => Ok(s.as_bytes().into()),
#[cfg(feature = "luau")]
Value::UserData(ud) if ud.1 == crate::types::SubtypeId::Buffer => unsafe {
let mut size = 0usize;
let buf = ffi::lua_tobuffer(ud.0.lua.ref_thread(), ud.0.index, &mut size);
mlua_assert!(!buf.is_null(), "invalid Luau buffer");
Ok(slice::from_raw_parts(buf as *const u8, size).into())
},
_ => Ok(lua
.coerce_string(value)?
.ok_or_else(|| Error::FromLuaConversionError { .ok_or_else(|| Error::FromLuaConversionError {
from: ty, from: ty,
to: "String", to: "BString",
message: Some("expected string or number".to_string()), message: Some("expected string or number".to_string()),
})? })?
.as_bytes() .as_bytes()
.to_vec(), .into()),
)) }
}
unsafe fn from_stack(idx: c_int, lua: &'lua Lua) -> Result<Self> {
let state = lua.state();
match ffi::lua_type(state, idx) {
ffi::LUA_TSTRING => {
let mut size = 0;
let data = ffi::lua_tolstring(state, idx, &mut size);
Ok(slice::from_raw_parts(data as *const u8, size).into())
}
#[cfg(feature = "luau")]
ffi::LUA_TBUFFER => {
let mut size = 0;
let buf = ffi::lua_tobuffer(state, idx, &mut size);
mlua_assert!(!buf.is_null(), "invalid Luau buffer");
Ok(slice::from_raw_parts(buf as *const u8, size).into())
}
_ => {
// Fallback to default
Self::from_lua(lua.stack_value(idx), lua)
}
}
} }
} }
impl<'lua> ToLua<'lua> for &BStr { impl<'lua> IntoLua<'lua> for &BStr {
#[inline] #[inline]
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> { fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::String(lua.create_string(self)?)) Ok(Value::String(lua.create_string(self)?))
} }
} }
#[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 { macro_rules! lua_convert_int {
($x:ty) => { ($x:ty) => {
impl<'lua> ToLua<'lua> for $x { impl<'lua> IntoLua<'lua> for $x {
#[inline] #[inline]
fn to_lua(self, _: &'lua Lua) -> Result<Value<'lua>> { fn into_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
cast(self) cast(self)
.map(Value::Integer) .map(Value::Integer)
.or_else(|| cast(self).map(Value::Number)) .or_else(|| cast(self).map(Value::Number))
@@ -370,6 +768,15 @@ macro_rules! lua_convert_int {
message: Some("out of range".to_owned()), message: Some("out of range".to_owned()),
}) })
} }
#[inline]
unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> {
match cast(self) {
Some(i) => ffi::lua_pushinteger(lua.state(), i),
None => ffi::lua_pushnumber(lua.state(), self as ffi::lua_Number),
}
Ok(())
}
} }
impl<'lua> FromLua<'lua> for $x { impl<'lua> FromLua<'lua> for $x {
@@ -420,9 +827,9 @@ lua_convert_int!(usize);
macro_rules! lua_convert_float { macro_rules! lua_convert_float {
($x:ty) => { ($x:ty) => {
impl<'lua> ToLua<'lua> for $x { impl<'lua> IntoLua<'lua> for $x {
#[inline] #[inline]
fn to_lua(self, _: &'lua Lua) -> Result<Value<'lua>> { fn into_lua(self, _: &'lua Lua) -> Result<Value<'lua>> {
cast(self) cast(self)
.ok_or_else(|| Error::ToLuaConversionError { .ok_or_else(|| Error::ToLuaConversionError {
from: stringify!($x), from: stringify!($x),
@@ -458,24 +865,24 @@ macro_rules! lua_convert_float {
lua_convert_float!(f32); lua_convert_float!(f32);
lua_convert_float!(f64); lua_convert_float!(f64);
impl<'lua, T> ToLua<'lua> for &[T] impl<'lua, T> IntoLua<'lua> for &[T]
where where
T: Clone + ToLua<'lua>, T: IntoLua<'lua> + Clone,
{ {
#[inline] #[inline]
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> { fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Table( Ok(Value::Table(
lua.create_sequence_from(self.iter().cloned())?, lua.create_sequence_from(self.iter().cloned())?,
)) ))
} }
} }
impl<'lua, T, const N: usize> ToLua<'lua> for [T; N] impl<'lua, T, const N: usize> IntoLua<'lua> for [T; N]
where where
T: ToLua<'lua>, T: IntoLua<'lua>,
{ {
#[inline] #[inline]
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> { fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Table(lua.create_sequence_from(self)?)) Ok(Value::Table(lua.create_sequence_from(self)?))
} }
} }
@@ -488,21 +895,22 @@ where
fn from_lua(value: Value<'lua>, _lua: &'lua Lua) -> Result<Self> { fn from_lua(value: Value<'lua>, _lua: &'lua Lua) -> Result<Self> {
match value { match value {
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
Value::Vector(x, y, z) if N == 3 => Ok(mlua_expect!( #[rustfmt::skip]
vec![ Value::Vector(v) if N == crate::types::Vector::SIZE => unsafe {
T::from_lua(Value::Number(x as _), _lua)?, use std::{mem, ptr};
T::from_lua(Value::Number(y as _), _lua)?, let mut arr: [mem::MaybeUninit<T>; N] = mem::MaybeUninit::uninit().assume_init();
T::from_lua(Value::Number(z as _), _lua)?, 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)?);
.try_into() ptr::write(arr[2].as_mut_ptr(), T::from_lua(Value::Number(v.z() as _), _lua)?);
.map_err(|_| ()), #[cfg(feature = "luau-vector4")]
"cannot convert vector to array" ptr::write(arr[3].as_mut_ptr(), T::from_lua(Value::Number(v.w() as _), _lua)?);
)), Ok(mem::transmute_copy(&arr))
},
Value::Table(table) => { Value::Table(table) => {
let vec = table.sequence_values().collect::<Result<Vec<_>>>()?; let vec = table.sequence_values().collect::<Result<Vec<_>>>()?;
vec.try_into() vec.try_into()
.map_err(|vec: Vec<T>| Error::FromLuaConversionError { .map_err(|vec: Vec<T>| Error::FromLuaConversionError {
from: "Table", from: "table",
to: "Array", to: "Array",
message: Some(format!("expected table of length {}, got {}", N, vec.len())), message: Some(format!("expected table of length {}, got {}", N, vec.len())),
}) })
@@ -516,9 +924,9 @@ where
} }
} }
impl<'lua, T: ToLua<'lua>> ToLua<'lua> for Box<[T]> { impl<'lua, T: IntoLua<'lua>> IntoLua<'lua> for Box<[T]> {
#[inline] #[inline]
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> { fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Table(lua.create_sequence_from(self.into_vec())?)) Ok(Value::Table(lua.create_sequence_from(self.into_vec())?))
} }
} }
@@ -530,9 +938,9 @@ impl<'lua, T: FromLua<'lua>> FromLua<'lua> for Box<[T]> {
} }
} }
impl<'lua, T: ToLua<'lua>> ToLua<'lua> for Vec<T> { impl<'lua, T: IntoLua<'lua>> IntoLua<'lua> for Vec<T> {
#[inline] #[inline]
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> { fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Table(lua.create_sequence_from(self)?)) Ok(Value::Table(lua.create_sequence_from(self)?))
} }
} }
@@ -541,12 +949,6 @@ impl<'lua, T: FromLua<'lua>> FromLua<'lua> for Vec<T> {
#[inline] #[inline]
fn from_lua(value: Value<'lua>, _lua: &'lua Lua) -> Result<Self> { fn from_lua(value: Value<'lua>, _lua: &'lua Lua) -> Result<Self> {
match value { 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(), Value::Table(table) => table.sequence_values().collect(),
_ => Err(Error::FromLuaConversionError { _ => Err(Error::FromLuaConversionError {
from: value.type_name(), from: value.type_name(),
@@ -557,11 +959,11 @@ impl<'lua, T: FromLua<'lua>> FromLua<'lua> for Vec<T> {
} }
} }
impl<'lua, K: Eq + Hash + ToLua<'lua>, V: ToLua<'lua>, S: BuildHasher> ToLua<'lua> impl<'lua, K: Eq + Hash + IntoLua<'lua>, V: IntoLua<'lua>, S: BuildHasher> IntoLua<'lua>
for HashMap<K, V, S> for HashMap<K, V, S>
{ {
#[inline] #[inline]
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> { fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Table(lua.create_table_from(self)?)) Ok(Value::Table(lua.create_table_from(self)?))
} }
} }
@@ -583,9 +985,9 @@ impl<'lua, K: Eq + Hash + FromLua<'lua>, V: FromLua<'lua>, S: BuildHasher + Defa
} }
} }
impl<'lua, K: Ord + ToLua<'lua>, V: ToLua<'lua>> ToLua<'lua> for BTreeMap<K, V> { impl<'lua, K: Ord + IntoLua<'lua>, V: IntoLua<'lua>> IntoLua<'lua> for BTreeMap<K, V> {
#[inline] #[inline]
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> { fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Table(lua.create_table_from(self)?)) Ok(Value::Table(lua.create_table_from(self)?))
} }
} }
@@ -605,9 +1007,9 @@ impl<'lua, K: Ord + FromLua<'lua>, V: FromLua<'lua>> FromLua<'lua> for BTreeMap<
} }
} }
impl<'lua, T: Eq + Hash + ToLua<'lua>, S: BuildHasher> ToLua<'lua> for HashSet<T, S> { impl<'lua, T: Eq + Hash + IntoLua<'lua>, S: BuildHasher> IntoLua<'lua> for HashSet<T, S> {
#[inline] #[inline]
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> { fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Table(lua.create_table_from( Ok(Value::Table(lua.create_table_from(
self.into_iter().map(|val| (val, true)), self.into_iter().map(|val| (val, true)),
)?)) )?))
@@ -618,7 +1020,7 @@ impl<'lua, T: Eq + Hash + FromLua<'lua>, S: BuildHasher + Default> FromLua<'lua>
#[inline] #[inline]
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<Self> { fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<Self> {
match value { 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 Value::Table(table) => table
.pairs::<T, Value<'lua>>() .pairs::<T, Value<'lua>>()
.map(|res| res.map(|(k, _)| k)) .map(|res| res.map(|(k, _)| k))
@@ -632,9 +1034,9 @@ impl<'lua, T: Eq + Hash + FromLua<'lua>, S: BuildHasher + Default> FromLua<'lua>
} }
} }
impl<'lua, T: Ord + ToLua<'lua>> ToLua<'lua> for BTreeSet<T> { impl<'lua, T: Ord + IntoLua<'lua>> IntoLua<'lua> for BTreeSet<T> {
#[inline] #[inline]
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> { fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
Ok(Value::Table(lua.create_table_from( Ok(Value::Table(lua.create_table_from(
self.into_iter().map(|val| (val, true)), self.into_iter().map(|val| (val, true)),
)?)) )?))
@@ -645,7 +1047,7 @@ impl<'lua, T: Ord + FromLua<'lua>> FromLua<'lua> for BTreeSet<T> {
#[inline] #[inline]
fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<Self> { fn from_lua(value: Value<'lua>, _: &'lua Lua) -> Result<Self> {
match value { 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 Value::Table(table) => table
.pairs::<T, Value<'lua>>() .pairs::<T, Value<'lua>>()
.map(|res| res.map(|(k, _)| k)) .map(|res| res.map(|(k, _)| k))
@@ -659,14 +1061,23 @@ impl<'lua, T: Ord + FromLua<'lua>> FromLua<'lua> for BTreeSet<T> {
} }
} }
impl<'lua, T: ToLua<'lua>> ToLua<'lua> for Option<T> { impl<'lua, T: IntoLua<'lua>> IntoLua<'lua> for Option<T> {
#[inline] #[inline]
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> { fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
match self { match self {
Some(val) => val.to_lua(lua), Some(val) => val.into_lua(lua),
None => Ok(Nil), None => Ok(Nil),
} }
} }
#[inline]
unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> {
match self {
Some(val) => val.push_into_stack(lua)?,
None => ffi::lua_pushnil(lua.state()),
}
Ok(())
}
} }
impl<'lua, T: FromLua<'lua>> FromLua<'lua> for Option<T> { impl<'lua, T: FromLua<'lua>> FromLua<'lua> for Option<T> {
@@ -677,4 +1088,13 @@ impl<'lua, T: FromLua<'lua>> FromLua<'lua> for Option<T> {
value => Ok(Some(T::from_lua(value, lua)?)), value => Ok(Some(T::from_lua(value, lua)?)),
} }
} }
#[inline]
unsafe fn from_stack(idx: c_int, lua: &'lua Lua) -> Result<Self> {
if ffi::lua_isnil(lua.state(), idx) != 0 {
Ok(None)
} else {
Ok(Some(T::from_stack(idx, lua)?))
}
}
} }
+167 -42
View File
@@ -1,5 +1,3 @@
#![allow(clippy::wrong_self_convention)]
use std::error::Error as StdError; use std::error::Error as StdError;
use std::fmt; use std::fmt;
use std::io::Error as IoError; use std::io::Error as IoError;
@@ -9,6 +7,8 @@ use std::str::Utf8Error;
use std::string::String as StdString; use std::string::String as StdString;
use std::sync::Arc; use std::sync::Arc;
use crate::private::Sealed;
/// Error type returned by `mlua` methods. /// Error type returned by `mlua` methods.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
#[non_exhaustive] #[non_exhaustive]
@@ -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 /// This error can only happen when Lua state was not created by us and does not have the
/// custom allocator attached. /// custom allocator attached.
MemoryLimitNotAvailable, 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. /// 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. /// This is an error because a mutable callback can only be borrowed mutably once.
@@ -69,8 +64,22 @@ pub enum Error {
/// called with a huge number of arguments, or a rust callback returns a huge number of return /// called with a huge number of arguments, or a rust callback returns a huge number of return
/// values. /// values.
StackError, StackError,
/// Too many arguments to `Function::bind` /// Too many arguments to `Function::bind`.
BindError, BindError,
/// Bad argument received from Lua (usually when calling a function).
///
/// This error can help to identify the argument that caused the error
/// (which is stored in the corresponding field).
BadArgument {
/// Function that was called.
to: Option<StdString>,
/// Argument position (usually starts from 1).
pos: usize,
/// Argument name.
name: Option<StdString>,
/// Underlying error returned when converting argument to a Lua value.
cause: Arc<Error>,
},
/// A Rust value could not be converted to a Lua value. /// A Rust value could not be converted to a Lua value.
ToLuaConversionError { ToLuaConversionError {
/// Name of the Rust type that could not be converted. /// Name of the Rust type that could not be converted.
@@ -92,7 +101,7 @@ pub enum Error {
/// [`Thread::resume`] was called on an inactive coroutine. /// [`Thread::resume`] was called on an inactive coroutine.
/// ///
/// A coroutine is inactive if its main function has returned or if an error has occurred inside /// A coroutine is inactive if its main function has returned or if an error has occurred inside
/// the coroutine. /// the coroutine. Already running coroutines are also marked as inactive (unresumable).
/// ///
/// [`Thread::status`] can be used to check if the coroutine can be resumed without causing this /// [`Thread::status`] can be used to check if the coroutine can be resumed without causing this
/// error. /// error.
@@ -116,7 +125,7 @@ pub enum Error {
/// ///
/// [`AnyUserData`]: crate::AnyUserData /// [`AnyUserData`]: crate::AnyUserData
UserDataDestructed, 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 /// 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 /// tries to call a method on the same [`UserData`] type. Consider restructuring your API to
@@ -125,7 +134,7 @@ pub enum Error {
/// [`AnyUserData`]: crate::AnyUserData /// [`AnyUserData`]: crate::AnyUserData
/// [`UserData`]: crate::UserData /// [`UserData`]: crate::UserData
UserDataBorrowError, 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 /// 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 /// tries to call a method on the same [`UserData`] type. Consider restructuring your API to
@@ -142,8 +151,11 @@ pub enum Error {
/// ///
/// [`MetaMethod`]: crate::MetaMethod /// [`MetaMethod`]: crate::MetaMethod
MetaMethodTypeError { MetaMethodTypeError {
/// Name of the metamethod.
method: StdString, method: StdString,
/// Passed value type.
type_name: &'static str, type_name: &'static str,
/// A string containing more detailed error information.
message: Option<StdString>, message: Option<StdString>,
}, },
/// A [`RegistryKey`] produced from a different Lua state was used. /// A [`RegistryKey`] produced from a different Lua state was used.
@@ -178,6 +190,13 @@ pub enum Error {
/// error. The Rust code that originally invoked the Lua code then receives a `CallbackError`, /// error. The Rust code that originally invoked the Lua code then receives a `CallbackError`,
/// from which the original error (and a stack traceback) can be recovered. /// from which the original error (and a stack traceback) can be recovered.
ExternalError(Arc<dyn StdError + Send + Sync>), ExternalError(Arc<dyn StdError + Send + Sync>),
/// An error with additional context.
WithContext {
/// A string containing additional context.
context: StdString,
/// Underlying error.
cause: Arc<Error>,
},
} }
/// A specialized `Result` type used by `mlua`'s API. /// A specialized `Result` type used by `mlua`'s API.
@@ -187,24 +206,21 @@ pub type Result<T> = StdResult<T, Error>;
impl fmt::Display for Error { impl fmt::Display for Error {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match *self { match *self {
Error::SyntaxError { ref message, .. } => write!(fmt, "syntax error: {}", message), Error::SyntaxError { ref message, .. } => write!(fmt, "syntax error: {message}"),
Error::RuntimeError(ref msg) => write!(fmt, "runtime error: {}", msg), Error::RuntimeError(ref msg) => write!(fmt, "runtime error: {msg}"),
Error::MemoryError(ref msg) => { Error::MemoryError(ref msg) => {
write!(fmt, "memory error: {}", msg) write!(fmt, "memory error: {msg}")
} }
#[cfg(any(feature = "lua53", feature = "lua52"))] #[cfg(any(feature = "lua53", feature = "lua52"))]
Error::GarbageCollectorError(ref msg) => { Error::GarbageCollectorError(ref msg) => {
write!(fmt, "garbage collector error: {}", msg) write!(fmt, "garbage collector error: {msg}")
} }
Error::SafetyError(ref msg) => { Error::SafetyError(ref msg) => {
write!(fmt, "safety error: {}", msg) write!(fmt, "safety error: {msg}")
}, },
Error::MemoryLimitNotAvailable => { Error::MemoryLimitNotAvailable => {
write!(fmt, "setting memory limit is not available") 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::RecursiveMutCallback => write!(fmt, "mutable callback called recursively"),
Error::CallbackDestructed => write!( Error::CallbackDestructed => write!(
fmt, fmt,
@@ -218,44 +234,55 @@ impl fmt::Display for Error {
fmt, fmt,
"too many arguments to Function::bind" "too many arguments to Function::bind"
), ),
Error::BadArgument { ref to, pos, ref name, ref cause } => {
if let Some(name) = name {
write!(fmt, "bad argument `{name}`")?;
} else {
write!(fmt, "bad argument #{pos}")?;
}
if let Some(to) = to {
write!(fmt, " to `{to}`")?;
}
write!(fmt, ": {cause}")
},
Error::ToLuaConversionError { from, to, ref message } => { Error::ToLuaConversionError { from, to, ref message } => {
write!(fmt, "error converting {} to Lua {}", from, to)?; write!(fmt, "error converting {from} to Lua {to}")?;
match *message { match *message {
None => Ok(()), None => Ok(()),
Some(ref message) => write!(fmt, " ({})", message), Some(ref message) => write!(fmt, " ({message})"),
} }
} }
Error::FromLuaConversionError { from, to, ref message } => { Error::FromLuaConversionError { from, to, ref message } => {
write!(fmt, "error converting Lua {} to {}", from, to)?; write!(fmt, "error converting Lua {from} to {to}")?;
match *message { match *message {
None => Ok(()), None => Ok(()),
Some(ref message) => write!(fmt, " ({})", message), Some(ref message) => write!(fmt, " ({message})"),
} }
} }
Error::CoroutineInactive => write!(fmt, "cannot resume inactive coroutine"), Error::CoroutineInactive => write!(fmt, "cannot resume inactive coroutine"),
Error::UserDataTypeMismatch => write!(fmt, "userdata is not expected type"), Error::UserDataTypeMismatch => write!(fmt, "userdata is not expected type"),
Error::UserDataDestructed => write!(fmt, "userdata has been destructed"), Error::UserDataDestructed => write!(fmt, "userdata has been destructed"),
Error::UserDataBorrowError => write!(fmt, "userdata already mutably borrowed"), Error::UserDataBorrowError => write!(fmt, "error borrowing userdata"),
Error::UserDataBorrowMutError => write!(fmt, "userdata already borrowed"), Error::UserDataBorrowMutError => write!(fmt, "error mutably borrowing userdata"),
Error::MetaMethodRestricted(ref method) => write!(fmt, "metamethod {} is restricted", method), Error::MetaMethodRestricted(ref method) => write!(fmt, "metamethod {method} is restricted"),
Error::MetaMethodTypeError { ref method, type_name, ref message } => { Error::MetaMethodTypeError { ref method, type_name, ref message } => {
write!(fmt, "metamethod {} has unsupported type {}", method, type_name)?; write!(fmt, "metamethod {method} has unsupported type {type_name}")?;
match *message { match *message {
None => Ok(()), None => Ok(()),
Some(ref message) => write!(fmt, " ({})", message), Some(ref message) => write!(fmt, " ({message})"),
} }
} }
Error::MismatchedRegistryKey => { Error::MismatchedRegistryKey => {
write!(fmt, "RegistryKey used from different Lua state") write!(fmt, "RegistryKey used from different Lua state")
} }
Error::CallbackError { ref cause, ref traceback } => { Error::CallbackError { ref cause, ref traceback } => {
writeln!(fmt, "callback error")?;
// Trace errors down to the root // Trace errors down to the root
let (mut cause, mut full_traceback) = (cause, None); let (mut cause, mut full_traceback) = (cause, None);
while let Error::CallbackError { cause: ref cause2, traceback: ref traceback2 } = **cause { while let Error::CallbackError { cause: ref cause2, traceback: ref traceback2 } = **cause {
cause = cause2; cause = cause2;
full_traceback = Some(traceback2); full_traceback = Some(traceback2);
} }
writeln!(fmt, "{cause}")?;
if let Some(full_traceback) = full_traceback { if let Some(full_traceback) = full_traceback {
let traceback = traceback.trim_start_matches("stack traceback:"); let traceback = traceback.trim_start_matches("stack traceback:");
let traceback = traceback.trim_start().trim_end(); let traceback = traceback.trim_start().trim_end();
@@ -269,20 +296,24 @@ impl fmt::Display for Error {
} else { } else {
writeln!(fmt, "{}", traceback.trim_end())?; writeln!(fmt, "{}", traceback.trim_end())?;
} }
write!(fmt, "caused by: {}", cause) Ok(())
} }
Error::PreviouslyResumedPanic => { Error::PreviouslyResumedPanic => {
write!(fmt, "previously resumed panic returned again") write!(fmt, "previously resumed panic returned again")
} }
#[cfg(feature = "serialize")] #[cfg(feature = "serialize")]
Error::SerializeError(ref err) => { Error::SerializeError(ref err) => {
write!(fmt, "serialize error: {}", err) write!(fmt, "serialize error: {err}")
}, },
#[cfg(feature = "serialize")] #[cfg(feature = "serialize")]
Error::DeserializeError(ref err) => { Error::DeserializeError(ref err) => {
write!(fmt, "deserialize error: {}", err) write!(fmt, "deserialize error: {err}")
}, },
Error::ExternalError(ref err) => write!(fmt, "{}", err), Error::ExternalError(ref err) => write!(fmt, "{err}"),
Error::WithContext { ref context, ref cause } => {
writeln!(fmt, "{context}")?;
write!(fmt, "{cause}")
}
} }
} }
} }
@@ -296,53 +327,147 @@ impl StdError for Error {
// Given that we include source to fmt::Display implementation for `CallbackError`, this call returns nothing. // Given that we include source to fmt::Display implementation for `CallbackError`, this call returns nothing.
Error::CallbackError { .. } => None, Error::CallbackError { .. } => None,
Error::ExternalError(ref err) => err.source(), Error::ExternalError(ref err) => err.source(),
Error::WithContext { ref cause, .. } => match cause.as_ref() {
Error::ExternalError(err) => err.source(),
_ => None,
},
_ => None, _ => None,
} }
} }
} }
impl Error { impl Error {
pub fn external<T: Into<Box<dyn StdError + Send + Sync>>>(err: T) -> 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()) Error::ExternalError(err.into().into())
} }
/// Attempts to downcast the external error object to a concrete type by reference.
pub fn downcast_ref<T>(&self) -> Option<&T>
where
T: StdError + 'static,
{
match self {
Error::ExternalError(err) => err.downcast_ref(),
Error::WithContext { cause, .. } => match cause.as_ref() {
Error::ExternalError(err) => err.downcast_ref(),
_ => None,
},
_ => None,
}
}
pub(crate) fn bad_self_argument(to: &str, cause: Error) -> Self {
Error::BadArgument {
to: Some(to.to_string()),
pos: 1,
name: Some("self".to_string()),
cause: Arc::new(cause),
}
}
pub(crate) fn from_lua_conversion<'a>(
from: &'static str,
to: &'static str,
message: impl Into<Option<&'a str>>,
) -> Self {
Error::FromLuaConversionError {
from,
to,
message: message.into().map(|s| s.into()),
}
}
} }
/// Trait for converting [`std::error::Error`] into Lua [`Error`].
pub trait ExternalError { pub trait ExternalError {
fn to_lua_err(self) -> Error; fn into_lua_err(self) -> Error;
} }
impl<E: Into<Box<dyn StdError + Send + Sync>>> ExternalError for E { impl<E: Into<Box<dyn StdError + Send + Sync>>> ExternalError for E {
fn to_lua_err(self) -> Error { fn into_lua_err(self) -> Error {
Error::external(self) Error::external(self)
} }
} }
/// Trait for converting [`std::result::Result`] into Lua [`Result`].
pub trait ExternalResult<T> { pub trait ExternalResult<T> {
fn to_lua_err(self) -> Result<T>; fn into_lua_err(self) -> Result<T>;
} }
impl<T, E> ExternalResult<T> for StdResult<T, E> impl<T, E> ExternalResult<T> for StdResult<T, E>
where where
E: ExternalError, E: ExternalError,
{ {
fn to_lua_err(self) -> Result<T> { fn into_lua_err(self) -> Result<T> {
self.map_err(|e| e.to_lua_err()) self.map_err(|e| e.into_lua_err())
} }
} }
impl std::convert::From<AddrParseError> for Error { /// Provides the `context` method for [`Error`] and `Result<T, Error>`.
pub trait ErrorContext: Sealed {
/// Wraps the error value with additional context.
fn context<C: fmt::Display>(self, context: C) -> Self;
/// Wrap the error value with additional context that is evaluated lazily
/// only once an error does occur.
fn with_context<C: fmt::Display>(self, f: impl FnOnce(&Error) -> C) -> Self;
}
impl ErrorContext for Error {
fn context<C: fmt::Display>(self, context: C) -> Self {
let context = context.to_string();
match self {
Error::WithContext { cause, .. } => Error::WithContext { context, cause },
_ => Error::WithContext {
context,
cause: Arc::new(self),
},
}
}
fn with_context<C: fmt::Display>(self, f: impl FnOnce(&Error) -> C) -> Self {
let context = f(&self).to_string();
match self {
Error::WithContext { cause, .. } => Error::WithContext { context, cause },
_ => Error::WithContext {
context,
cause: Arc::new(self),
},
}
}
}
impl<T> ErrorContext for StdResult<T, Error> {
fn context<C: fmt::Display>(self, context: C) -> Self {
self.map_err(|err| err.context(context))
}
fn with_context<C: fmt::Display>(self, f: impl FnOnce(&Error) -> C) -> Self {
self.map_err(|err| err.with_context(f))
}
}
impl From<AddrParseError> for Error {
fn from(err: AddrParseError) -> Self { fn from(err: AddrParseError) -> Self {
Error::external(err) Error::external(err)
} }
} }
impl std::convert::From<IoError> for Error { impl From<IoError> for Error {
fn from(err: IoError) -> Self { fn from(err: IoError) -> Self {
Error::external(err) Error::external(err)
} }
} }
impl std::convert::From<Utf8Error> for Error { impl From<Utf8Error> for Error {
fn from(err: Utf8Error) -> Self { fn from(err: Utf8Error) -> Self {
Error::external(err) Error::external(err)
} }
-101
View File
@@ -1,101 +0,0 @@
//! Low level bindings to Lua 5.4/5.3/5.2/5.1 including LuaJIT.
#![allow(non_camel_case_types, non_snake_case, dead_code)]
use std::os::raw::c_int;
#[cfg(feature = "lua54")]
pub use lua54::*;
#[cfg(feature = "lua53")]
pub use lua53::*;
#[cfg(feature = "lua52")]
pub use lua52::*;
#[cfg(any(feature = "lua51", feature = "luajit"))]
pub use lua51::*;
#[cfg(feature = "luau")]
pub use luau::*;
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
pub const LUA_MAX_UPVALUES: c_int = 255;
#[cfg(any(feature = "lua51", all(feature = "luajit", not(feature = "vendored"))))]
pub const LUA_MAX_UPVALUES: c_int = 60;
#[cfg(all(feature = "luajit", feature = "vendored"))]
pub const LUA_MAX_UPVALUES: c_int = 120;
#[cfg(feature = "luau")]
pub const LUA_MAX_UPVALUES: c_int = 200;
// I believe `luaL_traceback` < 5.4 requires this much free stack to not error.
// 5.4 uses `luaL_Buffer`
pub const LUA_TRACEBACK_STACK: c_int = 11;
// The minimum alignment guaranteed by the architecture. This value is used to
// add fast paths for low alignment values.
// Copied from https://github.com/rust-lang/rust/blob/master/library/std/src/sys/common/alloc.rs
#[cfg(all(any(
target_arch = "x86",
target_arch = "arm",
target_arch = "mips",
target_arch = "powerpc",
target_arch = "powerpc64",
target_arch = "sparc",
target_arch = "asmjs",
target_arch = "wasm32",
target_arch = "hexagon",
all(target_arch = "riscv32", not(target_os = "espidf")),
all(target_arch = "xtensa", not(target_os = "espidf")),
)))]
pub const SYS_MIN_ALIGN: usize = 8;
#[cfg(all(any(
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "mips64",
target_arch = "s390x",
target_arch = "sparc64",
target_arch = "riscv64",
target_arch = "wasm64",
)))]
pub const SYS_MIN_ALIGN: usize = 16;
// The allocator on the esp-idf platform guarentees 4 byte alignment.
#[cfg(all(any(
all(target_arch = "riscv32", target_os = "espidf"),
all(target_arch = "xtensa", target_os = "espidf"),
)))]
pub const SYS_MIN_ALIGN: usize = 4;
// Hack to avoid stripping a few unused Lua symbols that could be imported
// by C modules in unsafe mode
#[cfg(not(feature = "luau"))]
pub(crate) fn keep_lua_symbols() {
let mut symbols: Vec<*const extern "C" fn()> = Vec::new();
symbols.push(lua_atpanic as _);
symbols.push(lua_isuserdata as _);
symbols.push(lua_tocfunction as _);
symbols.push(luaL_loadstring as _);
symbols.push(luaL_openlibs as _);
if cfg!(any(feature = "lua54", feature = "lua53", feature = "lua52")) {
symbols.push(lua_getglobal as _);
symbols.push(lua_setglobal as _);
}
}
#[cfg(feature = "lua54")]
pub mod lua54;
#[cfg(feature = "lua53")]
pub mod lua53;
#[cfg(feature = "lua52")]
pub mod lua52;
#[cfg(any(feature = "lua51", feature = "luajit"))]
pub mod lua51;
#[cfg(feature = "luau")]
pub mod luau;
+360 -84
View File
@@ -1,33 +1,73 @@
use std::cell::RefCell;
use std::mem; use std::mem;
use std::os::raw::{c_int, c_void}; use std::os::raw::{c_int, c_void};
use std::ptr; use std::ptr;
use std::slice; use std::slice;
use crate::error::{Error, Result}; use crate::error::{Error, Result};
use crate::ffi; use crate::lua::Lua;
use crate::types::LuaRef; use crate::table::Table;
use crate::types::{Callback, LuaRef, MaybeSend};
use crate::util::{ 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, ToLuaMulti}; use crate::value::{FromLuaMulti, IntoLua, IntoLuaMulti, Value};
#[cfg(feature = "async")] #[cfg(feature = "async")]
use {futures_core::future::LocalBoxFuture, futures_util::future}; use {
crate::types::AsyncCallback,
futures_util::future::{self, Future},
};
/// Handle to an internal Lua function. /// Handle to an internal Lua function.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Function<'lua>(pub(crate) LuaRef<'lua>); pub struct Function<'lua>(pub(crate) LuaRef<'lua>);
/// Owned handle to an internal Lua function.
///
/// 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 OwnedFunction(pub(crate) crate::types::LuaOwnedRef);
#[cfg(feature = "unstable")]
impl OwnedFunction {
/// Get borrowed handle to the underlying Lua function.
#[cfg_attr(feature = "send", allow(unused))]
pub const fn to_ref(&self) -> Function {
Function(self.0.to_ref())
}
}
/// Contains information about a function.
///
/// Please refer to the [`Lua Debug Interface`] for more information.
///
/// [`Lua Debug Interface`]: https://www.lua.org/manual/5.4/manual.html#4.7
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct FunctionInfo { pub struct FunctionInfo {
pub name: Option<Vec<u8>>, /// A (reasonable) name of the function (`None` if the name cannot be found).
pub name_what: Option<Vec<u8>>, pub name: Option<String>,
pub what: Option<Vec<u8>>, /// Explains the `name` field (can be `global`/`local`/`method`/`field`/`upvalue`/etc).
pub source: Option<Vec<u8>>, ///
pub short_src: Option<Vec<u8>>, /// Always `None` for Luau.
pub line_defined: i32, pub name_what: Option<&'static str>,
#[cfg(not(feature = "luau"))] /// 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 last_line_defined: i32, 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: 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. /// Luau function coverage snapshot.
@@ -35,7 +75,7 @@ pub struct FunctionInfo {
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct CoverageInfo { pub struct CoverageInfo {
pub function: Option<std::string::String>, pub function: Option<String>,
pub line_defined: i32, pub line_defined: i32,
pub depth: i32, pub depth: i32,
pub hits: Vec<i32>, pub hits: Vec<i32>,
@@ -82,39 +122,31 @@ impl<'lua> Function<'lua> {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
pub fn call<A: ToLuaMulti<'lua>, R: FromLuaMulti<'lua>>(&self, args: A) -> Result<R> { pub fn call<A: IntoLuaMulti<'lua>, R: FromLuaMulti<'lua>>(&self, args: A) -> Result<R> {
let lua = self.0.lua; let lua = self.0.lua;
let state = lua.state();
unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 2)?;
let mut args = args.to_lua_multi(lua)?; // Push error handler
let nargs = args.len() as c_int; lua.push_error_traceback();
let stack_start = ffi::lua_gettop(state);
let results = unsafe { // Push function and the arguments
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, nargs + 3)?;
ffi::lua_pushcfunction(lua.state, error_traceback);
let stack_start = ffi::lua_gettop(lua.state);
lua.push_ref(&self.0); lua.push_ref(&self.0);
for arg in args.drain_all() { let nargs = args.push_into_stack_multi(lua)?;
lua.push_value(arg)?; // Call the function
} let ret = ffi::lua_pcall(state, nargs, ffi::LUA_MULTRET, stack_start);
let ret = ffi::lua_pcall(lua.state, nargs, ffi::LUA_MULTRET, stack_start);
if ret != ffi::LUA_OK { if ret != ffi::LUA_OK {
return Err(pop_error(lua.state, ret)); return Err(pop_error(state, ret));
} }
let nresults = ffi::lua_gettop(lua.state) - stack_start; // Get the results
let mut results = args; // Reuse MultiValue container let nresults = ffi::lua_gettop(state) - stack_start;
assert_stack(lua.state, 2); R::from_stack_multi(nresults, lua)
for _ in 0..nresults { }
results.push_front(lua.pop_value());
}
ffi::lua_pop(lua.state, 1);
results
};
R::from_lua_multi(results, lua)
} }
/// Returns a Feature that, when polled, calls `self`, passing `args` as function arguments, /// Returns a future that, when polled, calls `self`, passing `args` as function arguments,
/// and drives the execution. /// and drives the execution.
/// ///
/// Internally it wraps the function to an [`AsyncThread`]. /// Internally it wraps the function to an [`AsyncThread`].
@@ -125,14 +157,13 @@ impl<'lua> Function<'lua> {
/// ///
/// ``` /// ```
/// use std::time::Duration; /// use std::time::Duration;
/// use futures_timer::Delay;
/// # use mlua::{Lua, Result}; /// # use mlua::{Lua, Result};
/// # #[tokio::main] /// # #[tokio::main]
/// # async fn main() -> Result<()> { /// # async fn main() -> Result<()> {
/// # let lua = Lua::new(); /// # let lua = Lua::new();
/// ///
/// let sleep = lua.create_async_function(move |_lua, n: u64| async move { /// 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(()) /// Ok(())
/// })?; /// })?;
/// ///
@@ -145,21 +176,18 @@ impl<'lua> Function<'lua> {
/// [`AsyncThread`]: crate::AsyncThread /// [`AsyncThread`]: crate::AsyncThread
#[cfg(feature = "async")] #[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(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 where
'lua: 'fut, A: IntoLuaMulti<'lua>,
A: ToLuaMulti<'lua>, R: FromLuaMulti<'lua> + 'lua,
R: FromLuaMulti<'lua> + 'fut,
{ {
let lua = self.0.lua; let lua = self.0.lua;
match lua.create_recycled_thread(self.clone()) { let thread_res = lua.create_recycled_thread(self).map(|th| {
Ok(t) => { let mut th = th.into_async(args);
let mut t = t.into_async(args); th.set_recyclable(true);
t.set_recyclable(true); th
Box::pin(t) });
} async move { thread_res?.await }
Err(e) => Box::pin(future::err(e)),
}
} }
/// Returns a function that, when called, calls `self`, passing `args` as the first set of /// Returns a function that, when called, calls `self`, passing `args` as the first set of
@@ -189,8 +217,8 @@ impl<'lua> Function<'lua> {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
pub fn bind<A: ToLuaMulti<'lua>>(&self, args: A) -> Result<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 nargs = ffi::lua_gettop(state);
let nbinds = ffi::lua_tointeger(state, ffi::lua_upvalueindex(1)) as c_int; let nbinds = ffi::lua_tointeger(state, ffi::lua_upvalueindex(1)) as c_int;
ffi::luaL_checkstack(state, nbinds, ptr::null()); ffi::luaL_checkstack(state, nbinds, ptr::null());
@@ -206,8 +234,9 @@ impl<'lua> Function<'lua> {
} }
let lua = self.0.lua; let lua = self.0.lua;
let state = lua.state();
let args = args.to_lua_multi(lua)?; let args = args.into_lua_multi(lua)?;
let nargs = args.len() as c_int; let nargs = args.len() as c_int;
if nargs == 0 { if nargs == 0 {
@@ -219,14 +248,14 @@ impl<'lua> Function<'lua> {
} }
let args_wrapper = unsafe { let args_wrapper = unsafe {
let _sg = StackGuard::new(lua.state); let _sg = StackGuard::new(state);
check_stack(lua.state, nargs + 3)?; check_stack(state, nargs + 3)?;
ffi::lua_pushinteger(lua.state, nargs as ffi::lua_Integer); ffi::lua_pushinteger(state, nargs as ffi::lua_Integer);
for arg in args { for arg in args {
lua.push_value(arg)?; lua.push_value(arg)?;
} }
protect_lua!(lua.state, nargs + 1, 1, fn(state) { protect_lua!(state, nargs + 1, 1, fn(state) {
ffi::lua_pushcclosure(state, args_wrapper_impl, ffi::lua_gettop(state)); ffi::lua_pushcclosure(state, args_wrapper_impl, ffi::lua_gettop(state));
})?; })?;
@@ -242,10 +271,93 @@ impl<'lua> Function<'lua> {
"#, "#,
) )
.try_cache() .try_cache()
.set_name("_mlua_bind")? .set_name("__mlua_bind")
.call((self.clone(), args_wrapper)) .call((self.clone(), args_wrapper))
} }
/// Returns the environment of the Lua function.
///
/// By default Lua functions shares a global environment.
///
/// This function always returns `None` for Rust/C functions.
pub fn environment(&self) -> Option<Table> {
let lua = self.0.lua;
let state = lua.state();
unsafe {
let _sg = StackGuard::new(state);
assert_stack(state, 1);
lua.push_ref(&self.0);
if ffi::lua_iscfunction(state, -1) != 0 {
return None;
}
#[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))]
ffi::lua_getfenv(state, -1);
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
for i in 1..=255 {
// Traverse upvalues until we find the _ENV one
match ffi::lua_getupvalue(state, -1, i) {
s if s.is_null() => break,
s if std::ffi::CStr::from_ptr(s as _).to_bytes() == b"_ENV" => break,
_ => ffi::lua_pop(state, 1),
}
}
if ffi::lua_type(state, -1) != ffi::LUA_TTABLE {
return None;
}
Some(Table(lua.pop_ref()))
}
}
/// Sets the environment of the Lua function.
///
/// The environment is a table that is used as the global environment for the function.
/// Returns `true` if environment successfully changed, `false` otherwise.
///
/// This function does nothing for Rust/C functions.
pub fn set_environment(&self, env: Table) -> Result<bool> {
let lua = self.0.lua;
let state = lua.state();
unsafe {
let _sg = StackGuard::new(state);
check_stack(state, 2)?;
lua.push_ref(&self.0);
if ffi::lua_iscfunction(state, -1) != 0 {
return Ok(false);
}
#[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))]
{
lua.push_ref(&env.0);
ffi::lua_setfenv(state, -2);
}
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
for i in 1..=255 {
match ffi::lua_getupvalue(state, -1, i) {
s if s.is_null() => return Ok(false),
s if std::ffi::CStr::from_ptr(s as _).to_bytes() == b"_ENV" => {
ffi::lua_pop(state, 1);
// Create an anonymous function with the new environment
let f_with_env = lua
.load("return _ENV")
.set_environment(env)
.try_cache()
.into_function()?;
lua.push_ref(&f_with_env.0);
ffi::lua_upvaluejoin(state, -2, i, -1, 1);
break;
}
_ => ffi::lua_pop(state, 1),
}
}
Ok(true)
}
}
/// Returns information about the function. /// Returns information about the function.
/// ///
/// Corresponds to the `>Sn` what mask for [`lua_getinfo`] when applied to the function. /// Corresponds to the `>Sn` what mask for [`lua_getinfo`] when applied to the function.
@@ -253,30 +365,39 @@ impl<'lua> Function<'lua> {
/// [`lua_getinfo`]: https://www.lua.org/manual/5.4/manual.html#lua_getinfo /// [`lua_getinfo`]: https://www.lua.org/manual/5.4/manual.html#lua_getinfo
pub fn info(&self) -> FunctionInfo { pub fn info(&self) -> FunctionInfo {
let lua = self.0.lua; let lua = self.0.lua;
let state = lua.state();
unsafe { unsafe {
let _sg = StackGuard::new(lua.state); let _sg = StackGuard::new(state);
assert_stack(lua.state, 1); assert_stack(state, 1);
let mut ar: ffi::lua_Debug = mem::zeroed(); let mut ar: ffi::lua_Debug = mem::zeroed();
lua.push_ref(&self.0); lua.push_ref(&self.0);
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
let res = ffi::lua_getinfo(lua.state, cstr!(">Sn"), &mut ar); let res = ffi::lua_getinfo(state, cstr!(">Sn"), &mut ar);
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
let res = ffi::lua_getinfo(lua.state, -1, cstr!("sn"), &mut ar); let res = ffi::lua_getinfo(state, -1, cstr!("sn"), &mut ar);
mlua_assert!(res != 0, "lua_getinfo failed with `>Sn`"); mlua_assert!(res != 0, "lua_getinfo failed with `>Sn`");
FunctionInfo { FunctionInfo {
name: ptr_to_cstr_bytes(ar.name).map(|s| s.to_vec()), name: ptr_to_lossy_str(ar.name).map(|s| s.into_owned()),
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
name_what: ptr_to_cstr_bytes(ar.namewhat).map(|s| s.to_vec()), name_what: match ptr_to_str(ar.namewhat) {
Some("") => None,
val => val,
},
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
name_what: None, name_what: None,
what: ptr_to_cstr_bytes(ar.what).map(|s| s.to_vec()), what: ptr_to_str(ar.what).unwrap_or("main"),
source: ptr_to_cstr_bytes(ar.source).map(|s| s.to_vec()), source: ptr_to_lossy_str(ar.source).map(|s| s.into_owned()),
short_src: ptr_to_cstr_bytes(&ar.short_src as *const _).map(|s| s.to_vec()),
line_defined: ar.linedefined as i32,
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
last_line_defined: ar.lastlinedefined as i32, short_src: ptr_to_lossy_str(ar.short_src.as_ptr()).map(|s| s.into_owned()),
#[cfg(feature = "luau")]
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: linenumber_to_usize(ar.lastlinedefined),
#[cfg(feature = "luau")]
last_line_defined: None,
} }
} }
} }
@@ -292,7 +413,7 @@ impl<'lua> Function<'lua> {
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub fn dump(&self, strip: bool) -> Vec<u8> { pub fn dump(&self, strip: bool) -> Vec<u8> {
unsafe extern "C" fn writer( unsafe extern "C-unwind" fn writer(
_state: *mut ffi::lua_State, _state: *mut ffi::lua_State,
buf: *const c_void, buf: *const c_void,
buf_len: usize, buf_len: usize,
@@ -305,16 +426,16 @@ impl<'lua> Function<'lua> {
} }
let lua = self.0.lua; let lua = self.0.lua;
let state = lua.state();
let mut data: Vec<u8> = Vec::new(); let mut data: Vec<u8> = Vec::new();
unsafe { unsafe {
let _sg = StackGuard::new(lua.state); let _sg = StackGuard::new(state);
assert_stack(lua.state, 1); assert_stack(state, 1);
lua.push_ref(&self.0); lua.push_ref(&self.0);
let data_ptr = &mut data as *mut Vec<u8> as *mut c_void; let data_ptr = &mut data as *mut Vec<u8> as *mut c_void;
let strip = if strip { 1 } else { 0 }; ffi::lua_dump(state, writer, data_ptr, strip as i32);
ffi::lua_dump(lua.state, writer, data_ptr, strip); ffi::lua_pop(state, 1);
ffi::lua_pop(lua.state, 1);
} }
data data
@@ -330,7 +451,7 @@ impl<'lua> Function<'lua> {
/// Requires `feature = "luau"` /// Requires `feature = "luau"`
/// ///
/// [`Compiler::set_coverage_level`]: crate::chunk::Compiler::set_coverage_level /// [`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")))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn coverage<F>(&self, mut func: F) pub fn coverage<F>(&self, mut func: F)
where where
@@ -339,7 +460,7 @@ impl<'lua> Function<'lua> {
use std::ffi::CStr; use std::ffi::CStr;
use std::os::raw::c_char; 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, data: *mut c_void,
function: *const c_char, function: *const c_char,
line_defined: c_int, line_defined: c_int,
@@ -362,15 +483,55 @@ impl<'lua> Function<'lua> {
} }
let lua = self.0.lua; let lua = self.0.lua;
let state = lua.state();
unsafe { unsafe {
let _sg = StackGuard::new(lua.state); let _sg = StackGuard::new(state);
assert_stack(lua.state, 1); assert_stack(state, 1);
lua.push_ref(&self.0); lua.push_ref(&self.0);
let func_ptr = &mut func as *mut F as *mut c_void; let func_ptr = &mut func as *mut F as *mut c_void;
ffi::lua_getcoverage(lua.state, -1, func_ptr, callback::<F>); ffi::lua_getcoverage(state, -1, func_ptr, callback::<F>);
} }
} }
/// Converts this function to a generic C pointer.
///
/// There is no way to convert the pointer back to its original value.
///
/// Typically this function is used only for hashing and debug information.
#[inline]
pub fn to_pointer(&self) -> *const c_void {
self.0.to_pointer()
}
/// Creates a deep clone of the Lua function.
///
/// Copies the function prototype and all its upvalues to the
/// newly created function.
///
/// This function returns shallow clone (same handle) for Rust/C functions.
/// Requires `feature = "luau"`
#[cfg(feature = "luau")]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn deep_clone(&self) -> Self {
let ref_thread = self.0.lua.ref_thread();
unsafe {
if ffi::lua_iscfunction(ref_thread, self.0.index) != 0 {
return self.clone();
}
ffi::lua_clonefunction(ref_thread, self.0.index);
Function(self.0.lua.pop_ref_thread())
}
}
/// 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) -> OwnedFunction {
OwnedFunction(self.0.into_owned())
}
} }
impl<'lua> PartialEq for Function<'lua> { impl<'lua> PartialEq for Function<'lua> {
@@ -378,3 +539,118 @@ impl<'lua> PartialEq for Function<'lua> {
self.0 == other.0 self.0 == other.0
} }
} }
// Additional shortcuts
#[cfg(feature = "unstable")]
impl OwnedFunction {
/// Calls the function, passing `args` as function arguments.
///
/// This is a shortcut for [`Function::call()`].
#[inline]
pub fn call<'lua, A, R>(&'lua self, args: A) -> Result<R>
where
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua>,
{
self.to_ref().call(args)
}
/// Returns a future that, when polled, calls `self`, passing `args` as function arguments,
/// and drives the execution.
///
/// This is a shortcut for [`Function::call_async()`].
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
#[inline]
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).await
}
}
pub(crate) struct WrappedFunction<'lua>(pub(crate) Callback<'lua, 'static>);
#[cfg(feature = "async")]
pub(crate) struct WrappedAsyncFunction<'lua>(pub(crate) AsyncCallback<'lua, 'static>);
impl<'lua> Function<'lua> {
/// Wraps a Rust function or closure, returning an opaque type that implements [`IntoLua`] trait.
#[inline]
pub fn wrap<A, R, F>(func: F) -> impl IntoLua<'lua>
where
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
F: Fn(&'lua Lua, A) -> Result<R> + MaybeSend + 'static,
{
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)
}))
}
/// Wraps a Rust mutable closure, returning an opaque type that implements [`IntoLua`] trait.
#[inline]
pub fn wrap_mut<A, R, F>(func: F) -> impl IntoLua<'lua>
where
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
F: FnMut(&'lua Lua, A) -> Result<R> + MaybeSend + 'static,
{
let func = RefCell::new(func);
WrappedFunction(Box::new(move |lua, nargs| unsafe {
let mut func = func
.try_borrow_mut()
.map_err(|_| Error::RecursiveMutCallback)?;
let args = A::from_stack_args(nargs, 1, None, lua)?;
func(lua, args)?.push_into_stack_multi(lua)
}))
}
/// Wraps a Rust async function or closure, returning an opaque type that implements [`IntoLua`] trait.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn wrap_async<A, R, F, FR>(func: F) -> impl IntoLua<'lua>
where
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
F: Fn(&'lua Lua, A) -> FR + MaybeSend + 'static,
FR: Future<Output = Result<R>> + 'lua,
{
WrappedAsyncFunction(Box::new(move |lua, args| unsafe {
let args = match A::from_lua_args(args, 1, None, lua) {
Ok(args) => args,
Err(e) => return Box::pin(future::err(e)),
};
let fut = func(lua, args);
Box::pin(async move { fut.await?.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::*;
static_assertions::assert_not_impl_any!(Function: Send);
#[cfg(all(feature = "unstable", not(feature = "send")))]
static_assertions::assert_not_impl_any!(OwnedFunction: Send);
}
+90 -58
View File
@@ -1,11 +1,13 @@
use std::borrow::Cow;
use std::cell::UnsafeCell; use std::cell::UnsafeCell;
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
use std::ops::{BitOr, BitOrAssign}; use std::ops::{BitOr, BitOrAssign};
use std::os::raw::c_int; use std::os::raw::c_int;
use crate::ffi::{self, lua_Debug}; use ffi::lua_Debug;
use crate::lua::Lua; 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. /// Contains information about currently executing Lua code.
/// ///
@@ -63,23 +65,26 @@ impl<'lua> Debug<'lua> {
} }
/// Corresponds to the `n` what mask. /// Corresponds to the `n` what mask.
pub fn names(&self) -> DebugNames<'lua> { pub fn names(&self) -> DebugNames {
unsafe { unsafe {
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
mlua_assert!( mlua_assert!(
ffi::lua_getinfo(self.lua.state, cstr!("n"), self.ar.get()) != 0, ffi::lua_getinfo(self.lua.state(), cstr!("n"), self.ar.get()) != 0,
"lua_getinfo failed with `n`" "lua_getinfo failed with `n`"
); );
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
mlua_assert!( mlua_assert!(
ffi::lua_getinfo(self.lua.state, self.level, cstr!("n"), self.ar.get()) != 0, ffi::lua_getinfo(self.lua.state(), self.level, cstr!("n"), self.ar.get()) != 0,
"lua_getinfo failed with `n`" "lua_getinfo failed with `n`"
); );
DebugNames { DebugNames {
name: ptr_to_cstr_bytes((*self.ar.get()).name), name: ptr_to_lossy_str((*self.ar.get()).name),
#[cfg(not(feature = "luau"))] #[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")] #[cfg(feature = "luau")]
name_what: None, name_what: None,
} }
@@ -87,26 +92,31 @@ impl<'lua> Debug<'lua> {
} }
/// Corresponds to the `S` what mask. /// Corresponds to the `S` what mask.
pub fn source(&self) -> DebugSource<'lua> { pub fn source(&self) -> DebugSource {
unsafe { unsafe {
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
mlua_assert!( mlua_assert!(
ffi::lua_getinfo(self.lua.state, cstr!("S"), self.ar.get()) != 0, ffi::lua_getinfo(self.lua.state(), cstr!("S"), self.ar.get()) != 0,
"lua_getinfo failed with `S`" "lua_getinfo failed with `S`"
); );
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
mlua_assert!( mlua_assert!(
ffi::lua_getinfo(self.lua.state, self.level, cstr!("s"), self.ar.get()) != 0, ffi::lua_getinfo(self.lua.state(), self.level, cstr!("s"), self.ar.get()) != 0,
"lua_getinfo failed with `s`" "lua_getinfo failed with `s`"
); );
DebugSource { DebugSource {
source: ptr_to_cstr_bytes((*self.ar.get()).source), source: ptr_to_lossy_str((*self.ar.get()).source),
short_src: ptr_to_cstr_bytes((*self.ar.get()).short_src.as_ptr()),
line_defined: (*self.ar.get()).linedefined as i32,
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
last_line_defined: (*self.ar.get()).lastlinedefined as i32, short_src: ptr_to_lossy_str((*self.ar.get()).short_src.as_ptr()),
what: ptr_to_cstr_bytes((*self.ar.get()).what), #[cfg(feature = "luau")]
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: 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"),
} }
} }
} }
@@ -116,16 +126,16 @@ impl<'lua> Debug<'lua> {
unsafe { unsafe {
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
mlua_assert!( mlua_assert!(
ffi::lua_getinfo(self.lua.state, cstr!("l"), self.ar.get()) != 0, ffi::lua_getinfo(self.lua.state(), cstr!("l"), self.ar.get()) != 0,
"lua_getinfo failed with `l`" "lua_getinfo failed with `l`"
); );
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
mlua_assert!( mlua_assert!(
ffi::lua_getinfo(self.lua.state, self.level, cstr!("l"), self.ar.get()) != 0, ffi::lua_getinfo(self.lua.state(), self.level, cstr!("l"), self.ar.get()) != 0,
"lua_getinfo failed with `l`" "lua_getinfo failed with `l`"
); );
(*self.ar.get()).currentline as i32 (*self.ar.get()).currentline
} }
} }
@@ -136,7 +146,7 @@ impl<'lua> Debug<'lua> {
pub fn is_tail_call(&self) -> bool { pub fn is_tail_call(&self) -> bool {
unsafe { unsafe {
mlua_assert!( mlua_assert!(
ffi::lua_getinfo(self.lua.state, cstr!("t"), self.ar.get()) != 0, ffi::lua_getinfo(self.lua.state(), cstr!("t"), self.ar.get()) != 0,
"lua_getinfo failed with `t`" "lua_getinfo failed with `t`"
); );
(*self.ar.get()).currentline != 0 (*self.ar.get()).currentline != 0
@@ -148,20 +158,20 @@ impl<'lua> Debug<'lua> {
unsafe { unsafe {
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
mlua_assert!( mlua_assert!(
ffi::lua_getinfo(self.lua.state, cstr!("u"), self.ar.get()) != 0, ffi::lua_getinfo(self.lua.state(), cstr!("u"), self.ar.get()) != 0,
"lua_getinfo failed with `u`" "lua_getinfo failed with `u`"
); );
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
mlua_assert!( mlua_assert!(
ffi::lua_getinfo(self.lua.state, self.level, cstr!("a"), self.ar.get()) != 0, ffi::lua_getinfo(self.lua.state(), self.level, cstr!("a"), self.ar.get()) != 0,
"lua_getinfo failed with `a`" "lua_getinfo failed with `a`"
); );
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
let stack = DebugStack { let stack = DebugStack {
num_ups: (*self.ar.get()).nups as i32, num_ups: (*self.ar.get()).nups as _,
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
num_params: (*self.ar.get()).nparams as i32, num_params: (*self.ar.get()).nparams as _,
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))] #[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
is_vararg: (*self.ar.get()).isvararg != 0, is_vararg: (*self.ar.get()).isvararg != 0,
}; };
@@ -206,18 +216,26 @@ pub enum DebugEvent {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct DebugNames<'a> { pub struct DebugNames<'a> {
pub name: Option<&'a [u8]>, /// A (reasonable) name of the function (`None` if the name cannot be found).
pub name_what: Option<&'a [u8]>, 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)] #[derive(Clone, Debug)]
pub struct DebugSource<'a> { pub struct DebugSource<'a> {
pub source: Option<&'a [u8]>, /// Source of the chunk that created the function.
pub short_src: Option<&'a [u8]>, pub source: Option<Cow<'a, str>>,
pub line_defined: i32, /// A "printable" version of `source`, to be used in error messages.
#[cfg(not(feature = "luau"))] pub short_src: Option<Cow<'a, str>>,
pub last_line_defined: i32, /// The line number where the definition of the function starts.
pub what: Option<&'a [u8]>, 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)] #[derive(Copy, Clone, Debug)]
@@ -263,48 +281,59 @@ pub struct HookTriggers {
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
impl HookTriggers { impl HookTriggers {
/// Returns a new instance of `HookTriggers` with [`on_calls`] trigger set. /// An instance of `HookTriggers` with `on_calls` trigger set.
pub const ON_CALLS: Self = HookTriggers::new().on_calls();
/// An instance of `HookTriggers` with `on_returns` trigger set.
pub const ON_RETURNS: Self = HookTriggers::new().on_returns();
/// An instance of `HookTriggers` with `every_line` trigger set.
pub const EVERY_LINE: Self = HookTriggers::new().every_line();
/// Returns a new instance of `HookTriggers` with all triggers disabled.
pub const fn new() -> Self {
HookTriggers {
on_calls: false,
on_returns: false,
every_line: false,
every_nth_instruction: None,
}
}
/// Returns an instance of `HookTriggers` with [`on_calls`] trigger set.
/// ///
/// [`on_calls`]: #structfield.on_calls /// [`on_calls`]: #structfield.on_calls
pub fn on_calls() -> Self { pub const fn on_calls(mut self) -> Self {
HookTriggers { self.on_calls = true;
on_calls: true, self
..Default::default()
}
} }
/// Returns a new instance of `HookTriggers` with [`on_returns`] trigger set. /// Returns an instance of `HookTriggers` with [`on_returns`] trigger set.
/// ///
/// [`on_returns`]: #structfield.on_returns /// [`on_returns`]: #structfield.on_returns
pub fn on_returns() -> Self { pub const fn on_returns(mut self) -> Self {
HookTriggers { self.on_returns = true;
on_returns: true, self
..Default::default()
}
} }
/// Returns a new instance of `HookTriggers` with [`every_line`] trigger set. /// Returns an instance of `HookTriggers` with [`every_line`] trigger set.
/// ///
/// [`every_line`]: #structfield.every_line /// [`every_line`]: #structfield.every_line
pub fn every_line() -> Self { pub const fn every_line(mut self) -> Self {
HookTriggers { self.every_line = true;
every_line: true, self
..Default::default()
}
} }
/// Returns a new instance of `HookTriggers` with [`every_nth_instruction`] trigger set. /// Returns an instance of `HookTriggers` with [`every_nth_instruction`] trigger set.
/// ///
/// [`every_nth_instruction`]: #structfield.every_nth_instruction /// [`every_nth_instruction`]: #structfield.every_nth_instruction
pub fn every_nth_instruction(n: u32) -> Self { pub const fn every_nth_instruction(mut self, n: u32) -> Self {
HookTriggers { self.every_nth_instruction = Some(n);
every_nth_instruction: Some(n), self
..Default::default()
}
} }
// Compute the mask to pass to `lua_sethook`. // Compute the mask to pass to `lua_sethook`.
pub(crate) fn mask(&self) -> c_int { pub(crate) const fn mask(&self) -> c_int {
let mut mask: c_int = 0; let mut mask: c_int = 0;
if self.on_calls { if self.on_calls {
mask |= ffi::LUA_MASKCALL mask |= ffi::LUA_MASKCALL
@@ -323,8 +352,11 @@ impl HookTriggers {
// Returns the `count` parameter to pass to `lua_sethook`, if applicable. Otherwise, zero is // Returns the `count` parameter to pass to `lua_sethook`, if applicable. Otherwise, zero is
// returned. // returned.
pub(crate) fn count(&self) -> c_int { pub(crate) const fn count(&self) -> c_int {
self.every_nth_instruction.unwrap_or(0) as c_int match self.every_nth_instruction {
Some(n) => n as c_int,
None => 0,
}
} }
} }
+75 -17
View File
@@ -10,10 +10,10 @@
//! //!
//! # Converting data //! # Converting data
//! //!
//! The [`ToLua`] and [`FromLua`] traits allow conversion from Rust types to Lua values and vice //! The [`IntoLua`] and [`FromLua`] traits allow conversion from Rust types to Lua values and vice
//! versa. They are implemented for many data structures found in Rust's standard library. //! versa. They are implemented for many data structures found in Rust's standard library.
//! //!
//! For more general conversions, the [`ToLuaMulti`] and [`FromLuaMulti`] traits allow converting //! For more general conversions, the [`IntoLuaMulti`] and [`FromLuaMulti`] traits allow converting
//! between Rust types and *any number* of Lua values. //! between Rust types and *any number* of Lua values.
//! //!
//! Most code in `mlua` is generic over implementors of those traits, so in most places the normal //! Most code in `mlua` is generic over implementors of those traits, so in most places the normal
@@ -54,9 +54,9 @@
//! [executing]: crate::Chunk::exec //! [executing]: crate::Chunk::exec
//! [evaluating]: crate::Chunk::eval //! [evaluating]: crate::Chunk::eval
//! [globals]: crate::Lua::globals //! [globals]: crate::Lua::globals
//! [`ToLua`]: crate::ToLua //! [`IntoLua`]: crate::IntoLua
//! [`FromLua`]: crate::FromLua //! [`FromLua`]: crate::FromLua
//! [`ToLuaMulti`]: crate::ToLuaMulti //! [`IntoLuaMulti`]: crate::IntoLuaMulti
//! [`FromLuaMulti`]: crate::FromLuaMulti //! [`FromLuaMulti`]: crate::FromLuaMulti
//! [`Function`]: crate::Function //! [`Function`]: crate::Function
//! [`UserData`]: crate::UserData //! [`UserData`]: crate::UserData
@@ -71,11 +71,9 @@
//! [`serde::Serialize`]: https://docs.serde.rs/serde/ser/trait.Serialize.html //! [`serde::Serialize`]: https://docs.serde.rs/serde/ser/trait.Serialize.html
//! [`serde::Deserialize`]: https://docs.serde.rs/serde/de/trait.Deserialize.html //! [`serde::Deserialize`]: https://docs.serde.rs/serde/de/trait.Deserialize.html
// mlua types in rustdoc of other crates get linked to here.
#![doc(html_root_url = "https://docs.rs/mlua/0.8.2")]
// Deny warnings inside doc tests / examples. When this isn't present, rustdoc doesn't show *any* // Deny warnings inside doc tests / examples. When this isn't present, rustdoc doesn't show *any*
// warnings at all. // warnings at all.
#![doc(test(attr(deny(warnings))))] #![doc(test(attr(warn(warnings))))] // FIXME: Remove this when rust-lang/rust#123748 is fixed
#![cfg_attr(docsrs, feature(doc_cfg))] #![cfg_attr(docsrs, feature(doc_cfg))]
#[macro_use] #[macro_use]
@@ -84,12 +82,12 @@ mod macros;
mod chunk; mod chunk;
mod conversion; mod conversion;
mod error; mod error;
mod ffi;
mod function; mod function;
mod hook; mod hook;
mod lua; mod lua;
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
mod luau; mod luau;
mod memory;
mod multi; mod multi;
mod scope; mod scope;
mod stdlib; mod stdlib;
@@ -98,16 +96,17 @@ mod table;
mod thread; mod thread;
mod types; mod types;
mod userdata; mod userdata;
mod userdata_ext;
mod userdata_impl; mod userdata_impl;
mod util; mod util;
mod value; mod value;
pub mod prelude; pub mod prelude;
pub use crate::{ffi::lua_CFunction, ffi::lua_State}; pub use ffi::{self, lua_CFunction, lua_State};
pub use crate::chunk::{AsChunk, Chunk, ChunkMode}; pub use crate::chunk::{AsChunk, Chunk, ChunkMode};
pub use crate::error::{Error, ExternalError, ExternalResult, Result}; pub use crate::error::{Error, ErrorContext, ExternalError, ExternalResult, Result};
pub use crate::function::{Function, FunctionInfo}; pub use crate::function::{Function, FunctionInfo};
pub use crate::hook::{Debug, DebugEvent, DebugNames, DebugSource, DebugStack}; pub use crate::hook::{Debug, DebugEvent, DebugNames, DebugSource, DebugStack};
pub use crate::lua::{GCMode, Lua, LuaOptions}; pub use crate::lua::{GCMode, Lua, LuaOptions};
@@ -117,18 +116,25 @@ pub use crate::stdlib::StdLib;
pub use crate::string::String; pub use crate::string::String;
pub use crate::table::{Table, TableExt, TablePairs, TableSequence}; pub use crate::table::{Table, TableExt, TablePairs, TableSequence};
pub use crate::thread::{Thread, ThreadStatus}; pub use crate::thread::{Thread, ThreadStatus};
pub use crate::types::{Integer, LightUserData, Number, RegistryKey}; pub use crate::types::{AppDataRef, AppDataRefMut, Integer, LightUserData, Number, RegistryKey};
pub use crate::userdata::{ pub use crate::userdata::{
AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMetatable, UserDataMethods, AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMetatable, UserDataMethods,
UserDataRef, UserDataRefMut,
}; };
pub use crate::value::{FromLua, FromLuaMulti, MultiValue, Nil, ToLua, ToLuaMulti, Value}; pub use crate::userdata_ext::AnyUserDataExt;
pub use crate::userdata_impl::UserDataRegistry;
pub use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, MultiValue, Nil, Value};
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
pub use crate::hook::HookTriggers; pub use crate::hook::HookTriggers;
#[cfg(any(feature = "luau", doc))] #[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))] #[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")] #[cfg(feature = "async")]
pub use crate::thread::AsyncThread; pub use crate::thread::AsyncThread;
@@ -143,17 +149,24 @@ pub use crate::serde::{
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))] #[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
pub mod serde; pub mod serde;
#[cfg(any(feature = "mlua_derive"))] #[cfg(feature = "mlua_derive")]
#[allow(unused_imports)] #[allow(unused_imports)]
#[macro_use] #[macro_use]
extern crate mlua_derive; extern crate mlua_derive;
// Unstable features
#[cfg(feature = "unstable")]
pub use crate::{
function::OwnedFunction, string::OwnedString, table::OwnedTable, thread::OwnedThread,
userdata::OwnedAnyUserData,
};
/// Create a type that implements [`AsChunk`] and can capture Rust variables. /// Create a type that implements [`AsChunk`] and can capture Rust variables.
/// ///
/// This macro allows to write Lua code directly in Rust code. /// This macro allows to write Lua code directly in Rust code.
/// ///
/// Rust variables can be referenced from Lua using `$` prefix, as shown in the example below. /// Rust variables can be referenced from Lua using `$` prefix, as shown in the example below.
/// User's Rust types needs to implement [`UserData`] or [`ToLua`] traits. /// User's Rust types needs to implement [`UserData`] or [`IntoLua`] traits.
/// ///
/// Captured variables are **moved** into the chunk. /// Captured variables are **moved** into the chunk.
/// ///
@@ -199,11 +212,19 @@ extern crate mlua_derive;
/// ///
/// [`AsChunk`]: crate::AsChunk /// [`AsChunk`]: crate::AsChunk
/// [`UserData`]: crate::UserData /// [`UserData`]: crate::UserData
/// [`ToLua`]: crate::ToLua /// [`IntoLua`]: crate::IntoLua
#[cfg(any(feature = "macros"))] #[cfg(feature = "macros")]
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))] #[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
pub use mlua_derive::chunk; 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. /// Registers Lua module entrypoint.
/// ///
/// You can register multiple entrypoints as required. /// You can register multiple entrypoints as required.
@@ -221,6 +242,43 @@ pub use mlua_derive::chunk;
/// ///
/// Internally in the code above the compiler defines C function `luaopen_my_module`. /// Internally in the code above the compiler defines C function `luaopen_my_module`.
/// ///
/// You can also pass options to the attribute:
///
/// * name - name of the module, defaults to the name of the function
///
/// ```ignore
/// #[mlua::lua_module(name = "alt_module")]
/// fn my_module(lua: &Lua) -> Result<Table> {
/// ...
/// }
/// ```
///
/// * 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(any(feature = "module", docsrs))]
#[cfg_attr(docsrs, doc(cfg(feature = "module")))] #[cfg_attr(docsrs, doc(cfg(feature = "module")))]
pub use mlua_derive::lua_module; pub use mlua_derive::lua_module;
pub(crate) mod private {
use super::*;
pub trait Sealed {}
impl Sealed for Error {}
impl<T> Sealed for std::result::Result<T, Error> {}
impl Sealed for Lua {}
impl Sealed for Table<'_> {}
impl Sealed for AnyUserData<'_> {}
}
+1451 -1070
View File
File diff suppressed because it is too large Load Diff
-124
View File
@@ -1,124 +0,0 @@
use std::ffi::CStr;
use std::os::raw::{c_float, c_int};
use crate::chunk::ChunkMode;
use crate::error::{Error, Result};
use crate::ffi;
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
impl Lua {
pub(crate) unsafe fn prepare_luau_state(&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)?)?;
Ok(())
}
}
unsafe extern "C" 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);
match option.to_str() {
Ok("collect") => {
ffi::lua_gc(state, ffi::LUA_GCCOLLECT, 0);
0
}
Ok("stop") => {
ffi::lua_gc(state, ffi::LUA_GCSTOP, 0);
0
}
Ok("restart") => {
ffi::lua_gc(state, ffi::LUA_GCRESTART, 0);
0
}
Ok("count") => {
let kbytes = ffi::lua_gc(state, ffi::LUA_GCCOUNT, 0) as ffi::lua_Number;
let kbytes_rem = ffi::lua_gc(state, ffi::LUA_GCCOUNTB, 0) as ffi::lua_Number;
ffi::lua_pushnumber(state, kbytes + kbytes_rem / 1024.0);
1
}
Ok("step") => {
let res = ffi::lua_gc(state, ffi::LUA_GCSTEP, arg);
ffi::lua_pushboolean(state, res);
1
}
Ok("isrunning") => {
let res = ffi::lua_gc(state, ffi::LUA_GCISRUNNING, 0);
ffi::lua_pushboolean(state, res);
1
}
_ => ffi::luaL_error(state, cstr!("collectgarbage called with invalid option")),
}
}
fn lua_require(lua: &Lua, name: Option<std::string::String>) -> Result<Value> {
let name = name.ok_or_else(|| Error::RuntimeError("invalid module name".into()))?;
// Find module in the cache
let loaded = unsafe {
let _sg = StackGuard::new(lua.state);
check_stack(lua.state, 2)?;
protect_lua!(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 {
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;
ffi::lua_pushvector(state, x, y, z);
1
}
+88
View File
@@ -0,0 +1,88 @@
use std::ffi::CStr;
use std::os::raw::{c_float, c_int};
use crate::error::Result;
use crate::lua::Lua;
// Since Luau has some missing standard functions, we re-implement them here
impl Lua {
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("vector", self.create_c_function(lua_vector)?)?;
// Set `_VERSION` global to include version number
// The environment variable `LUAU_VERSION` set by the build script
if let Some(version) = ffi::luau_version() {
globals.raw_set("_VERSION", format!("Luau {version}"))?;
}
Ok(())
}
pub(crate) fn disable_c_modules(&self) -> Result<()> {
package::disable_dylibs(self);
Ok(())
}
}
unsafe extern "C-unwind" fn lua_collectgarbage(state: *mut ffi::lua_State) -> c_int {
let option = ffi::luaL_optstring(state, 1, cstr!("collect"));
let option = CStr::from_ptr(option);
let arg = ffi::luaL_optinteger(state, 2, 0);
match option.to_str() {
Ok("collect") => {
ffi::lua_gc(state, ffi::LUA_GCCOLLECT, 0);
0
}
Ok("stop") => {
ffi::lua_gc(state, ffi::LUA_GCSTOP, 0);
0
}
Ok("restart") => {
ffi::lua_gc(state, ffi::LUA_GCRESTART, 0);
0
}
Ok("count") => {
let kbytes = ffi::lua_gc(state, ffi::LUA_GCCOUNT, 0) as ffi::lua_Number;
let kbytes_rem = ffi::lua_gc(state, ffi::LUA_GCCOUNTB, 0) as ffi::lua_Number;
ffi::lua_pushnumber(state, kbytes + kbytes_rem / 1024.0);
1
}
Ok("step") => {
let res = ffi::lua_gc(state, ffi::LUA_GCSTEP, arg);
ffi::lua_pushboolean(state, res);
1
}
Ok("isrunning") => {
let res = ffi::lua_gc(state, ffi::LUA_GCISRUNNING, 0);
ffi::lua_pushboolean(state, res);
1
}
_ => ffi::luaL_error(state, cstr!("collectgarbage called with invalid option")),
}
}
// Luau vector datatype constructor
unsafe extern "C-unwind" fn lua_vector(state: *mut ffi::lua_State) -> c_int {
let x = ffi::luaL_checknumber(state, 1) as c_float;
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;
+269
View File
@@ -0,0 +1,269 @@
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(())
}
#[allow(unused_variables)]
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)
}
+8 -2
View File
@@ -101,9 +101,15 @@ macro_rules! protect_lua {
}; };
($state:expr, $nargs:expr, $nresults:expr, fn($state_inner:ident) $code:expr) => {{ ($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; $code;
$nresults let nresults = $nresults;
if nresults == ::ffi::LUA_MULTRET {
ffi::lua_gettop($state_inner)
} else {
nresults
}
} }
crate::util::protect_lua_call($state, $nargs, do_call) crate::util::protect_lua_call($state, $nargs, do_call)
+147
View File
@@ -0,0 +1,147 @@
use std::alloc::{self, Layout};
use std::os::raw::c_void;
use std::ptr;
pub(crate) static ALLOCATOR: ffi::lua_Alloc = allocator;
#[repr(C)]
#[derive(Default)]
pub(crate) struct MemoryState {
used_memory: isize,
memory_limit: isize,
// Can be set to temporary ignore the memory limit.
// This is used when calling `lua_pushcfunction` for lua5.1/jit/luau.
ignore_limit: bool,
// Indicates that the memory limit was reached on the last allocation.
#[cfg(feature = "luau")]
limit_reached: bool,
}
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
}
#[inline]
pub(crate) fn memory_limit(&self) -> usize {
self.memory_limit as usize
}
#[inline]
pub(crate) fn set_memory_limit(&mut self, limit: usize) -> usize {
let prev_limit = self.memory_limit;
self.memory_limit = limit as isize;
prev_limit as usize
}
// 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", feature = "luau"))]
#[inline]
pub(crate) unsafe fn relax_limit_with(state: *mut ffi::lua_State, f: impl FnOnce()) {
let mem_state = Self::get(state);
if !mem_state.is_null() {
(*mem_state).ignore_limit = true;
f();
(*mem_state).ignore_limit = false;
} else {
f();
}
}
// Does nothing apart from calling `f()`, we don't need to bypass any limits
#[cfg(any(feature = "lua52", feature = "lua53", feature = "lua54"))]
#[inline]
pub(crate) unsafe fn relax_limit_with(_state: *mut ffi::lua_State, f: impl FnOnce()) {
f();
}
// 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 {
(*Self::get(state)).limit_reached
}
}
unsafe extern "C-unwind" fn allocator(
extra: *mut c_void,
ptr: *mut c_void,
osize: usize,
nsize: usize,
) -> *mut c_void {
let mem_state = &mut *(extra as *mut MemoryState);
#[cfg(feature = "luau")]
{
// Reset the flag
mem_state.limit_reached = false;
}
if nsize == 0 {
// Free memory
if !ptr.is_null() {
let layout = Layout::from_size_align_unchecked(osize, ffi::SYS_MIN_ALIGN);
alloc::dealloc(ptr as *mut u8, layout);
mem_state.used_memory -= osize as isize;
}
return ptr::null_mut();
}
// Do not allocate more than isize::MAX
if nsize > isize::MAX as usize {
return ptr::null_mut();
}
// Are we fit to the memory limits?
let mut mem_diff = nsize as isize;
if !ptr.is_null() {
mem_diff -= osize as isize;
}
let mem_limit = mem_state.memory_limit;
let new_used_memory = mem_state.used_memory + mem_diff;
if mem_limit > 0 && new_used_memory > mem_limit && !mem_state.ignore_limit {
#[cfg(feature = "luau")]
{
mem_state.limit_reached = true;
}
return ptr::null_mut();
}
mem_state.used_memory += mem_diff;
if ptr.is_null() {
// Allocate new memory
let new_layout = match Layout::from_size_align(nsize, ffi::SYS_MIN_ALIGN) {
Ok(layout) => layout,
Err(_) => return ptr::null_mut(),
};
let new_ptr = alloc::alloc(new_layout) as *mut c_void;
if new_ptr.is_null() {
alloc::handle_alloc_error(new_layout);
}
return new_ptr;
}
// Reallocate memory
let old_layout = Layout::from_size_align_unchecked(osize, ffi::SYS_MIN_ALIGN);
let new_ptr = alloc::realloc(ptr as *mut u8, old_layout, nsize) as *mut c_void;
if new_ptr.is_null() {
alloc::handle_alloc_error(old_layout);
}
new_ptr
}
+187 -43
View File
@@ -1,51 +1,128 @@
#![allow(clippy::wrong_self_convention)]
use std::iter::FromIterator;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use std::os::raw::c_int;
use std::result::Result as StdResult; use std::result::Result as StdResult;
use crate::error::Result; use crate::error::Result;
use crate::lua::Lua; use crate::lua::Lua;
use crate::value::{FromLua, FromLuaMulti, MultiValue, Nil, ToLua, ToLuaMulti}; 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 /// Result is convertible to `MultiValue` following the common Lua idiom of returning the result
/// on success, or in the case of an error, returning `nil` and an error message. /// on success, or in the case of an error, returning `nil` and an error message.
impl<'lua, T: ToLua<'lua>, E: ToLua<'lua>> ToLuaMulti<'lua> for StdResult<T, E> { impl<'lua, T: IntoLua<'lua>, E: IntoLua<'lua>> IntoLuaMulti<'lua> for StdResult<T, E> {
#[inline] #[inline]
fn to_lua_multi(self, lua: &'lua Lua) -> Result<MultiValue<'lua>> { fn into_lua_multi(self, lua: &'lua Lua) -> Result<MultiValue<'lua>> {
let mut result = MultiValue::new_or_cached(lua); let mut result = MultiValue::with_lua_and_capacity(lua, 2);
match self { match self {
Ok(v) => result.push_front(v.to_lua(lua)?), Ok(v) => result.push_front(v.into_lua(lua)?),
Err(e) => { Err(e) => {
result.push_front(e.to_lua(lua)?); result.push_front(e.into_lua(lua)?);
result.push_front(Nil); result.push_front(Nil);
} }
} }
Ok(result) Ok(result)
} }
#[inline]
unsafe fn push_into_stack_multi(self, lua: &'lua Lua) -> Result<c_int> {
match self {
Ok(v) => v.push_into_stack(lua).map(|_| 1),
Err(e) => {
let state = lua.state();
check_stack(state, 3)?;
ffi::lua_pushnil(state);
e.push_into_stack(lua)?;
Ok(2)
}
}
}
} }
impl<'lua, T: ToLua<'lua>> ToLuaMulti<'lua> for T { impl<'lua, E: IntoLua<'lua>> IntoLuaMulti<'lua> for StdResult<(), E> {
#[inline] #[inline]
fn to_lua_multi(self, lua: &'lua Lua) -> Result<MultiValue<'lua>> { fn into_lua_multi(self, lua: &'lua Lua) -> Result<MultiValue<'lua>> {
let mut v = MultiValue::new_or_cached(lua); match self {
v.push_front(self.to_lua(lua)?); 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)
}
}
}
#[inline]
unsafe fn push_into_stack_multi(self, lua: &'lua Lua) -> Result<c_int> {
match self {
Ok(_) => Ok(0),
Err(e) => {
let state = lua.state();
check_stack(state, 3)?;
ffi::lua_pushnil(state);
e.push_into_stack(lua)?;
Ok(2)
}
}
}
}
impl<'lua, T: IntoLua<'lua>> IntoLuaMulti<'lua> for T {
#[inline]
fn into_lua_multi(self, lua: &'lua Lua) -> Result<MultiValue<'lua>> {
let mut v = MultiValue::with_lua_and_capacity(lua, 1);
v.push_front(self.into_lua(lua)?);
Ok(v) 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 { impl<'lua, T: FromLua<'lua>> FromLuaMulti<'lua> for T {
#[inline] #[inline]
fn from_lua_multi(mut values: MultiValue<'lua>, lua: &'lua Lua) -> Result<Self> { 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); T::from_lua(values.pop_front().unwrap_or(Nil), lua)
lua.cache_multivalue(values); }
res
#[inline]
fn from_lua_args(
mut args: MultiValue<'lua>,
i: usize,
to: Option<&str>,
lua: &'lua Lua,
) -> Result<Self> {
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)
} }
} }
impl<'lua> ToLuaMulti<'lua> for MultiValue<'lua> { impl<'lua> IntoLuaMulti<'lua> for MultiValue<'lua> {
#[inline] #[inline]
fn to_lua_multi(self, _: &'lua Lua) -> Result<MultiValue<'lua>> { fn into_lua_multi(self, _: &'lua Lua) -> Result<MultiValue<'lua>> {
Ok(self) Ok(self)
} }
} }
@@ -128,11 +205,11 @@ impl<T> DerefMut for Variadic<T> {
} }
} }
impl<'lua, T: ToLua<'lua>> ToLuaMulti<'lua> for Variadic<T> { impl<'lua, T: IntoLua<'lua>> IntoLuaMulti<'lua> for Variadic<T> {
#[inline] #[inline]
fn to_lua_multi(self, lua: &'lua Lua) -> Result<MultiValue<'lua>> { fn into_lua_multi(self, lua: &'lua Lua) -> Result<MultiValue<'lua>> {
let mut values = MultiValue::new_or_cached(lua); let mut values = MultiValue::with_lua_and_capacity(lua, self.0.len());
values.refill(self.0.into_iter().map(|e| e.to_lua(lua)))?; values.refill(self.0.into_iter().map(|e| e.into_lua(lua)))?;
Ok(values) Ok(values)
} }
} }
@@ -140,62 +217,129 @@ impl<'lua, T: ToLua<'lua>> ToLuaMulti<'lua> for Variadic<T> {
impl<'lua, T: FromLua<'lua>> FromLuaMulti<'lua> for Variadic<T> { impl<'lua, T: FromLua<'lua>> FromLuaMulti<'lua> for Variadic<T> {
#[inline] #[inline]
fn from_lua_multi(mut values: MultiValue<'lua>, lua: &'lua Lua) -> Result<Self> { fn from_lua_multi(mut values: MultiValue<'lua>, lua: &'lua Lua) -> Result<Self> {
let res = values values
.drain_all() .drain_all()
.map(|e| T::from_lua(e, lua)) .map(|e| T::from_lua(e, lua))
.collect::<Result<Vec<T>>>() .collect::<Result<Vec<T>>>()
.map(Variadic); .map(Variadic)
lua.cache_multivalue(values);
res
} }
} }
macro_rules! impl_tuple { macro_rules! impl_tuple {
() => ( () => (
impl<'lua> ToLuaMulti<'lua> for () { impl<'lua> IntoLuaMulti<'lua> for () {
#[inline] #[inline]
fn to_lua_multi(self, lua: &'lua Lua) -> Result<MultiValue<'lua>> { fn into_lua_multi(self, lua: &'lua Lua) -> Result<MultiValue<'lua>> {
Ok(MultiValue::new_or_cached(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 () { impl<'lua> FromLuaMulti<'lua> for () {
#[inline] #[inline]
fn from_lua_multi(values: MultiValue<'lua>, lua: &'lua Lua) -> Result<Self> { fn from_lua_multi(_values: MultiValue<'lua>, _lua: &'lua Lua) -> Result<Self> {
lua.cache_multivalue(values); 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(()) Ok(())
} }
} }
); );
($last:ident $($name:ident)*) => ( ($last:ident $($name:ident)*) => (
impl<'lua, $($name,)* $last> ToLuaMulti<'lua> for ($($name,)* $last,) impl<'lua, $($name,)* $last> IntoLuaMulti<'lua> for ($($name,)* $last,)
where $($name: ToLua<'lua>,)* where $($name: IntoLua<'lua>,)*
$last: ToLuaMulti<'lua> $last: IntoLuaMulti<'lua>
{ {
#[allow(unused_mut)] #[allow(unused_mut, non_snake_case)]
#[allow(non_snake_case)]
#[inline] #[inline]
fn to_lua_multi(self, lua: &'lua Lua) -> Result<MultiValue<'lua>> { fn into_lua_multi(self, lua: &'lua Lua) -> Result<MultiValue<'lua>> {
let ($($name,)* $last,) = self; let ($($name,)* $last,) = self;
let mut results = $last.to_lua_multi(lua)?; let mut results = $last.into_lua_multi(lua)?;
push_reverse!(results, $($name.to_lua(lua)?,)*); push_reverse!(results, $($name.into_lua(lua)?,)*);
Ok(results) 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,) impl<'lua, $($name,)* $last> FromLuaMulti<'lua> for ($($name,)* $last,)
where $($name: FromLua<'lua>,)* where $($name: FromLua<'lua>,)*
$last: FromLuaMulti<'lua> $last: FromLuaMulti<'lua>
{ {
#[allow(unused_mut)] #[allow(unused_mut, non_snake_case)]
#[allow(non_snake_case)]
#[inline] #[inline]
fn from_lua_multi(mut values: MultiValue<'lua>, lua: &'lua Lua) -> Result<Self> { fn from_lua_multi(mut values: MultiValue<'lua>, lua: &'lua Lua) -> Result<Self> {
$(let $name = values.pop_front().unwrap_or(Nil);)* $(let $name = FromLua::from_lua(values.pop_front().unwrap_or(Nil), lua)?;)*
let $last = FromLuaMulti::from_lua_multi(values, lua)?; let $last = FromLuaMulti::from_lua_multi(values, lua)?;
Ok(($(FromLua::from_lua($name, lua)?,)* $last,)) Ok(($($name,)* $last,))
}
#[allow(unused_mut, non_snake_case)]
#[inline]
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(args.pop_front().unwrap_or(Nil), i, to, lua)?;
i += 1;
)*
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,))
} }
} }
); );
+20 -11
View File
@@ -2,16 +2,18 @@
#[doc(no_inline)] #[doc(no_inline)]
pub use crate::{ pub use crate::{
AnyUserData as LuaAnyUserData, Chunk as LuaChunk, Error as LuaError, AnyUserData as LuaAnyUserData, AnyUserDataExt as LuaAnyUserDataExt, Chunk as LuaChunk,
ExternalError as LuaExternalError, ExternalResult as LuaExternalResult, FromLua, FromLuaMulti, Error as LuaError, ErrorContext as LuaErrorContext, ExternalError as LuaExternalError,
Function as LuaFunction, FunctionInfo as LuaFunctionInfo, GCMode as LuaGCMode, ExternalResult as LuaExternalResult, FromLua, FromLuaMulti, Function as LuaFunction,
Integer as LuaInteger, LightUserData as LuaLightUserData, Lua, LuaOptions, FunctionInfo as LuaFunctionInfo, GCMode as LuaGCMode, Integer as LuaInteger, IntoLua,
MetaMethod as LuaMetaMethod, MultiValue as LuaMultiValue, Nil as LuaNil, Number as LuaNumber, IntoLuaMulti, LightUserData as LuaLightUserData, Lua, LuaOptions, MetaMethod as LuaMetaMethod,
RegistryKey as LuaRegistryKey, Result as LuaResult, StdLib as LuaStdLib, String as LuaString, MultiValue as LuaMultiValue, Nil as LuaNil, Number as LuaNumber, RegistryKey as LuaRegistryKey,
Table as LuaTable, TableExt as LuaTableExt, TablePairs as LuaTablePairs, Result as LuaResult, StdLib as LuaStdLib, String as LuaString, Table as LuaTable,
TableSequence as LuaTableSequence, Thread as LuaThread, ThreadStatus as LuaThreadStatus, ToLua, TableExt as LuaTableExt, TablePairs as LuaTablePairs, TableSequence as LuaTableSequence,
ToLuaMulti, UserData as LuaUserData, UserDataFields as LuaUserDataFields, Thread as LuaThread, ThreadStatus as LuaThreadStatus, UserData as LuaUserData,
UserDataMetatable as LuaUserDataMetatable, UserDataMethods as LuaUserDataMethods, UserDataFields as LuaUserDataFields, UserDataMetatable as LuaUserDataMetatable,
UserDataMethods as LuaUserDataMethods, UserDataRef as LuaUserDataRef,
UserDataRefMut as LuaUserDataRefMut, UserDataRegistry as LuaUserDataRegistry,
Value as LuaValue, Value as LuaValue,
}; };
@@ -21,7 +23,7 @@ pub use crate::HookTriggers as LuaHookTriggers;
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
#[doc(no_inline)] #[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")] #[cfg(feature = "async")]
#[doc(no_inline)] #[doc(no_inline)]
@@ -33,3 +35,10 @@ pub use crate::{
DeserializeOptions as LuaDeserializeOptions, LuaSerdeExt, DeserializeOptions as LuaDeserializeOptions, LuaSerdeExt,
SerializeOptions as LuaSerializeOptions, SerializeOptions as LuaSerializeOptions,
}; };
#[cfg(feature = "unstable")]
#[doc(no_inline)]
pub use crate::{
OwnedAnyUserData as LuaOwnedAnyUserData, OwnedFunction as LuaOwnedFunction,
OwnedString as LuaOwnedString, OwnedTable as LuaOwnedTable, OwnedThread as LuaOwnedThread,
};
+570 -524
View File
File diff suppressed because it is too large Load Diff
+152 -36
View File
@@ -1,7 +1,7 @@
use std::cell::RefCell; use std::cell::RefCell;
use std::convert::TryInto;
use std::os::raw::c_void; use std::os::raw::c_void;
use std::rc::Rc; use std::rc::Rc;
use std::result::Result as StdResult;
use std::string::String as StdString; use std::string::String as StdString;
use rustc_hash::FxHashSet; use rustc_hash::FxHashSet;
@@ -9,6 +9,7 @@ use serde::de::{self, IntoDeserializer};
use crate::error::{Error, Result}; use crate::error::{Error, Result};
use crate::table::{Table, TablePairs, TableSequence}; use crate::table::{Table, TablePairs, TableSequence};
use crate::userdata::AnyUserData;
use crate::value::Value; use crate::value::Value;
/// A struct for deserializing Lua values into Rust values. /// A struct for deserializing Lua values into Rust values.
@@ -23,14 +24,14 @@ pub struct Deserializer<'lua> {
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
#[non_exhaustive] #[non_exhaustive]
pub struct Options { 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. /// and [`Error`] will cause an error.
/// Otherwise these types skipped when iterating or serialized as unit type. /// Otherwise these types skipped when iterating or serialized as unit type.
/// ///
/// Default: **true** /// Default: **true**
/// ///
/// [`Function`]: crate::Function
/// [`Thread`]: crate::Thread /// [`Thread`]: crate::Thread
/// [`UserData`]: crate::UserData
/// [`LightUserData`]: crate::LightUserData /// [`LightUserData`]: crate::LightUserData
/// [`Error`]: crate::Error /// [`Error`]: crate::Error
pub deny_unsupported_types: bool, pub deny_unsupported_types: bool,
@@ -41,6 +42,11 @@ pub struct Options {
/// ///
/// Default: **true** /// Default: **true**
pub deny_recursive_tables: bool, 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 { impl Default for Options {
@@ -55,6 +61,7 @@ impl Options {
Options { Options {
deny_unsupported_types: true, deny_unsupported_types: true,
deny_recursive_tables: true, deny_recursive_tables: true,
sort_keys: false,
} }
} }
@@ -71,10 +78,19 @@ impl Options {
/// ///
/// [`deny_recursive_tables`]: #structfield.deny_recursive_tables /// [`deny_recursive_tables`]: #structfield.deny_recursive_tables
#[must_use] #[must_use]
pub fn deny_recursive_tables(mut self, enabled: bool) -> Self { pub const fn deny_recursive_tables(mut self, enabled: bool) -> Self {
self.deny_recursive_tables = enabled; self.deny_recursive_tables = enabled;
self 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> { impl<'lua> Deserializer<'lua> {
@@ -117,13 +133,11 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
Value::Nil => visitor.visit_unit(), Value::Nil => visitor.visit_unit(),
Value::Boolean(b) => visitor.visit_bool(b), Value::Boolean(b) => visitor.visit_bool(b),
#[allow(clippy::useless_conversion)] #[allow(clippy::useless_conversion)]
Value::Integer(i) => { Value::Integer(i) => visitor.visit_i64(i.into()),
visitor.visit_i64(i.try_into().expect("cannot convert lua_Integer to i64"))
}
#[allow(clippy::useless_conversion)] #[allow(clippy::useless_conversion)]
Value::Number(n) => visitor.visit_f64(n.into()), Value::Number(n) => visitor.visit_f64(n.into()),
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
Value::Vector(_, _, _) => self.deserialize_seq(visitor), Value::Vector(_) => self.deserialize_seq(visitor),
Value::String(s) => match s.to_str() { Value::String(s) => match s.to_str() {
Ok(s) => visitor.visit_str(s), Ok(s) => visitor.visit_str(s),
Err(_) => visitor.visit_bytes(s.as_bytes()), Err(_) => visitor.visit_bytes(s.as_bytes()),
@@ -131,16 +145,25 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
Value::Table(ref t) if t.raw_len() > 0 || t.is_array() => self.deserialize_seq(visitor), Value::Table(ref t) if t.raw_len() > 0 || t.is_array() => self.deserialize_seq(visitor),
Value::Table(_) => self.deserialize_map(visitor), Value::Table(_) => self.deserialize_map(visitor),
Value::LightUserData(ud) if ud.0.is_null() => visitor.visit_none(), Value::LightUserData(ud) if ud.0.is_null() => visitor.visit_none(),
Value::UserData(ud) if ud.is_serializable() => {
serde_userdata(ud, |value| value.deserialize_any(visitor))
}
#[cfg(feature = "luau")]
Value::UserData(ud) if ud.1 == crate::types::SubtypeId::Buffer => unsafe {
let mut size = 0usize;
let buf = ffi::lua_tobuffer(ud.0.lua.ref_thread(), ud.0.index, &mut size);
mlua_assert!(!buf.is_null(), "invalid Luau buffer");
let buf = std::slice::from_raw_parts(buf as *const u8, size);
visitor.visit_bytes(buf)
},
Value::Function(_) Value::Function(_)
| Value::Thread(_) | Value::Thread(_)
| Value::UserData(_) | Value::UserData(_)
| Value::LightUserData(_) | Value::LightUserData(_)
| Value::Error(_) => { | Value::Error(_) => {
if self.options.deny_unsupported_types { if self.options.deny_unsupported_types {
Err(de::Error::custom(format!( let msg = format!("unsupported value type `{}`", self.value.type_name());
"unsupported value type `{}`", Err(de::Error::custom(msg))
self.value.type_name()
)))
} else { } else {
visitor.visit_unit() visitor.visit_unit()
} }
@@ -163,8 +186,8 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
#[inline] #[inline]
fn deserialize_enum<V>( fn deserialize_enum<V>(
self, self,
_name: &str, name: &'static str,
_variants: &'static [&'static str], variants: &'static [&'static str],
visitor: V, visitor: V,
) -> Result<V::Value> ) -> Result<V::Value>
where where
@@ -191,13 +214,18 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
&"map with a single key", &"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")); return Err(de::Error::custom("bad enum value"));
} }
(variant, Some(value), Some(_guard)) (variant, Some(value), Some(_guard))
} }
Value::String(variant) => (variant.to_str()?.to_owned(), None, None), Value::String(variant) => (variant.to_str()?.to_owned(), None, None),
Value::UserData(ud) if ud.is_serializable() => {
return serde_userdata(ud, |value| value.deserialize_enum(name, variants, visitor));
}
_ => return Err(de::Error::custom("bad enum value")), _ => return Err(de::Error::custom("bad enum value")),
}; };
@@ -216,9 +244,9 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
{ {
match self.value { match self.value {
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
Value::Vector(x, y, z) => { Value::Vector(vec) => {
let mut deserializer = VecDeserializer { let mut deserializer = VecDeserializer {
vec: [x, y, z], vec,
next: 0, next: 0,
options: self.options, options: self.options,
visited: self.visited, visited: self.visited,
@@ -228,9 +256,9 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
Value::Table(t) => { Value::Table(t) => {
let _guard = RecursionGuard::new(&t, &self.visited); let _guard = RecursionGuard::new(&t, &self.visited);
let len = t.raw_len() as usize; let len = t.raw_len();
let mut deserializer = SeqDeserializer { let mut deserializer = SeqDeserializer {
seq: t.raw_sequence_values(), seq: t.sequence_values(),
options: self.options, options: self.options,
visited: self.visited, visited: self.visited,
}; };
@@ -244,6 +272,9 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
)) ))
} }
} }
Value::UserData(ud) if ud.is_serializable() => {
serde_userdata(ud, |value| value.deserialize_seq(visitor))
}
value => Err(de::Error::invalid_type( value => Err(de::Error::invalid_type(
de::Unexpected::Other(value.type_name()), de::Unexpected::Other(value.type_name()),
&"table", &"table",
@@ -282,7 +313,7 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
let _guard = RecursionGuard::new(&t, &self.visited); let _guard = RecursionGuard::new(&t, &self.visited);
let mut deserializer = MapDeserializer { let mut deserializer = MapDeserializer {
pairs: t.pairs(), pairs: MapPairs::new(t, self.options.sort_keys)?,
value: None, value: None,
options: self.options, options: self.options,
visited: self.visited, visited: self.visited,
@@ -299,6 +330,9 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
)) ))
} }
} }
Value::UserData(ud) if ud.is_serializable() => {
serde_userdata(ud, |value| value.deserialize_map(visitor))
}
value => Err(de::Error::invalid_type( value => Err(de::Error::invalid_type(
de::Unexpected::Other(value.type_name()), de::Unexpected::Other(value.type_name()),
&"table", &"table",
@@ -320,16 +354,43 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
} }
#[inline] #[inline]
fn deserialize_newtype_struct<V>(self, _name: &'static str, visitor: V) -> Result<V::Value> fn deserialize_newtype_struct<V>(self, name: &'static str, visitor: V) -> Result<V::Value>
where where
V: de::Visitor<'de>, V: de::Visitor<'de>,
{ {
visitor.visit_newtype_struct(self) match self.value {
Value::UserData(ud) if ud.is_serializable() => {
serde_userdata(ud, |value| value.deserialize_newtype_struct(name, visitor))
}
_ => visitor.visit_newtype_struct(self),
}
}
#[inline]
fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value>
where
V: de::Visitor<'de>,
{
match self.value {
Value::LightUserData(ud) if ud.0.is_null() => visitor.visit_unit(),
_ => self.deserialize_any(visitor),
}
}
#[inline]
fn deserialize_unit_struct<V>(self, _name: &'static str, visitor: V) -> Result<V::Value>
where
V: de::Visitor<'de>,
{
match self.value {
Value::LightUserData(ud) if ud.0.is_null() => visitor.visit_unit(),
_ => self.deserialize_any(visitor),
}
} }
serde::forward_to_deserialize_any! { serde::forward_to_deserialize_any! {
bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string bytes bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string bytes
byte_buf unit unit_struct identifier ignored_any byte_buf identifier ignored_any
} }
} }
@@ -350,7 +411,9 @@ impl<'lua, 'de> de::SeqAccess<'de> for SeqDeserializer<'lua> {
match self.seq.next() { match self.seq.next() {
Some(value) => { Some(value) => {
let value = 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; continue;
} }
let visited = Rc::clone(&self.visited); let visited = Rc::clone(&self.visited);
@@ -372,7 +435,7 @@ impl<'lua, 'de> de::SeqAccess<'de> for SeqDeserializer<'lua> {
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
struct VecDeserializer { struct VecDeserializer {
vec: [f32; 3], vec: crate::types::Vector,
next: usize, next: usize,
options: Options, options: Options,
visited: Rc<RefCell<FxHashSet<*const c_void>>>, visited: Rc<RefCell<FxHashSet<*const c_void>>>,
@@ -386,7 +449,7 @@ impl<'de> de::SeqAccess<'de> for VecDeserializer {
where where
T: de::DeserializeSeed<'de>, T: de::DeserializeSeed<'de>,
{ {
match self.vec.get(self.next) { match self.vec.0.get(self.next) {
Some(&n) => { Some(&n) => {
self.next += 1; self.next += 1;
let visited = Rc::clone(&self.visited); let visited = Rc::clone(&self.visited);
@@ -399,12 +462,54 @@ impl<'de> de::SeqAccess<'de> for VecDeserializer {
} }
fn size_hint(&self) -> Option<usize> { 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> { struct MapDeserializer<'lua> {
pairs: TablePairs<'lua, Value<'lua>, Value<'lua>>, pairs: MapPairs<'lua>,
value: Option<Value<'lua>>, value: Option<Value<'lua>>,
options: Options, options: Options,
visited: Rc<RefCell<FxHashSet<*const c_void>>>, visited: Rc<RefCell<FxHashSet<*const c_void>>>,
@@ -422,9 +527,11 @@ impl<'lua, 'de> de::MapAccess<'de> for MapDeserializer<'lua> {
match self.pairs.next() { match self.pairs.next() {
Some(item) => { Some(item) => {
let (key, value) = item?; let (key, value) = item?;
if check_value_if_skip(&key, self.options, &self.visited)? let skip_key = check_value_for_skip(&key, self.options, &self.visited)
|| check_value_if_skip(&value, 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; continue;
} }
self.processed += 1; self.processed += 1;
@@ -553,14 +660,14 @@ impl<'lua, 'de> de::VariantAccess<'de> for VariantDeserializer<'lua> {
// Adds `ptr` to the `visited` map and removes on drop // Adds `ptr` to the `visited` map and removes on drop
// Used to track recursive tables but allow to traverse same tables multiple times // Used to track recursive tables but allow to traverse same tables multiple times
struct RecursionGuard { pub(crate) struct RecursionGuard {
ptr: *const c_void, ptr: *const c_void,
visited: Rc<RefCell<FxHashSet<*const c_void>>>, visited: Rc<RefCell<FxHashSet<*const c_void>>>,
} }
impl RecursionGuard { impl RecursionGuard {
#[inline] #[inline]
fn new(table: &Table, visited: &Rc<RefCell<FxHashSet<*const c_void>>>) -> Self { pub(crate) fn new(table: &Table, visited: &Rc<RefCell<FxHashSet<*const c_void>>>) -> Self {
let visited = Rc::clone(visited); let visited = Rc::clone(visited);
let ptr = table.to_pointer(); let ptr = table.to_pointer();
visited.borrow_mut().insert(ptr); visited.borrow_mut().insert(ptr);
@@ -575,21 +682,22 @@ impl Drop for RecursionGuard {
} }
// Checks `options` and decides should we emit an error or skip next element // 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, value: &Value,
options: Options, options: Options,
visited: &RefCell<FxHashSet<*const c_void>>, visited: &RefCell<FxHashSet<*const c_void>>,
) -> Result<bool> { ) -> StdResult<bool, &'static str> {
match value { match value {
Value::Table(table) => { Value::Table(table) => {
let ptr = table.to_pointer(); let ptr = table.to_pointer();
if visited.borrow().contains(&ptr) { if visited.borrow().contains(&ptr) {
if options.deny_recursive_tables { if options.deny_recursive_tables {
return Err(de::Error::custom("recursive table detected")); return Err("recursive table detected");
} }
return Ok(true); // skip return Ok(true); // skip
} }
} }
Value::UserData(ud) if ud.is_serializable() => {}
Value::Function(_) Value::Function(_)
| Value::Thread(_) | Value::Thread(_)
| Value::UserData(_) | Value::UserData(_)
@@ -603,3 +711,11 @@ fn check_value_if_skip(
} }
Ok(false) // do not skip Ok(false) // do not skip
} }
fn serde_userdata<V>(
ud: AnyUserData,
f: impl FnOnce(serde_value::Value) -> std::result::Result<V, serde_value::DeserializerError>,
) -> Result<V> {
let value = serde_value::to_value(ud).map_err(|err| Error::SerializeError(err.to_string()))?;
f(value).map_err(|err| Error::DeserializeError(err.to_string()))
}
+23 -32
View File
@@ -1,21 +1,19 @@
//! (De)Serialization support using serde. //! (De)Serialization support using serde.
use std::os::raw::c_void; use std::os::raw::c_void;
use std::ptr;
use serde::{Deserialize, Serialize}; use serde::{de::DeserializeOwned, ser::Serialize};
use crate::error::Result; use crate::error::Result;
use crate::ffi;
use crate::lua::Lua; use crate::lua::Lua;
use crate::private::Sealed;
use crate::table::Table; use crate::table::Table;
use crate::types::LightUserData; use crate::util::check_stack;
use crate::util::{assert_stack, check_stack, StackGuard};
use crate::value::Value; use crate::value::Value;
/// Trait for serializing/deserializing Lua values using Serde. /// Trait for serializing/deserializing Lua values using Serde.
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))] #[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
pub trait LuaSerdeExt<'lua> { pub trait LuaSerdeExt: Sealed {
/// A special value (lightuserdata) to encode/decode optional (none) values. /// A special value (lightuserdata) to encode/decode optional (none) values.
/// ///
/// Requires `feature = "serialize"` /// Requires `feature = "serialize"`
@@ -37,7 +35,7 @@ pub trait LuaSerdeExt<'lua> {
/// Ok(()) /// Ok(())
/// } /// }
/// ``` /// ```
fn null(&'lua self) -> Value<'lua>; fn null(&self) -> Value;
/// A metatable attachable to a Lua table to systematically encode it as Array (instead of Map). /// A metatable attachable to a Lua table to systematically encode it as Array (instead of Map).
/// As result, encoded Array will contain only sequence part of the table, with the same length /// As result, encoded Array will contain only sequence part of the table, with the same length
@@ -68,7 +66,7 @@ pub trait LuaSerdeExt<'lua> {
/// Ok(()) /// Ok(())
/// } /// }
/// ``` /// ```
fn array_metatable(&'lua self) -> Table<'lua>; fn array_metatable(&self) -> Table;
/// Converts `T` into a [`Value`] instance. /// Converts `T` into a [`Value`] instance.
/// ///
@@ -101,7 +99,7 @@ pub trait LuaSerdeExt<'lua> {
/// "#).exec() /// "#).exec()
/// } /// }
/// ``` /// ```
fn to_value<T: Serialize + ?Sized>(&'lua self, t: &T) -> Result<Value<'lua>>; fn to_value<'lua, T: Serialize + ?Sized>(&'lua self, t: &T) -> Result<Value<'lua>>;
/// Converts `T` into a [`Value`] instance with options. /// Converts `T` into a [`Value`] instance with options.
/// ///
@@ -126,7 +124,7 @@ pub trait LuaSerdeExt<'lua> {
/// "#).exec() /// "#).exec()
/// } /// }
/// ``` /// ```
fn to_value_with<T>(&'lua self, t: &T, options: ser::Options) -> Result<Value<'lua>> fn to_value_with<'lua, T>(&'lua self, t: &T, options: ser::Options) -> Result<Value<'lua>>
where where
T: Serialize + ?Sized; T: Serialize + ?Sized;
@@ -159,7 +157,7 @@ pub trait LuaSerdeExt<'lua> {
/// } /// }
/// ``` /// ```
#[allow(clippy::wrong_self_convention)] #[allow(clippy::wrong_self_convention)]
fn from_value<T: Deserialize<'lua>>(&'lua self, value: Value<'lua>) -> Result<T>; fn from_value<T: DeserializeOwned>(&self, value: Value) -> Result<T>;
/// Deserializes a [`Value`] into any serde deserializable object with options. /// Deserializes a [`Value`] into any serde deserializable object with options.
/// ///
@@ -191,53 +189,46 @@ pub trait LuaSerdeExt<'lua> {
/// } /// }
/// ``` /// ```
#[allow(clippy::wrong_self_convention)] #[allow(clippy::wrong_self_convention)]
fn from_value_with<T: Deserialize<'lua>>( fn from_value_with<T: DeserializeOwned>(&self, value: Value, options: de::Options)
&'lua self, -> Result<T>;
value: Value<'lua>,
options: de::Options,
) -> Result<T>;
} }
impl<'lua> LuaSerdeExt<'lua> for Lua { impl LuaSerdeExt for Lua {
fn null(&'lua self) -> Value<'lua> { fn null(&self) -> Value {
Value::LightUserData(LightUserData(ptr::null_mut())) Value::NULL
} }
fn array_metatable(&'lua self) -> Table<'lua> { fn array_metatable(&self) -> Table {
unsafe { unsafe {
let _sg = StackGuard::new(self.state); push_array_metatable(self.ref_thread());
assert_stack(self.state, 1); Table(self.pop_ref_thread())
push_array_metatable(self.state);
Table(self.pop_ref())
} }
} }
fn to_value<T>(&'lua self, t: &T) -> Result<Value<'lua>> fn to_value<'lua, T>(&'lua self, t: &T) -> Result<Value<'lua>>
where where
T: Serialize + ?Sized, T: Serialize + ?Sized,
{ {
t.serialize(ser::Serializer::new(self)) t.serialize(ser::Serializer::new(self))
} }
fn to_value_with<T>(&'lua self, t: &T, options: ser::Options) -> Result<Value<'lua>> fn to_value_with<'lua, T>(&'lua self, t: &T, options: ser::Options) -> Result<Value<'lua>>
where where
T: Serialize + ?Sized, T: Serialize + ?Sized,
{ {
t.serialize(ser::Serializer::new_with_options(self, options)) t.serialize(ser::Serializer::new_with_options(self, options))
} }
fn from_value<T>(&'lua self, value: Value<'lua>) -> Result<T> fn from_value<T>(&self, value: Value) -> Result<T>
where where
T: Deserialize<'lua>, T: DeserializeOwned,
{ {
T::deserialize(de::Deserializer::new(value)) T::deserialize(de::Deserializer::new(value))
} }
fn from_value_with<T>(&'lua self, value: Value<'lua>, options: de::Options) -> Result<T> fn from_value_with<T>(&self, value: Value, options: de::Options) -> Result<T>
where where
T: Deserialize<'lua>, T: DeserializeOwned,
{ {
T::deserialize(de::Deserializer::new_with_options(value, options)) T::deserialize(de::Deserializer::new_with_options(value, options))
} }
+150 -62
View File
@@ -1,16 +1,10 @@
use std::os::raw::c_int;
use serde::{ser, Serialize}; use serde::{ser, Serialize};
use super::LuaSerdeExt; use super::LuaSerdeExt;
use crate::error::{Error, Result}; use crate::error::{Error, Result};
use crate::ffi;
use crate::lua::Lua; use crate::lua::Lua;
use crate::string::String;
use crate::table::Table; use crate::table::Table;
use crate::types::Integer; use crate::value::{IntoLua, Value};
use crate::util::{check_stack, StackGuard};
use crate::value::{ToLua, Value};
/// A struct for serializing Rust values into Lua values. /// A struct for serializing Rust values into Lua values.
#[derive(Debug)] #[derive(Debug)]
@@ -48,6 +42,12 @@ pub struct Options {
/// [`null`]: crate::LuaSerdeExt::null /// [`null`]: crate::LuaSerdeExt::null
/// [`Nil`]: crate::Value::Nil /// [`Nil`]: crate::Value::Nil
pub serialize_unit_to_null: bool, pub serialize_unit_to_null: bool,
/// If true, serialize `serde_json::Number` with arbitrary_precision to a Lua number.
/// Otherwise it will be serialized as an object (what serde does).
///
/// Default: **false**
pub detect_serde_json_arbitrary_precision: bool,
} }
impl Default for Options { impl Default for Options {
@@ -63,6 +63,7 @@ impl Options {
set_array_metatable: true, set_array_metatable: true,
serialize_none_to_null: true, serialize_none_to_null: true,
serialize_unit_to_null: true, serialize_unit_to_null: true,
detect_serde_json_arbitrary_precision: false,
} }
} }
@@ -92,6 +93,20 @@ impl Options {
self.serialize_unit_to_null = enabled; self.serialize_unit_to_null = enabled;
self self
} }
/// Sets [`detect_serde_json_arbitrary_precision`] option.
///
/// This option is used to serialize `serde_json::Number` with arbitrary precision to a Lua number.
/// Otherwise it will be serialized as an object (what serde does).
///
/// This option is disabled by default.
///
/// [`detect_serde_json_arbitrary_precision`]: #structfield.detect_serde_json_arbitrary_precision
#[must_use]
pub const fn detect_serde_json_arbitrary_precision(mut self, enabled: bool) -> Self {
self.detect_serde_json_arbitrary_precision = enabled;
self
}
} }
impl<'lua> Serializer<'lua> { impl<'lua> Serializer<'lua> {
@@ -110,7 +125,7 @@ macro_rules! lua_serialize_number {
($name:ident, $t:ty) => { ($name:ident, $t:ty) => {
#[inline] #[inline]
fn $name(self, value: $t) -> Result<Value<'lua>> { fn $name(self, value: $t) -> Result<Value<'lua>> {
value.to_lua(self.lua) value.into_lua(self.lua)
} }
}; };
} }
@@ -121,12 +136,12 @@ impl<'lua> ser::Serializer for Serializer<'lua> {
// Associated types for keeping track of additional state while serializing // Associated types for keeping track of additional state while serializing
// compound data structures like sequences and maps. // compound data structures like sequences and maps.
type SerializeSeq = SerializeVec<'lua>; type SerializeSeq = SerializeSeq<'lua>;
type SerializeTuple = SerializeVec<'lua>; type SerializeTuple = SerializeSeq<'lua>;
type SerializeTupleStruct = SerializeVec<'lua>; type SerializeTupleStruct = SerializeSeq<'lua>;
type SerializeTupleVariant = SerializeTupleVariant<'lua>; type SerializeTupleVariant = SerializeTupleVariant<'lua>;
type SerializeMap = SerializeMap<'lua>; type SerializeMap = SerializeMap<'lua>;
type SerializeStruct = SerializeMap<'lua>; type SerializeStruct = SerializeStruct<'lua>;
type SerializeStructVariant = SerializeStructVariant<'lua>; type SerializeStructVariant = SerializeStructVariant<'lua>;
#[inline] #[inline]
@@ -236,13 +251,11 @@ impl<'lua> ser::Serializer for Serializer<'lua> {
#[inline] #[inline]
fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq> { 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.unwrap_or(0), 0)?;
let table = self.lua.create_table_with_capacity(len, 0)?;
if self.options.set_array_metatable { if self.options.set_array_metatable {
table.set_metatable(Some(self.lua.array_metatable())); table.set_metatable(Some(self.lua.array_metatable()));
} }
let options = self.options; Ok(SerializeSeq::new(table, self.options))
Ok(SerializeVec { table, options })
} }
#[inline] #[inline]
@@ -253,9 +266,14 @@ impl<'lua> ser::Serializer for Serializer<'lua> {
#[inline] #[inline]
fn serialize_tuple_struct( fn serialize_tuple_struct(
self, self,
_name: &'static str, name: &'static str,
len: usize, len: usize,
) -> Result<Self::SerializeTupleStruct> { ) -> 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)) self.serialize_seq(Some(len))
} }
@@ -268,7 +286,7 @@ impl<'lua> ser::Serializer for Serializer<'lua> {
_len: usize, _len: usize,
) -> Result<Self::SerializeTupleVariant> { ) -> Result<Self::SerializeTupleVariant> {
Ok(SerializeTupleVariant { Ok(SerializeTupleVariant {
name: self.lua.create_string(variant)?, variant,
table: self.lua.create_table()?, table: self.lua.create_table()?,
options: self.options, options: self.options,
}) })
@@ -276,17 +294,31 @@ impl<'lua> ser::Serializer for Serializer<'lua> {
#[inline] #[inline]
fn serialize_map(self, len: Option<usize>) -> Result<Self::SerializeMap> { fn serialize_map(self, len: Option<usize>) -> Result<Self::SerializeMap> {
let len = len.unwrap_or(0) as c_int;
Ok(SerializeMap { Ok(SerializeMap {
key: None, 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, options: self.options,
}) })
} }
#[inline] #[inline]
fn serialize_struct(self, _name: &'static str, len: usize) -> Result<Self::SerializeStruct> { fn serialize_struct(self, name: &'static str, len: usize) -> Result<Self::SerializeStruct> {
self.serialize_map(Some(len)) if self.options.detect_serde_json_arbitrary_precision
&& name == "$serde_json::private::Number"
&& len == 1
{
return Ok(SerializeStruct {
lua: self.lua,
inner: None,
options: self.options,
});
}
Ok(SerializeStruct {
lua: self.lua,
inner: Some(Value::Table(self.lua.create_table_with_capacity(0, len)?)),
options: self.options,
})
} }
#[inline] #[inline]
@@ -298,20 +330,48 @@ impl<'lua> ser::Serializer for Serializer<'lua> {
len: usize, len: usize,
) -> Result<Self::SerializeStructVariant> { ) -> Result<Self::SerializeStructVariant> {
Ok(SerializeStructVariant { Ok(SerializeStructVariant {
name: self.lua.create_string(variant)?, 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, options: self.options,
}) })
} }
} }
#[doc(hidden)] #[doc(hidden)]
pub struct SerializeVec<'lua> { pub struct SerializeSeq<'lua> {
table: Table<'lua>, lua: &'lua Lua,
#[cfg(feature = "luau")]
vector: Option<crate::types::Vector>,
table: Option<Table<'lua>>,
next: usize,
options: Options, 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 Ok = Value<'lua>;
type Error = Error; type Error = Error;
@@ -319,34 +379,19 @@ impl<'lua> ser::SerializeSeq for SerializeVec<'lua> {
where where
T: Serialize + ?Sized, T: Serialize + ?Sized,
{ {
let lua = self.table.0.lua; let value = self.lua.to_value_with(value, self.options)?;
let value = lua.to_value_with(value, self.options)?; let table = self.table.as_ref().unwrap();
unsafe { table.raw_seti(self.next + 1, value)?;
let _sg = StackGuard::new(lua.state); self.next += 1;
check_stack(lua.state, 4)?; Ok(())
lua.push_ref(&self.table.0);
lua.push_value(value)?;
if lua.unlikely_memory_error() {
let len = ffi::lua_rawlen(lua.state, -2) as Integer;
ffi::lua_rawseti(lua.state, -2, len + 1);
ffi::lua_pop(lua.state, 1);
Ok(())
} else {
protect_lua!(lua.state, 2, 0, fn(state) {
let len = ffi::lua_rawlen(state, -2) as Integer;
ffi::lua_rawseti(state, -2, len + 1);
})
}
}
} }
fn end(self) -> Result<Value<'lua>> { 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 Ok = Value<'lua>;
type Error = Error; type Error = Error;
@@ -362,7 +407,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 Ok = Value<'lua>;
type Error = Error; type Error = Error;
@@ -370,17 +415,29 @@ impl<'lua> ser::SerializeTupleStruct for SerializeVec<'lua> {
where where
T: Serialize + ?Sized, 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) ser::SerializeSeq::serialize_element(self, value)
} }
fn end(self) -> Result<Value<'lua>> { fn end(self) -> Result<Value<'lua>> {
#[cfg(feature = "luau")]
if let Some(vector) = self.vector {
return Ok(Value::Vector(vector));
}
ser::SerializeSeq::end(self) ser::SerializeSeq::end(self)
} }
} }
#[doc(hidden)] #[doc(hidden)]
pub struct SerializeTupleVariant<'lua> { pub struct SerializeTupleVariant<'lua> {
name: String<'lua>, variant: &'static str,
table: Table<'lua>, table: Table<'lua>,
options: Options, options: Options,
} }
@@ -394,15 +451,13 @@ impl<'lua> ser::SerializeTupleVariant for SerializeTupleVariant<'lua> {
T: Serialize + ?Sized, T: Serialize + ?Sized,
{ {
let lua = self.table.0.lua; let lua = self.table.0.lua;
let idx = self.table.raw_len() + 1; self.table.raw_push(lua.to_value_with(value, self.options)?)
self.table
.raw_insert(idx, lua.to_value_with(value, self.options)?)
} }
fn end(self) -> Result<Value<'lua>> { fn end(self) -> Result<Value<'lua>> {
let lua = self.table.0.lua; let lua = self.table.0.lua;
let table = lua.create_table()?; let table = lua.create_table()?;
table.raw_set(self.name, self.table)?; table.raw_set(self.variant, self.table)?;
Ok(Value::Table(table)) Ok(Value::Table(table))
} }
} }
@@ -445,7 +500,14 @@ impl<'lua> ser::SerializeMap for SerializeMap<'lua> {
} }
} }
impl<'lua> ser::SerializeStruct for SerializeMap<'lua> { #[doc(hidden)]
pub struct SerializeStruct<'lua> {
lua: &'lua Lua,
inner: Option<Value<'lua>>,
options: Options,
}
impl<'lua> ser::SerializeStruct for SerializeStruct<'lua> {
type Ok = Value<'lua>; type Ok = Value<'lua>;
type Error = Error; type Error = Error;
@@ -453,18 +515,44 @@ impl<'lua> ser::SerializeStruct for SerializeMap<'lua> {
where where
T: Serialize + ?Sized, T: Serialize + ?Sized,
{ {
ser::SerializeMap::serialize_key(self, key)?; match self.inner {
ser::SerializeMap::serialize_value(self, value) Some(Value::Table(ref table)) => {
table.raw_set(key, self.lua.to_value_with(value, self.options)?)?;
}
None if self.options.detect_serde_json_arbitrary_precision => {
// A special case for `serde_json::Number` with arbitrary precision.
assert_eq!(key, "$serde_json::private::Number");
self.inner = Some(self.lua.to_value_with(value, self.options)?);
}
_ => unreachable!(),
}
Ok(())
} }
fn end(self) -> Result<Value<'lua>> { fn end(self) -> Result<Value<'lua>> {
ser::SerializeMap::end(self) match self.inner {
Some(table @ Value::Table(_)) => Ok(table),
Some(value) if self.options.detect_serde_json_arbitrary_precision => {
let number_s = value.as_str().expect("not an arbitrary precision number");
if number_s.contains(['.', 'e', 'E']) {
if let Ok(number) = number_s.parse().map(Value::Number) {
return Ok(number);
}
}
Ok(number_s
.parse()
.map(Value::Integer)
.or_else(|_| number_s.parse().map(Value::Number))
.unwrap_or(value))
}
_ => unreachable!(),
}
} }
} }
#[doc(hidden)] #[doc(hidden)]
pub struct SerializeStructVariant<'lua> { pub struct SerializeStructVariant<'lua> {
name: String<'lua>, variant: &'static str,
table: Table<'lua>, table: Table<'lua>,
options: Options, options: Options,
} }
@@ -485,8 +573,8 @@ impl<'lua> ser::SerializeStructVariant for SerializeStructVariant<'lua> {
fn end(self) -> Result<Value<'lua>> { fn end(self) -> Result<Value<'lua>> {
let lua = self.table.0.lua; let lua = self.table.0.lua;
let table = lua.create_table()?; let table = lua.create_table_with_capacity(0, 1)?;
table.raw_set(self.name, self.table)?; table.raw_set(self.variant, self.table)?;
Ok(Value::Table(table)) Ok(Value::Table(table))
} }
} }
+15 -3
View File
@@ -1,5 +1,4 @@
use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign}; use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign};
use std::u32;
/// Flags describing the set of lua standard libraries to load. /// Flags describing the set of lua standard libraries to load.
#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] #[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
@@ -16,32 +15,44 @@ impl StdLib {
feature = "luau" feature = "luau"
))] ))]
pub const COROUTINE: StdLib = StdLib(1); pub const COROUTINE: StdLib = StdLib(1);
/// [`table`](https://www.lua.org/manual/5.4/manual.html#6.6) library /// [`table`](https://www.lua.org/manual/5.4/manual.html#6.6) library
pub const TABLE: StdLib = StdLib(1 << 1); pub const TABLE: StdLib = StdLib(1 << 1);
/// [`io`](https://www.lua.org/manual/5.4/manual.html#6.8) library /// [`io`](https://www.lua.org/manual/5.4/manual.html#6.8) library
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))] #[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub const IO: StdLib = StdLib(1 << 2); pub const IO: StdLib = StdLib(1 << 2);
/// [`os`](https://www.lua.org/manual/5.4/manual.html#6.9) library /// [`os`](https://www.lua.org/manual/5.4/manual.html#6.9) library
pub const OS: StdLib = StdLib(1 << 3); pub const OS: StdLib = StdLib(1 << 3);
/// [`string`](https://www.lua.org/manual/5.4/manual.html#6.4) library /// [`string`](https://www.lua.org/manual/5.4/manual.html#6.4) library
pub const STRING: StdLib = StdLib(1 << 4); pub const STRING: StdLib = StdLib(1 << 4);
/// [`utf8`](https://www.lua.org/manual/5.4/manual.html#6.5) library /// [`utf8`](https://www.lua.org/manual/5.4/manual.html#6.5) library
/// ///
/// Requires `feature = "lua54/lua53/luau"` /// Requires `feature = "lua54/lua53/luau"`
#[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))] #[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))]
pub const UTF8: StdLib = StdLib(1 << 5); pub const UTF8: StdLib = StdLib(1 << 5);
/// [`bit`](https://www.lua.org/manual/5.2/manual.html#6.7) library /// [`bit`](https://www.lua.org/manual/5.2/manual.html#6.7) library
/// ///
/// Requires `feature = "lua52/luajit/luau"` /// Requires `feature = "lua52/luajit/luau"`
#[cfg(any(feature = "lua52", feature = "luajit", feature = "luau", doc))] #[cfg(any(feature = "lua52", feature = "luajit", feature = "luau", doc))]
pub const BIT: StdLib = StdLib(1 << 6); pub const BIT: StdLib = StdLib(1 << 6);
/// [`math`](https://www.lua.org/manual/5.4/manual.html#6.7) library /// [`math`](https://www.lua.org/manual/5.4/manual.html#6.7) library
pub const MATH: StdLib = StdLib(1 << 7); pub const MATH: StdLib = StdLib(1 << 7);
/// [`package`](https://www.lua.org/manual/5.4/manual.html#6.3) library /// [`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); 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 /// [`jit`](http://luajit.org/ext_jit.html) library
/// ///
/// Requires `feature = "luajit"` /// Requires `feature = "luajit"`
@@ -55,6 +66,7 @@ impl StdLib {
#[cfg(any(feature = "luajit", doc))] #[cfg(any(feature = "luajit", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luajit")))] #[cfg_attr(docsrs, doc(cfg(feature = "luajit")))]
pub const FFI: StdLib = StdLib(1 << 30); pub const FFI: StdLib = StdLib(1 << 30);
/// (**unsafe**) [`debug`](https://www.lua.org/manual/5.4/manual.html#6.10) library /// (**unsafe**) [`debug`](https://www.lua.org/manual/5.4/manual.html#6.10) library
pub const DEBUG: StdLib = StdLib(1 << 31); pub const DEBUG: StdLib = StdLib(1 << 31);
+107 -15
View File
@@ -2,7 +2,7 @@ use std::borrow::{Borrow, Cow};
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::os::raw::c_void; use std::os::raw::c_void;
use std::string::String as StdString; use std::string::String as StdString;
use std::{slice, str}; use std::{fmt, slice, str};
#[cfg(feature = "serialize")] #[cfg(feature = "serialize")]
use { use {
@@ -11,16 +11,35 @@ use {
}; };
use crate::error::{Error, Result}; use crate::error::{Error, Result};
use crate::ffi;
use crate::types::LuaRef; use crate::types::LuaRef;
use crate::util::{assert_stack, StackGuard};
/// Handle to an internal Lua string. /// Handle to an internal Lua string.
/// ///
/// Unlike Rust strings, Lua strings may not be valid UTF-8. /// Unlike Rust strings, Lua strings may not be valid UTF-8.
#[derive(Clone, Debug)] #[derive(Clone)]
pub struct String<'lua>(pub(crate) LuaRef<'lua>); pub struct String<'lua>(pub(crate) LuaRef<'lua>);
/// Owned handle to an internal Lua string.
///
/// 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)]
pub struct OwnedString(pub(crate) crate::types::LuaOwnedRef);
#[cfg(feature = "unstable")]
impl OwnedString {
/// Get borrowed handle to the underlying Lua string.
#[cfg_attr(feature = "send", allow(unused))]
pub const fn to_ref(&self) -> String {
String(self.0.to_ref())
}
}
impl<'lua> String<'lua> { impl<'lua> String<'lua> {
/// Get a `&str` slice if the Lua string is valid UTF-8. /// Get a `&str` slice if the Lua string is valid UTF-8.
/// ///
@@ -40,6 +59,7 @@ impl<'lua> String<'lua> {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[inline]
pub fn to_str(&self) -> Result<&str> { pub fn to_str(&self) -> Result<&str> {
str::from_utf8(self.as_bytes()).map_err(|e| Error::FromLuaConversionError { str::from_utf8(self.as_bytes()).map_err(|e| Error::FromLuaConversionError {
from: "string", from: "string",
@@ -66,6 +86,7 @@ impl<'lua> String<'lua> {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[inline]
pub fn to_string_lossy(&self) -> Cow<'_, str> { pub fn to_string_lossy(&self) -> Cow<'_, str> {
StdString::from_utf8_lossy(self.as_bytes()) StdString::from_utf8_lossy(self.as_bytes())
} }
@@ -87,6 +108,7 @@ impl<'lua> String<'lua> {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
#[inline]
pub fn as_bytes(&self) -> &[u8] { pub fn as_bytes(&self) -> &[u8] {
let nulled = self.as_bytes_with_nul(); let nulled = self.as_bytes_with_nul();
&nulled[..nulled.len() - 1] &nulled[..nulled.len() - 1]
@@ -94,35 +116,67 @@ impl<'lua> String<'lua> {
/// Get the bytes that make up this string, including the trailing nul byte. /// Get the bytes that make up this string, including the trailing nul byte.
pub fn as_bytes_with_nul(&self) -> &[u8] { pub fn as_bytes_with_nul(&self) -> &[u8] {
let lua = self.0.lua; let ref_thread = self.0.lua.ref_thread();
unsafe { unsafe {
let _sg = StackGuard::new(lua.state);
assert_stack(lua.state, 1);
lua.push_ref(&self.0);
mlua_debug_assert!( mlua_debug_assert!(
ffi::lua_type(lua.state, -1) == ffi::LUA_TSTRING, ffi::lua_type(ref_thread, self.0.index) == ffi::LUA_TSTRING,
"string ref is not string type" "string ref is not string type"
); );
let mut size = 0; let mut size = 0;
// This will not trigger a 'm' error, because the reference is guaranteed to be of // This will not trigger a 'm' error, because the reference is guaranteed to be of
// string type // string type
let data = ffi::lua_tolstring(lua.state, -1, &mut size); let data = ffi::lua_tolstring(ref_thread, self.0.index, &mut size);
slice::from_raw_parts(data as *const u8, size + 1) slice::from_raw_parts(data as *const u8, size + 1)
} }
} }
/// Converts the string to a generic C pointer. /// Converts this string to a generic C pointer.
/// ///
/// There is no way to convert the pointer back to its original value. /// There is no way to convert the pointer back to its original value.
/// ///
/// Typically this function is used only for hashing and debug information. /// Typically this function is used only for hashing and debug information.
#[inline] #[inline]
pub fn to_pointer(&self) -> *const c_void { pub fn to_pointer(&self) -> *const c_void {
let lua = self.0.lua; self.0.to_pointer()
unsafe { lua.ref_thread_exec(|refthr| ffi::lua_topointer(refthr, self.0.index)) } }
/// 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) -> OwnedString {
OwnedString(self.0.into_owned())
}
}
impl<'lua> fmt::Debug for String<'lua> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let bytes = self.as_bytes();
// Check if the string is valid utf8
if let Ok(s) = str::from_utf8(bytes) {
return s.fmt(f);
}
// Format as bytes
write!(f, "b\"")?;
for &b in bytes {
// https://doc.rust-lang.org/reference/tokens.html#byte-escapes
match b {
b'\n' => write!(f, "\\n")?,
b'\r' => write!(f, "\\r")?,
b'\t' => write!(f, "\\t")?,
b'\\' | b'"' => write!(f, "\\{}", b as char)?,
b'\0' => write!(f, "\\0")?,
// ASCII printable
0x20..=0x7e => write!(f, "{}", b as char)?,
_ => write!(f, "\\x{b:02x}")?,
}
}
write!(f, "\"")?;
Ok(())
} }
} }
@@ -148,7 +202,7 @@ impl<'lua> Borrow<[u8]> for String<'lua> {
// in other ways. // in other ways.
impl<'lua, T> PartialEq<T> for String<'lua> impl<'lua, T> PartialEq<T> for String<'lua>
where where
T: AsRef<[u8]>, T: AsRef<[u8]> + ?Sized,
{ {
fn eq(&self, other: &T) -> bool { fn eq(&self, other: &T) -> bool {
self.as_bytes() == other.as_ref() self.as_bytes() == other.as_ref()
@@ -175,3 +229,41 @@ impl<'lua> Serialize for String<'lua> {
} }
} }
} }
// Additional shortcuts
#[cfg(feature = "unstable")]
impl OwnedString {
/// Get a `&str` slice if the Lua string is valid UTF-8.
///
/// This is a shortcut for [`String::to_str()`].
#[inline]
pub fn to_str(&self) -> Result<&str> {
let s = self.to_ref();
// Reattach lifetime to &self
unsafe { std::mem::transmute(s.to_str()) }
}
/// Get the bytes that make up this string.
///
/// This is a shortcut for [`String::as_bytes()`].
#[inline]
pub fn as_bytes(&self) -> &[u8] {
let s = self.to_ref();
// Reattach lifetime to &self
unsafe { std::mem::transmute(s.as_bytes()) }
}
}
#[cfg(feature = "unstable")]
impl fmt::Debug for OwnedString {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.to_ref().fmt(f)
}
}
#[cfg(test)]
mod assertions {
use super::*;
static_assertions::assert_not_impl_any!(String: Send);
}
+677 -264
View File
File diff suppressed because it is too large Load Diff
+274 -157
View File
@@ -1,35 +1,32 @@
use std::cmp; use std::os::raw::{c_int, c_void};
use std::os::raw::c_int;
use crate::error::{Error, Result}; use crate::error::{Error, Result};
use crate::ffi; #[allow(unused)]
use crate::lua::Lua;
use crate::types::LuaRef; use crate::types::LuaRef;
use crate::util::{check_stack, error_traceback, pop_error, StackGuard}; use crate::util::{check_stack, error_traceback_thread, pop_error, StackGuard};
use crate::value::{FromLuaMulti, ToLuaMulti}; use crate::value::{FromLuaMulti, IntoLuaMulti};
#[cfg(any( #[cfg(not(feature = "luau"))]
feature = "lua54", use crate::{
all(feature = "luajit", feature = "vendored"), hook::{Debug, HookTriggers},
feature = "luau", types::MaybeSend,
))] };
use crate::function::Function;
#[cfg(feature = "async")] #[cfg(feature = "async")]
use { use {
crate::{ crate::value::MultiValue,
lua::{Lua, ASYNC_POLL_PENDING}, futures_util::stream::Stream,
value::{MultiValue, Value},
},
futures_core::{future::Future, stream::Stream},
std::{ std::{
cell::RefCell, future::Future,
marker::PhantomData, marker::PhantomData,
pin::Pin, pin::Pin,
ptr::NonNull,
task::{Context, Poll, Waker}, task::{Context, Poll, Waker},
}, },
}; };
/// Status of a Lua thread (or coroutine). /// Status of a Lua thread (coroutine).
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum ThreadStatus { pub enum ThreadStatus {
/// The thread was just created, or is suspended because it has called `coroutine.yield`. /// The thread was just created, or is suspended because it has called `coroutine.yield`.
@@ -44,27 +41,61 @@ pub enum ThreadStatus {
Error, Error,
} }
/// Handle to an internal Lua thread (or coroutine). /// Handle to an internal Lua thread (coroutine).
#[derive(Clone, Debug)] #[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`]. /// Thread (coroutine) representation as an async [`Future`] or [`Stream`].
/// ///
/// Requires `feature = "async"` /// Requires `feature = "async"`
/// ///
/// [`Future`]: futures_core::future::Future /// [`Future`]: std::future::Future
/// [`Stream`]: futures_core::stream::Stream /// [`Stream`]: futures_util::stream::Stream
#[cfg(feature = "async")] #[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))] #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
#[derive(Debug)] #[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct AsyncThread<'lua, R> { pub struct AsyncThread<'lua, R> {
thread: Thread<'lua>, thread: Thread<'lua>,
args0: RefCell<Option<Result<MultiValue<'lua>>>>, init_args: Option<Result<MultiValue<'lua>>>,
ret: PhantomData<R>, ret: PhantomData<R>,
recycle: bool, recycle: bool,
} }
impl<'lua> Thread<'lua> { 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. /// Resumes execution of this thread.
/// ///
/// Equivalent to `coroutine.resume`. /// Equivalent to `coroutine.resume`.
@@ -108,57 +139,68 @@ impl<'lua> Thread<'lua> {
/// ``` /// ```
pub fn resume<A, R>(&self, args: A) -> Result<R> pub fn resume<A, R>(&self, args: A) -> Result<R>
where where
A: ToLuaMulti<'lua>, A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua>, R: FromLuaMulti<'lua>,
{ {
if self.status() != ThreadStatus::Resumable {
return Err(Error::CoroutineInactive);
}
let lua = self.0.lua; let lua = self.0.lua;
let mut args = args.to_lua_multi(lua)?; let state = lua.state();
let nargs = args.len() as c_int; let thread_state = self.state();
let results = unsafe { unsafe {
let _sg = StackGuard::new(lua.state); let _sg = StackGuard::new(state);
check_stack(lua.state, cmp::max(nargs + 1, 3))?; let _thread_sg = StackGuard::with_top(thread_state, 0);
let thread_state = let nresults = self.resume_inner(args)?;
lua.ref_thread_exec(|ref_thread| ffi::lua_tothread(ref_thread, self.0.index)); check_stack(state, nresults + 1)?;
ffi::lua_xmove(thread_state, state, nresults);
let status = ffi::lua_status(thread_state); R::from_stack_multi(nresults, lua)
if status != ffi::LUA_YIELD && ffi::lua_gettop(thread_state) == 0 { }
return Err(Error::CoroutineInactive); }
}
/// 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();
let nargs = args.push_into_stack_multi(lua)?;
if nargs > 0 {
check_stack(thread_state, nargs)?; check_stack(thread_state, nargs)?;
for arg in args.drain_all() { ffi::lua_xmove(state, thread_state, nargs);
lua.push_value(arg)?; }
}
ffi::lua_xmove(lua.state, thread_state, nargs);
let mut nresults = 0; let mut nresults = 0;
let ret = ffi::lua_resume(thread_state, state, nargs, &mut nresults as *mut c_int);
let ret = ffi::lua_resume(thread_state, lua.state, nargs, &mut nresults as *mut c_int); if ret != ffi::LUA_OK && ret != ffi::LUA_YIELD {
if ret != ffi::LUA_OK && ret != ffi::LUA_YIELD { if ret == ffi::LUA_ERRMEM {
protect_lua!(lua.state, 0, 0, |_| error_traceback(thread_state))?; // Don't call error handler for memory errors
return Err(pop_error(thread_state, ret)); 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 Ok(nresults)
check_stack(lua.state, nresults + 2)?; // 2 is extra for `lua.pop_value()` below
ffi::lua_xmove(thread_state, lua.state, nresults);
for _ in 0..nresults {
results.push_front(lua.pop_value());
}
results
};
R::from_lua_multi(results, lua)
} }
/// Gets the status of the thread. /// Gets the status of the thread.
pub fn status(&self) -> ThreadStatus { pub fn status(&self) -> ThreadStatus {
let lua = self.0.lua; let thread_state = self.state();
if thread_state == self.0.lua.state() {
// The coroutine is currently running
return ThreadStatus::Unresumable;
}
unsafe { unsafe {
let thread_state =
lua.ref_thread_exec(|ref_thread| ffi::lua_tothread(ref_thread, self.0.index));
let status = ffi::lua_status(thread_state); let status = ffi::lua_status(thread_state);
if status != ffi::LUA_OK && status != ffi::LUA_YIELD { if status != ffi::LUA_OK && status != ffi::LUA_YIELD {
ThreadStatus::Error ThreadStatus::Error
@@ -170,53 +212,63 @@ impl<'lua> Thread<'lua> {
} }
} }
/// Sets a 'hook' function that will periodically be called as Lua code executes.
///
/// This function is similar or [`Lua::set_hook()`] except that it sets for the thread.
/// To remove a hook call [`Lua::remove_hook()`].
#[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub fn set_hook<F>(&self, triggers: HookTriggers, callback: F)
where
F: Fn(&Lua, Debug) -> Result<()> + MaybeSend + 'static,
{
let lua = self.0.lua;
unsafe {
lua.set_thread_hook(self.state(), triggers, callback);
}
}
/// Resets a thread /// Resets a thread
/// ///
/// In [Lua 5.4]: cleans its call stack and closes all pending to-be-closed variables. /// In [Lua 5.4]: cleans its call stack and closes all pending to-be-closed variables.
/// Returns a error in case of either the original error that stopped the thread or errors /// Returns a error in case of either the original error that stopped the thread or errors
/// in closing methods. /// 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. /// Lua threads in arbitrary states (like yielded or errored) can be reset properly.
/// ///
/// Sets a Lua function for the thread afterwards. /// 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 /// [Lua 5.4]: https://www.lua.org/manual/5.4/manual.html#lua_closethread
/// [LuaJIT]: https://github.com/openresty/luajit2#lua_resetthread #[cfg(any(feature = "lua54", feature = "luau"))]
#[cfg(any( #[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "luau"))))]
feature = "lua54", pub fn reset(&self, func: crate::function::Function<'lua>) -> Result<()> {
all(feature = "luajit", feature = "vendored"),
feature = "luau",
))]
pub fn reset(&self, func: Function<'lua>) -> Result<()> {
let lua = self.0.lua; let lua = self.0.lua;
let thread_state = self.state();
if thread_state == lua.state() {
return Err(Error::runtime("cannot reset a running thread"));
}
unsafe { unsafe {
let _sg = StackGuard::new(lua.state); #[cfg(all(feature = "lua54", not(feature = "vendored")))]
check_stack(lua.state, 2)?;
lua.push_ref(&self.0);
let thread_state = ffi::lua_tothread(lua.state, -1);
#[cfg(feature = "lua54")]
let status = ffi::lua_resetthread(thread_state); let status = ffi::lua_resetthread(thread_state);
#[cfg(all(feature = "lua54", feature = "vendored"))]
let status = ffi::lua_closethread(thread_state, lua.state());
#[cfg(feature = "lua54")] #[cfg(feature = "lua54")]
if status != ffi::LUA_OK { if status != ffi::LUA_OK {
return Err(pop_error(thread_state, status)); return Err(pop_error(thread_state, status));
} }
#[cfg(all(feature = "luajit", feature = "vendored"))]
ffi::lua_resetthread(lua.state, thread_state);
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
ffi::lua_resetthread(thread_state); ffi::lua_resetthread(thread_state);
lua.push_ref(&func.0); // Push function to the top of the thread stack
ffi::lua_xmove(lua.state, thread_state, 1); ffi::lua_xpush(lua.ref_thread(), thread_state, func.0.index);
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
{ {
// Inherit `LUA_GLOBALSINDEX` from the caller // Inherit `LUA_GLOBALSINDEX` from the main thread
ffi::lua_xpush(lua.state, thread_state, ffi::LUA_GLOBALSINDEX); ffi::lua_xpush(lua.main_state(), thread_state, ffi::LUA_GLOBALSINDEX);
ffi::lua_replace(thread_state, ffi::LUA_GLOBALSINDEX); ffi::lua_replace(thread_state, ffi::LUA_GLOBALSINDEX);
} }
@@ -236,8 +288,8 @@ impl<'lua> Thread<'lua> {
/// ///
/// Requires `feature = "async"` /// Requires `feature = "async"`
/// ///
/// [`Future`]: futures_core::future::Future /// [`Future`]: std::future::Future
/// [`Stream`]: futures_core::stream::Stream /// [`Stream`]: futures_util::stream::Stream
/// [`resume()`]: https://www.lua.org/manual/5.4/manual.html#lua_resume /// [`resume()`]: https://www.lua.org/manual/5.4/manual.html#lua_resume
/// ///
/// # Examples /// # Examples
@@ -273,13 +325,13 @@ impl<'lua> Thread<'lua> {
#[cfg_attr(docsrs, doc(cfg(feature = "async")))] #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn into_async<A, R>(self, args: A) -> AsyncThread<'lua, R> pub fn into_async<A, R>(self, args: A) -> AsyncThread<'lua, R>
where where
A: ToLuaMulti<'lua>, A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua>, R: FromLuaMulti<'lua>,
{ {
let args = args.to_lua_multi(self.0.lua); let args = args.into_lua_multi(self.0.lua);
AsyncThread { AsyncThread {
thread: self, thread: self,
args0: RefCell::new(Some(args)), init_args: Some(args),
ret: PhantomData, ret: PhantomData,
recycle: false, recycle: false,
} }
@@ -321,16 +373,32 @@ impl<'lua> Thread<'lua> {
#[doc(hidden)] #[doc(hidden)]
pub fn sandbox(&self) -> Result<()> { pub fn sandbox(&self) -> Result<()> {
let lua = self.0.lua; let lua = self.0.lua;
let state = lua.state();
let thread_state = self.state();
unsafe { unsafe {
let thread = lua.ref_thread_exec(|t| ffi::lua_tothread(t, self.0.index)); check_stack(thread_state, 3)?;
check_stack(thread, 1)?; check_stack(state, 3)?;
check_stack(lua.state, 3)?; protect_lua!(state, 0, 0, |_| ffi::luaL_sandboxthread(thread_state))
// Inherit `LUA_GLOBALSINDEX` from the caller
ffi::lua_xpush(lua.state, thread, ffi::LUA_GLOBALSINDEX);
ffi::lua_replace(thread, ffi::LUA_GLOBALSINDEX);
protect_lua!(lua.state, 0, 0, |_| ffi::luaL_sandboxthread(thread))
} }
} }
/// Converts this thread to a generic C pointer.
///
/// There is no way to convert the pointer back to its original value.
///
/// Typically this function is used only for hashing and debug information.
#[inline]
pub fn to_pointer(&self) -> *const c_void {
self.0.to_pointer()
}
/// Convert this handle to owned version.
#[cfg(all(feature = "unstable", any(not(feature = "send"), doc)))]
#[cfg_attr(docsrs, doc(cfg(all(feature = "unstable", not(feature = "send")))))]
#[inline]
pub fn into_owned(self) -> OwnedThread {
OwnedThread(self.0.into_owned(), self.1)
}
} }
impl<'lua> PartialEq for Thread<'lua> { impl<'lua> PartialEq for Thread<'lua> {
@@ -339,6 +407,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")] #[cfg(feature = "async")]
impl<'lua, R> AsyncThread<'lua, R> { impl<'lua, R> AsyncThread<'lua, R> {
#[inline] #[inline]
@@ -348,16 +436,22 @@ impl<'lua, R> AsyncThread<'lua, R> {
} }
#[cfg(feature = "async")] #[cfg(feature = "async")]
#[cfg(any( #[cfg(any(feature = "lua54", feature = "luau"))]
feature = "lua54",
all(feature = "luajit", feature = "vendored"),
feature = "luau",
))]
impl<'lua, R> Drop for AsyncThread<'lua, R> { impl<'lua, R> Drop for AsyncThread<'lua, R> {
fn drop(&mut self) { fn drop(&mut self) {
if self.recycle { if self.recycle {
unsafe { unsafe {
self.thread.0.lua.recycle_thread(&mut self.thread); let lua = self.thread.0.lua;
// For Lua 5.4 this also closes all pending to-be-closed variables
if !lua.recycle_thread(&mut self.thread) {
#[cfg(feature = "lua54")]
if self.thread.status() == ThreadStatus::Error {
#[cfg(not(feature = "vendored"))]
ffi::lua_resetthread(self.thread.state());
#[cfg(feature = "vendored")]
ffi::lua_closethread(self.thread.state(), lua.state());
}
}
} }
} }
} }
@@ -371,26 +465,36 @@ where
type Item = Result<R>; type Item = Result<R>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> { fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let lua = self.thread.0.lua; if self.thread.status() != ThreadStatus::Resumable {
return Poll::Ready(None);
match self.thread.status() {
ThreadStatus::Resumable => {}
_ => return Poll::Ready(None),
};
let _wg = WakerGuard::new(lua, cx.waker().clone());
let ret: MultiValue = if let Some(args) = self.args0.borrow_mut().take() {
self.thread.resume(args?)?
} else {
self.thread.resume(())?
};
if is_poll_pending(&ret) {
return Poll::Pending;
} }
cx.waker().wake_by_ref(); let lua = self.thread.0.lua;
Poll::Ready(Some(R::from_lua_multi(ret, 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)))
}
} }
} }
@@ -402,67 +506,80 @@ where
type Output = Result<R>; type Output = Result<R>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { 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 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() { // This is safe as we are not moving the whole struct
ThreadStatus::Resumable => {} let this = self.get_unchecked_mut();
_ => return Poll::Ready(Err(Error::CoroutineInactive)), 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().clone()); if nresults == 1 && is_poll_pending(thread_state) {
let ret: MultiValue = if let Some(args) = self.args0.borrow_mut().take() { return Poll::Pending;
self.thread.resume(args?)? }
} else {
self.thread.resume(())?
};
if is_poll_pending(&ret) { if ffi::lua_status(thread_state) == ffi::LUA_YIELD {
return Poll::Pending; // Ignore value returned via yield()
cx.waker().wake_by_ref();
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 = self.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")] #[cfg(feature = "async")]
#[inline(always)] #[inline(always)]
fn is_poll_pending(val: &MultiValue) -> bool { unsafe fn is_poll_pending(state: *mut ffi::lua_State) -> bool {
match val.iter().enumerate().last() { ffi::lua_tolightuserdata(state, -1) == Lua::poll_pending().0
Some((0, Value::LightUserData(ud))) => {
std::ptr::eq(ud.0 as *const u8, &ASYNC_POLL_PENDING as *const u8)
}
_ => false,
}
} }
#[cfg(feature = "async")] #[cfg(feature = "async")]
struct WakerGuard<'lua> { struct WakerGuard<'lua, 'a> {
lua: &'lua Lua, lua: &'lua Lua,
prev: Option<Waker>, prev: NonNull<Waker>,
_phantom: PhantomData<&'a ()>,
} }
#[cfg(feature = "async")] #[cfg(feature = "async")]
impl<'lua> WakerGuard<'lua> { impl<'lua, 'a> WakerGuard<'lua, 'a> {
#[inline] #[inline]
pub fn new(lua: &Lua, waker: Waker) -> Result<WakerGuard> { pub fn new(lua: &'lua Lua, waker: &'a Waker) -> Result<WakerGuard<'lua, 'a>> {
unsafe { let prev = unsafe { lua.set_waker(NonNull::from(waker)) };
let prev = lua.set_waker(Some(waker)); Ok(WakerGuard {
Ok(WakerGuard { lua, prev }) lua,
} prev,
_phantom: PhantomData,
})
} }
} }
#[cfg(feature = "async")] #[cfg(feature = "async")]
impl<'lua> Drop for WakerGuard<'lua> { impl<'lua, 'a> Drop for WakerGuard<'lua, 'a> {
fn drop(&mut self) { fn drop(&mut self) {
unsafe { unsafe { self.lua.set_waker(self.prev) };
self.lua.set_waker(self.prev.take());
}
} }
} }
#[cfg(test)]
mod assertions {
use super::*;
static_assertions::assert_not_impl_any!(Thread: Send);
}
+390 -39
View File
@@ -1,34 +1,49 @@
use std::cell::UnsafeCell; use std::any::{Any, TypeId};
use std::cell::{Cell, Ref, RefCell, RefMut, UnsafeCell};
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::ops::{Deref, DerefMut};
use std::os::raw::{c_int, c_void}; use std::os::raw::{c_int, c_void};
use std::result::Result as StdResult;
use std::sync::atomic::{AtomicI32, Ordering};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::{fmt, mem, ptr}; use std::{fmt, mem, ptr};
#[cfg(feature = "lua54")] use rustc_hash::FxHashMap;
use std::ffi::CStr;
#[cfg(feature = "async")]
use futures_core::future::LocalBoxFuture;
use crate::error::Result; use crate::error::Result;
use crate::ffi;
#[cfg(not(feature = "luau"))] #[cfg(not(feature = "luau"))]
use crate::hook::Debug; use crate::hook::Debug;
use crate::lua::{ExtraData, Lua}; 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. /// Type of Lua integer numbers.
pub type Integer = ffi::lua_Integer; pub type Integer = ffi::lua_Integer;
/// Type of Lua floating point numbers. /// Type of Lua floating point numbers.
pub type Number = ffi::lua_Number; 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. /// A "light" userdata value. Equivalent to an unmanaged raw pointer.
#[derive(Debug, Copy, Clone, Eq, PartialEq)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct LightUserData(pub *mut c_void); pub struct LightUserData(pub *mut c_void);
pub(crate) type Callback<'lua, 'a> = pub(crate) type Callback<'lua, 'a> = Box<dyn Fn(&'lua Lua, c_int) -> Result<c_int> + 'a>;
Box<dyn Fn(&'lua Lua, MultiValue<'lua>) -> Result<MultiValue<'lua>> + 'a>;
pub(crate) struct Upvalue<T> { pub(crate) struct Upvalue<T> {
pub(crate) data: T, pub(crate) data: T,
@@ -39,13 +54,13 @@ pub(crate) type CallbackUpvalue = Upvalue<Callback<'static, 'static>>;
#[cfg(feature = "async")] #[cfg(feature = "async")]
pub(crate) type AsyncCallback<'lua, 'a> = 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")] #[cfg(feature = "async")]
pub(crate) type AsyncCallbackUpvalue = Upvalue<AsyncCallback<'static, 'static>>; pub(crate) type AsyncCallbackUpvalue = Upvalue<AsyncCallback<'static, 'static>>;
#[cfg(feature = "async")] #[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. /// Type to set next Luau VM action after executing interrupt function.
#[cfg(any(feature = "luau", doc))] #[cfg(any(feature = "luau", doc))]
@@ -62,16 +77,16 @@ pub(crate) type HookCallback = Arc<dyn Fn(&Lua, Debug) -> Result<()> + Send>;
pub(crate) type HookCallback = Arc<dyn Fn(&Lua, Debug) -> Result<()>>; pub(crate) type HookCallback = Arc<dyn Fn(&Lua, Debug) -> Result<()>>;
#[cfg(all(feature = "luau", feature = "send"))] #[cfg(all(feature = "luau", feature = "send"))]
pub(crate) type InterruptCallback = Arc<dyn Fn() -> Result<VmState> + Send>; pub(crate) type InterruptCallback = Arc<dyn Fn(&Lua) -> Result<VmState> + Send>;
#[cfg(all(feature = "luau", not(feature = "send")))] #[cfg(all(feature = "luau", not(feature = "send")))]
pub(crate) type InterruptCallback = Arc<dyn Fn() -> Result<VmState>>; pub(crate) type InterruptCallback = Arc<dyn Fn(&Lua) -> Result<VmState>>;
#[cfg(all(feature = "send", feature = "lua54"))] #[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"))] #[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")] #[cfg(feature = "send")]
pub trait MaybeSend: Send {} pub trait MaybeSend: Send {}
@@ -83,6 +98,92 @@ pub trait MaybeSend {}
#[cfg(not(feature = "send"))] #[cfg(not(feature = "send"))]
impl<T> MaybeSend for T {} 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; pub(crate) struct DestructedUserdata;
/// An auto generated key into the Lua registry. /// An auto generated key into the Lua registry.
@@ -94,34 +195,34 @@ pub(crate) struct DestructedUserdata;
/// Be warned, If you place this into Lua via a [`UserData`] type or a rust callback, it is *very /// 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. /// 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 /// 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 /// [`UserData`]: crate::UserData
/// [`RegistryKey`]: crate::RegistryKey /// [`RegistryKey`]: crate::RegistryKey
/// [`Lua::remove_registry_value`]: crate::Lua::remove_registry_value /// [`Lua::remove_registry_value`]: crate::Lua::remove_registry_value
/// [`Lua::expire_registry_values`]: crate::Lua::expire_registry_values /// [`Lua::expire_registry_values`]: crate::Lua::expire_registry_values
/// [`AnyUserData::set_user_value`]: crate::AnyUserData::set_user_value /// [`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 struct RegistryKey {
pub(crate) registry_id: c_int, pub(crate) registry_id: AtomicI32,
pub(crate) unref_list: Arc<Mutex<Option<Vec<c_int>>>>, pub(crate) unref_list: Arc<Mutex<Option<Vec<c_int>>>>,
} }
impl fmt::Debug for RegistryKey { impl fmt::Debug for RegistryKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "RegistryKey({})", self.registry_id) write!(f, "RegistryKey({})", self.id())
} }
} }
impl Hash for RegistryKey { impl Hash for RegistryKey {
fn hash<H: Hasher>(&self, state: &mut H) { fn hash<H: Hasher>(&self, state: &mut H) {
self.registry_id.hash(state) self.id().hash(state)
} }
} }
impl PartialEq for RegistryKey { impl PartialEq for RegistryKey {
fn eq(&self, other: &RegistryKey) -> bool { fn eq(&self, other: &RegistryKey) -> bool {
self.registry_id == other.registry_id && Arc::ptr_eq(&self.unref_list, &other.unref_list) self.id() == other.id() && Arc::ptr_eq(&self.unref_list, &other.unref_list)
} }
} }
@@ -129,17 +230,41 @@ impl Eq for RegistryKey {}
impl Drop for RegistryKey { impl Drop for RegistryKey {
fn drop(&mut self) { fn drop(&mut self) {
let mut unref_list = mlua_expect!(self.unref_list.lock(), "unref list poisoned"); let registry_id = self.id();
if let Some(list) = unref_list.as_mut() { // We don't need to collect nil slot
list.push(self.registry_id); if registry_id > ffi::LUA_REFNIL {
let mut unref_list = mlua_expect!(self.unref_list.lock(), "unref list poisoned");
if let Some(list) = unref_list.as_mut() {
list.push(registry_id);
}
} }
} }
} }
impl RegistryKey { impl RegistryKey {
// Destroys the RegistryKey without adding to the drop list /// Creates a new instance of `RegistryKey`
pub(crate) fn take(self) -> c_int { pub(crate) const fn new(id: c_int, unref_list: Arc<Mutex<Option<Vec<c_int>>>>) -> Self {
let registry_id = self.registry_id; RegistryKey {
registry_id: AtomicI32::new(id),
unref_list,
}
}
/// Returns the underlying Lua reference of this `RegistryKey`
#[inline(always)]
pub fn id(&self) -> c_int {
self.registry_id.load(Ordering::Relaxed)
}
/// Sets the unique Lua reference key of this `RegistryKey`
#[inline(always)]
pub(crate) fn set_id(&self, id: c_int) {
self.registry_id.store(id, Ordering::Relaxed);
}
/// Destroys the `RegistryKey` without adding to the unref list
pub(crate) fn take(self) -> i32 {
let registry_id = self.id();
unsafe { unsafe {
ptr::read(&self.unref_list); ptr::read(&self.unref_list);
mem::forget(self); mem::forget(self);
@@ -151,11 +276,36 @@ impl RegistryKey {
pub(crate) struct LuaRef<'lua> { pub(crate) struct LuaRef<'lua> {
pub(crate) lua: &'lua Lua, pub(crate) lua: &'lua Lua,
pub(crate) index: c_int, pub(crate) index: c_int,
pub(crate) drop: bool,
}
impl<'lua> LuaRef<'lua> {
pub(crate) const fn new(lua: &'lua Lua, index: c_int) -> Self {
LuaRef {
lua,
index,
drop: true,
}
}
#[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 {
assert!(self.drop, "Cannot turn non-drop reference into owned");
let owned_ref = LuaOwnedRef::new(self.lua.clone(), self.index);
mem::forget(self);
owned_ref
}
} }
impl<'lua> fmt::Debug for LuaRef<'lua> { impl<'lua> fmt::Debug for LuaRef<'lua> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Ref({})", self.index) write!(f, "Ref({:p})", self.to_pointer())
} }
} }
@@ -167,21 +317,222 @@ impl<'lua> Clone for LuaRef<'lua> {
impl<'lua> Drop for LuaRef<'lua> { impl<'lua> Drop for LuaRef<'lua> {
fn drop(&mut self) { fn drop(&mut self) {
if self.index > 0 { if self.drop {
self.lua.drop_ref(self); self.lua.drop_ref_index(self.index);
} }
} }
} }
impl<'lua> PartialEq for LuaRef<'lua> { impl<'lua> PartialEq for LuaRef<'lua> {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
let lua = self.lua; let ref_thread = self.lua.ref_thread();
unsafe { assert!(
let _sg = StackGuard::new(lua.state); ref_thread == other.lua.ref_thread(),
assert_stack(lua.state, 2); "Lua instance passed Value created from a different main Lua state"
lua.push_ref(self); );
lua.push_ref(other); unsafe { ffi::lua_rawequal(ref_thread, self.index, other.index) == 1 }
ffi::lua_rawequal(lua.state, -1, -2) == 1 }
}
#[cfg(feature = "unstable")]
pub(crate) struct LuaOwnedRef {
pub(crate) inner: Arc<LuaInner>,
pub(crate) index: c_int,
_non_send: PhantomData<*const ()>,
}
#[cfg(feature = "unstable")]
impl fmt::Debug for LuaOwnedRef {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "OwnedRef({:p})", self.to_ref().to_pointer())
}
}
#[cfg(feature = "unstable")]
impl Clone for LuaOwnedRef {
fn clone(&self) -> Self {
self.to_ref().clone().into_owned()
}
}
#[cfg(feature = "unstable")]
impl Drop for LuaOwnedRef {
fn drop(&mut self) {
let lua: &Lua = unsafe { mem::transmute(&self.inner) };
lua.drop_ref_index(self.index);
}
}
#[cfg(feature = "unstable")]
impl LuaOwnedRef {
pub(crate) const fn new(inner: Arc<LuaInner>, index: c_int) -> Self {
LuaOwnedRef {
inner,
index,
_non_send: PhantomData,
}
}
pub(crate) const fn to_ref(&self) -> LuaRef {
LuaRef {
lua: unsafe { mem::transmute(&self.inner) },
index: self.index,
drop: false,
} }
} }
} }
#[derive(Debug, Default)]
pub(crate) struct AppData {
#[cfg(not(feature = "send"))]
container: UnsafeCell<FxHashMap<TypeId, RefCell<Box<dyn Any>>>>,
#[cfg(feature = "send")]
container: UnsafeCell<FxHashMap<TypeId, RefCell<Box<dyn Any + Send>>>>,
borrow: Cell<usize>,
}
impl AppData {
#[track_caller]
pub(crate) fn insert<T: MaybeSend + 'static>(&self, data: T) -> Option<T> {
match self.try_insert(data) {
Ok(data) => data,
Err(_) => panic!("cannot mutably borrow app data container"),
}
}
pub(crate) fn try_insert<T: MaybeSend + 'static>(&self, data: T) -> StdResult<Option<T>, T> {
if self.borrow.get() != 0 {
return Err(data);
}
// SAFETY: we checked that there are no other references to the container
Ok(unsafe { &mut *self.container.get() }
.insert(TypeId::of::<T>(), RefCell::new(Box::new(data)))
.and_then(|data| data.into_inner().downcast::<T>().ok().map(|data| *data)))
}
#[track_caller]
pub(crate) fn borrow<T: 'static>(&self) -> Option<AppDataRef<T>> {
let data = unsafe { &*self.container.get() }
.get(&TypeId::of::<T>())?
.borrow();
self.borrow.set(self.borrow.get() + 1);
Some(AppDataRef {
data: Ref::filter_map(data, |data| data.downcast_ref()).ok()?,
borrow: &self.borrow,
})
}
#[track_caller]
pub(crate) fn borrow_mut<T: 'static>(&self) -> Option<AppDataRefMut<T>> {
let data = unsafe { &*self.container.get() }
.get(&TypeId::of::<T>())?
.borrow_mut();
self.borrow.set(self.borrow.get() + 1);
Some(AppDataRefMut {
data: RefMut::filter_map(data, |data| data.downcast_mut()).ok()?,
borrow: &self.borrow,
})
}
#[track_caller]
pub(crate) fn remove<T: 'static>(&self) -> Option<T> {
if self.borrow.get() != 0 {
panic!("cannot mutably borrow app data container");
}
// SAFETY: we checked that there are no other references to the container
unsafe { &mut *self.container.get() }
.remove(&TypeId::of::<T>())?
.into_inner()
.downcast::<T>()
.ok()
.map(|data| *data)
}
}
/// A wrapper type for an immutably borrowed value from an app data container.
///
/// This type is similar to [`Ref`].
pub struct AppDataRef<'a, T: ?Sized + 'a> {
data: Ref<'a, T>,
borrow: &'a Cell<usize>,
}
impl<T: ?Sized> Drop for AppDataRef<'_, T> {
fn drop(&mut self) {
self.borrow.set(self.borrow.get() - 1);
}
}
impl<T: ?Sized> Deref for AppDataRef<'_, T> {
type Target = T;
#[inline]
fn deref(&self) -> &Self::Target {
&self.data
}
}
impl<T: ?Sized + fmt::Display> fmt::Display for AppDataRef<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
impl<T: ?Sized + fmt::Debug> fmt::Debug for AppDataRef<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
/// A wrapper type for a mutably borrowed value from an app data container.
///
/// This type is similar to [`RefMut`].
pub struct AppDataRefMut<'a, T: ?Sized + 'a> {
data: RefMut<'a, T>,
borrow: &'a Cell<usize>,
}
impl<T: ?Sized> Drop for AppDataRefMut<'_, T> {
fn drop(&mut self) {
self.borrow.set(self.borrow.get() - 1);
}
}
impl<T: ?Sized> Deref for AppDataRefMut<'_, T> {
type Target = T;
#[inline]
fn deref(&self) -> &Self::Target {
&self.data
}
}
impl<T: ?Sized> DerefMut for AppDataRefMut<'_, T> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.data
}
}
impl<T: ?Sized + fmt::Display> fmt::Display for AppDataRefMut<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
impl<T: ?Sized + fmt::Debug> fmt::Debug for AppDataRefMut<'_, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
#[cfg(test)]
mod assertions {
use super::*;
static_assertions::assert_impl_all!(RegistryKey: Send, Sync);
static_assertions::assert_not_impl_any!(LuaRef: Send);
#[cfg(feature = "unstable")]
static_assertions::assert_not_impl_any!(LuaOwnedRef: Send);
}
+568 -357
View File
File diff suppressed because it is too large Load Diff
+190
View File
@@ -0,0 +1,190 @@
use crate::error::{Error, Result};
use crate::private::Sealed;
use crate::userdata::{AnyUserData, MetaMethod};
use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Value};
#[cfg(feature = "async")]
use futures_util::future::{self, LocalBoxFuture};
/// An extension trait for [`AnyUserData`] that provides a variety of convenient functionality.
pub trait AnyUserDataExt<'lua>: Sealed {
/// Gets the value associated to `key` from the userdata, assuming it has `__index` metamethod.
fn get<K: IntoLua<'lua>, V: FromLua<'lua>>(&self, key: K) -> Result<V>;
/// Sets the value associated to `key` in the userdata, assuming it has `__newindex` metamethod.
fn set<K: IntoLua<'lua>, V: IntoLua<'lua>>(&self, key: K, value: V) -> Result<()>;
/// Calls the userdata as a function assuming it has `__call` metamethod.
///
/// The metamethod is called with the userdata as its first argument, followed by the passed arguments.
fn call<A, R>(&self, args: A) -> Result<R>
where
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua>;
/// Asynchronously calls the userdata as a function assuming it has `__call` metamethod.
///
/// 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<A, R>(&self, args: A) -> LocalBoxFuture<'lua, Result<R>>
where
A: IntoLuaMulti<'lua>,
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: &str, args: A) -> Result<R>
where
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua>;
/// Gets the function associated to `key` from the table and asynchronously executes it,
/// passing the table itself along with `args` as function arguments and returning Future.
///
/// Requires `feature = "async"`
///
/// This might invoke the `__index` metamethod.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn call_async_method<A, R>(&self, name: &str, args: A) -> LocalBoxFuture<'lua, Result<R>>
where
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'lua;
/// Gets the function associated to `key` from the table and executes it,
/// passing `args` as function arguments.
///
/// This is a shortcut for
/// `table.get::<_, Function>(key)?.call(args)`
///
/// This might invoke the `__index` metamethod.
fn call_function<A, R>(&self, name: &str, args: A) -> Result<R>
where
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua>;
/// Gets the function associated to `key` from the table and asynchronously executes it,
/// passing `args` as function arguments and returning Future.
///
/// Requires `feature = "async"`
///
/// This might invoke the `__index` metamethod.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn call_async_function<A, R>(&self, name: &str, args: A) -> LocalBoxFuture<'lua, Result<R>>
where
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'lua;
}
impl<'lua> AnyUserDataExt<'lua> for AnyUserData<'lua> {
fn get<K: IntoLua<'lua>, V: FromLua<'lua>>(&self, key: K) -> Result<V> {
let metatable = self.get_metatable()?;
match metatable.get::<Value>(MetaMethod::Index)? {
Value::Table(table) => table.raw_get(key),
Value::Function(func) => func.call((self.clone(), key)),
_ => Err(Error::runtime("attempt to index a userdata value")),
}
}
fn set<K: IntoLua<'lua>, V: IntoLua<'lua>>(&self, key: K, value: V) -> Result<()> {
let metatable = self.get_metatable()?;
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::runtime("attempt to index a userdata value")),
}
}
fn call<A, R>(&self, args: A) -> Result<R>
where
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua>,
{
let metatable = self.get_metatable()?;
match metatable.get::<Value>(MetaMethod::Call)? {
Value::Function(func) => func.call((self.clone(), args)),
_ => Err(Error::runtime("attempt to call a userdata value")),
}
}
#[cfg(feature = "async")]
fn call_async<A, R>(&self, args: A) -> LocalBoxFuture<'lua, Result<R>>
where
A: IntoLuaMulti<'lua>,
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)) => {
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: &str, args: A) -> Result<R>
where
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua>,
{
self.call_function(name, (self.clone(), args))
}
#[cfg(feature = "async")]
fn call_async_method<A, R>(&self, name: &str, args: A) -> LocalBoxFuture<'lua, Result<R>>
where
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'lua,
{
self.call_async_function(name, (self.clone(), args))
}
fn call_function<A, R>(&self, name: &str, args: A) -> Result<R>
where
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua>,
{
match self.get(name)? {
Value::Function(func) => func.call(args),
val => {
let msg = format!("attempt to call a {} value", val.type_name());
Err(Error::runtime(msg))
}
}
}
#[cfg(feature = "async")]
fn call_async_function<A, R>(&self, name: &str, args: A) -> LocalBoxFuture<'lua, Result<R>>
where
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'lua,
{
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)),
}
}
}
+661 -493
View File
File diff suppressed because it is too large Load Diff
+158 -109
View File
@@ -1,17 +1,20 @@
use std::any::{Any, TypeId}; use std::any::{Any, TypeId};
use std::borrow::Cow;
use std::ffi::CStr; use std::ffi::CStr;
use std::fmt::Write; use std::fmt::Write;
use std::mem::MaybeUninit; use std::mem::MaybeUninit;
use std::os::raw::{c_char, c_int, c_void}; use std::os::raw::{c_char, c_int, c_void};
use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe}; use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe};
use std::sync::Arc; use std::sync::Arc;
use std::{mem, ptr, slice}; use std::{ptr, slice, str};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use crate::error::{Error, Result}; use crate::error::{Error, Result};
use crate::ffi; use crate::memory::MemoryState;
pub(crate) use short_names::short_type_name;
static METATABLE_CACHE: Lazy<FxHashMap<TypeId, u8>> = Lazy::new(|| { static METATABLE_CACHE: Lazy<FxHashMap<TypeId, u8>> = Lazy::new(|| {
let mut map = FxHashMap::with_capacity_and_hasher(32, Default::default()); let mut map = FxHashMap::with_capacity_and_hasher(32, Default::default());
@@ -47,7 +50,6 @@ pub unsafe fn check_stack(state: *mut ffi::lua_State, amount: c_int) -> Result<(
pub struct StackGuard { pub struct StackGuard {
state: *mut ffi::lua_State, state: *mut ffi::lua_State,
top: c_int, top: c_int,
extra: c_int,
} }
impl StackGuard { impl StackGuard {
@@ -59,18 +61,12 @@ impl StackGuard {
StackGuard { StackGuard {
state, state,
top: ffi::lua_gettop(state), top: ffi::lua_gettop(state),
extra: 0,
} }
} }
// Similar to `new`, but checks and keeps `extra` elements from top of the stack on Drop. // Same as `new()`, but allows specifying the expected stack size at the end of the scope.
#[inline] pub const fn with_top(state: *mut ffi::lua_State, top: c_int) -> StackGuard {
pub unsafe fn new_extra(state: *mut ffi::lua_State, extra: c_int) -> StackGuard { StackGuard { state, top }
StackGuard {
state,
top: ffi::lua_gettop(state),
extra,
}
} }
} }
@@ -78,14 +74,11 @@ impl Drop for StackGuard {
fn drop(&mut self) { fn drop(&mut self) {
unsafe { unsafe {
let top = ffi::lua_gettop(self.state); let top = ffi::lua_gettop(self.state);
if top < self.top + self.extra { if top < self.top {
mlua_panic!("{} too many stack values popped", self.top - top) mlua_panic!("{} too many stack values popped", self.top - top)
} }
if top > self.top + self.extra { if top > self.top {
if self.extra > 0 { ffi::lua_settop(self.state, self.top);
ffi::lua_rotate(self.state, self.top + 1, self.extra);
}
ffi::lua_settop(self.state, self.top + self.extra);
} }
} }
} }
@@ -100,12 +93,14 @@ impl Drop for StackGuard {
pub unsafe fn protect_lua_call( pub unsafe fn protect_lua_call(
state: *mut ffi::lua_State, state: *mut ffi::lua_State,
nargs: c_int, 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<()> { ) -> Result<()> {
let stack_start = ffi::lua_gettop(state) - nargs; let stack_start = ffi::lua_gettop(state) - nargs;
ffi::lua_pushcfunction(state, error_traceback); MemoryState::relax_limit_with(state, || {
ffi::lua_pushcfunction(state, f); ffi::lua_pushcfunction(state, error_traceback);
ffi::lua_pushcfunction(state, f);
});
if nargs > 0 { if nargs > 0 {
ffi::lua_rotate(state, stack_start + 1, 2); ffi::lua_rotate(state, stack_start + 1, 2);
} }
@@ -143,7 +138,7 @@ where
nresults: c_int, 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 where
F: Fn(*mut ffi::lua_State) -> R, F: Fn(*mut ffi::lua_State) -> R,
R: Copy, R: Copy,
@@ -162,8 +157,10 @@ where
let stack_start = ffi::lua_gettop(state) - nargs; let stack_start = ffi::lua_gettop(state) - nargs;
ffi::lua_pushcfunction(state, error_traceback); MemoryState::relax_limit_with(state, || {
ffi::lua_pushcfunction(state, do_call::<F, R>); ffi::lua_pushcfunction(state, error_traceback);
ffi::lua_pushcfunction(state, do_call::<F, R>);
});
if nargs > 0 { if nargs > 0 {
ffi::lua_rotate(state, stack_start + 1, 2); ffi::lua_rotate(state, stack_start + 1, 2);
} }
@@ -242,15 +239,11 @@ pub unsafe fn pop_error(state: *mut ffi::lua_State, err_code: c_int) -> Error {
} }
} }
// Uses 3 stack spaces, does not call checkstack. // Uses 3 (or 1 if unprotected) stack spaces, does not call checkstack.
#[inline] #[inline(always)]
pub unsafe fn push_string<S: AsRef<[u8]> + ?Sized>( pub unsafe fn push_string(state: *mut ffi::lua_State, s: &[u8], protect: bool) -> Result<()> {
state: *mut ffi::lua_State, // Always use protected mode if the string is too long
s: &S, if protect || s.len() > (1 << 30) {
protect: bool,
) -> Result<()> {
let s = s.as_ref();
if protect {
protect_lua!(state, 0, 1, |state| { protect_lua!(state, 0, 1, |state| {
ffi::lua_pushlstring(state, s.as_ptr() as *const c_char, s.len()); ffi::lua_pushlstring(state, s.as_ptr() as *const c_char, s.len());
}) })
@@ -260,14 +253,30 @@ pub unsafe fn push_string<S: AsRef<[u8]> + ?Sized>(
} }
} }
// Uses 3 stack spaces (when protect), does not call checkstack.
#[cfg(feature = "luau")]
#[inline(always)]
pub unsafe fn push_buffer(state: *mut ffi::lua_State, b: &[u8], protect: bool) -> Result<()> {
let data = if protect {
protect_lua!(state, 0, 1, |state| ffi::lua_newbuffer(state, b.len()))?
} else {
ffi::lua_newbuffer(state, b.len())
};
let buf = slice::from_raw_parts_mut(data as *mut u8, b.len());
buf.copy_from_slice(b);
Ok(())
}
// Uses 3 stack spaces, does not call checkstack. // Uses 3 stack spaces, does not call checkstack.
#[inline] #[inline]
pub unsafe fn push_table( pub unsafe fn push_table(
state: *mut ffi::lua_State, state: *mut ffi::lua_State,
narr: c_int, narr: usize,
nrec: c_int, nrec: usize,
protect: bool, protect: bool,
) -> Result<()> { ) -> 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 { if protect {
protect_lua!(state, 0, 1, |state| ffi::lua_createtable(state, narr, nrec)) protect_lua!(state, 0, 1, |state| ffi::lua_createtable(state, narr, nrec))
} else { } else {
@@ -277,11 +286,7 @@ pub unsafe fn push_table(
} }
// Uses 4 stack spaces, does not call checkstack. // Uses 4 stack spaces, does not call checkstack.
pub unsafe fn rawset_field<S>(state: *mut ffi::lua_State, table: c_int, field: &S) -> Result<()> pub unsafe fn rawset_field(state: *mut ffi::lua_State, table: c_int, field: &str) -> Result<()> {
where
S: AsRef<[u8]> + ?Sized,
{
let field = field.as_ref();
ffi::lua_pushvalue(state, table); ffi::lua_pushvalue(state, table);
protect_lua!(state, 2, 0, |state| { protect_lua!(state, 2, 0, |state| {
ffi::lua_pushlstring(state, field.as_ptr() as *const c_char, field.len()); ffi::lua_pushlstring(state, field.as_ptr() as *const c_char, field.len());
@@ -291,38 +296,23 @@ where
} }
// Internally uses 3 stack spaces, does not call checkstack. // Internally uses 3 stack spaces, does not call checkstack.
#[cfg(not(feature = "luau"))]
#[inline] #[inline]
pub unsafe fn push_userdata<T>(state: *mut ffi::lua_State, t: T, protect: bool) -> Result<()> { pub unsafe fn push_userdata<T>(state: *mut ffi::lua_State, t: T, protect: bool) -> Result<()> {
#[cfg(not(feature = "luau"))]
let ud = if protect { let ud = if protect {
protect_lua!(state, 0, 1, |state| { 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 { } 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); #[cfg(feature = "luau")]
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>() + 1;
let ud = if protect { let ud = if protect {
protect_lua!(state, 0, 1, |state| { protect_lua!(state, 0, 1, |state| { ffi::lua_newuserdata_t::<T>(state) })?
ffi::lua_newuserdatadtor(state, size, destructor::<T>) as *mut T
})?
} else { } else {
ffi::lua_newuserdatadtor(state, size, destructor::<T>) as *mut T ffi::lua_newuserdata_t::<T>(state)
}; };
ptr::write(ud, t); ptr::write(ud, t);
Ok(()) Ok(())
} }
@@ -337,10 +327,10 @@ pub unsafe fn push_userdata_uv<T>(
) -> Result<()> { ) -> Result<()> {
let ud = if protect { let ud = if protect {
protect_lua!(state, 0, 1, |state| { 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 { } 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); ptr::write(ud, t);
Ok(()) Ok(())
@@ -414,14 +404,17 @@ pub unsafe fn get_gc_userdata<T: Any>(
ud 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); 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 {
let t = ffi::lua_type(state, -1); ffi::lua_pushboolean(state, ffi::lua_isfunction(state, -1));
ffi::lua_pop(state, 1); 1
ffi::lua_pushboolean(state, (t == ffi::LUA_TFUNCTION) as 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 1
} }
@@ -432,11 +425,22 @@ unsafe fn init_userdata_metatable_index(state: *mut ffi::lua_State) -> Result<()
} }
ffi::lua_pop(state, 1); ffi::lua_pop(state, 1);
// Create and cache `__index` helper // Create and cache `__index` generator
let code = cstr!( let code = cstr!(
r#" r#"
local error, isfunction = ... local error, isfunction, istable = ...
return function (__index, field_getters, methods) return function (__index, field_getters, methods)
-- Common case: has field getters and index is a table
if field_getters ~= nil and methods == nil and istable(__index) then
return function (self, key)
local field_getter = field_getters[key]
if field_getter ~= nil then
return field_getter(self)
end
return __index[key]
end
end
return function (self, key) return function (self, key)
if field_getters ~= nil then if field_getters ~= nil then
local field_getter = field_getters[key] local field_getter = field_getters[key]
@@ -471,7 +475,13 @@ unsafe fn init_userdata_metatable_index(state: *mut ffi::lua_State) -> Result<()
} }
ffi::lua_pushcfunction(state, lua_error_impl); ffi::lua_pushcfunction(state, lua_error_impl);
ffi::lua_pushcfunction(state, lua_isfunction_impl); ffi::lua_pushcfunction(state, lua_isfunction_impl);
ffi::lua_call(state, 2, 1); ffi::lua_pushcfunction(state, lua_istable_impl);
ffi::lua_call(state, 3, 1);
#[cfg(feature = "luau-jit")]
if ffi::luau_codegen_supported() != 0 {
ffi::luau_codegen_compile(state, -1);
}
// Store in the registry // Store in the registry
ffi::lua_pushvalue(state, -1); ffi::lua_pushvalue(state, -1);
@@ -486,7 +496,7 @@ pub unsafe fn init_userdata_metatable_newindex(state: *mut ffi::lua_State) -> Re
} }
ffi::lua_pop(state, 1); ffi::lua_pop(state, 1);
// Create and cache `__newindex` helper // Create and cache `__newindex` generator
let code = cstr!( let code = cstr!(
r#" r#"
local error, isfunction = ... local error, isfunction = ...
@@ -521,6 +531,11 @@ pub unsafe fn init_userdata_metatable_newindex(state: *mut ffi::lua_State) -> Re
ffi::lua_pushcfunction(state, lua_isfunction_impl); ffi::lua_pushcfunction(state, lua_isfunction_impl);
ffi::lua_call(state, 2, 1); ffi::lua_call(state, 2, 1);
#[cfg(feature = "luau-jit")]
if ffi::luau_codegen_supported() != 0 {
ffi::luau_codegen_compile(state, -1);
}
// Store in the registry // Store in the registry
ffi::lua_pushvalue(state, -1); ffi::lua_pushvalue(state, -1);
ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, newindex_key); ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, newindex_key);
@@ -535,12 +550,13 @@ pub unsafe fn init_userdata_metatable_newindex(state: *mut ffi::lua_State) -> Re
// captured `__index` if no matches found. // captured `__index` if no matches found.
// The same is also applicable for `__newindex` metamethod and `field_setters` table. // The same is also applicable for `__newindex` metamethod and `field_setters` table.
// Internally uses 9 stack spaces and does not call checkstack. // 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, state: *mut ffi::lua_State,
metatable: c_int, metatable: c_int,
field_getters: Option<c_int>, field_getters: Option<c_int>,
field_setters: Option<c_int>, field_setters: Option<c_int>,
methods: Option<c_int>, methods: Option<c_int>,
extra_init: Option<fn(*mut ffi::lua_State) -> Result<()>>,
) -> Result<()> { ) -> Result<()> {
ffi::lua_pushvalue(state, metatable); ffi::lua_pushvalue(state, metatable);
@@ -548,7 +564,7 @@ pub unsafe fn init_userdata_metatable<T>(
// Push `__index` generator function // Push `__index` generator function
init_userdata_metatable_index(state)?; init_userdata_metatable_index(state)?;
push_string(state, "__index", true)?; push_string(state, b"__index", true)?;
let index_type = ffi::lua_rawget(state, -3); let index_type = ffi::lua_rawget(state, -3);
match index_type { match index_type {
ffi::LUA_TNIL | ffi::LUA_TTABLE | ffi::LUA_TFUNCTION => { ffi::LUA_TNIL | ffi::LUA_TTABLE | ffi::LUA_TFUNCTION => {
@@ -573,7 +589,7 @@ pub unsafe fn init_userdata_metatable<T>(
// Push `__newindex` generator function // Push `__newindex` generator function
init_userdata_metatable_newindex(state)?; init_userdata_metatable_newindex(state)?;
push_string(state, "__newindex", true)?; push_string(state, b"__newindex", true)?;
let newindex_type = ffi::lua_rawget(state, -3); let newindex_type = ffi::lua_rawget(state, -3);
match newindex_type { match newindex_type {
ffi::LUA_TNIL | ffi::LUA_TTABLE | ffi::LUA_TFUNCTION => { ffi::LUA_TNIL | ffi::LUA_TTABLE | ffi::LUA_TFUNCTION => {
@@ -587,10 +603,9 @@ pub unsafe fn init_userdata_metatable<T>(
rawset_field(state, -2, "__newindex")?; rawset_field(state, -2, "__newindex")?;
} }
#[cfg(not(feature = "luau"))] // Additional initialization
{ if let Some(extra_init) = extra_init {
ffi::lua_pushcfunction(state, userdata_destructor::<T>); extra_init(state)?;
rawset_field(state, -2, "__gc")?;
} }
ffi::lua_pushboolean(state, 0); ffi::lua_pushboolean(state, 0);
@@ -602,7 +617,7 @@ pub unsafe fn init_userdata_metatable<T>(
} }
#[cfg(not(feature = "luau"))] #[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 // 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 // Lua 5.4 ignores it, other versions generates `LUA_ERRGCMM` without calling message handler
take_userdata::<T>(state); take_userdata::<T>(state);
@@ -647,8 +662,6 @@ where
Ok(Err(err)) => { Ok(Err(err)) => {
ffi::lua_settop(state, 1); ffi::lua_settop(state, 1);
let wrapped_error = ud as *mut WrappedFailure;
// Build `CallbackError` with traceback // Build `CallbackError` with traceback
let traceback = if ffi::lua_checkstack(state, ffi::LUA_TRACEBACK_STACK) != 0 { let traceback = if ffi::lua_checkstack(state, ffi::LUA_TRACEBACK_STACK) != 0 {
ffi::luaL_traceback(state, state, ptr::null(), 0); ffi::luaL_traceback(state, state, ptr::null(), 0);
@@ -659,10 +672,8 @@ where
"<not enough stack space for traceback>".to_string() "<not enough stack space for traceback>".to_string()
}; };
let cause = Arc::new(err); let cause = Arc::new(err);
ptr::write( let wrapped_error = WrappedFailure::Error(Error::CallbackError { traceback, cause });
wrapped_error, ptr::write(ud, wrapped_error);
WrappedFailure::Error(Error::CallbackError { traceback, cause }),
);
get_gc_metatable::<WrappedFailure>(state); get_gc_metatable::<WrappedFailure>(state);
ffi::lua_setmetatable(state, -2); ffi::lua_setmetatable(state, -2);
@@ -670,7 +681,7 @@ where
} }
Err(p) => { Err(p) => {
ffi::lua_settop(state, 1); 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); get_gc_metatable::<WrappedFailure>(state);
ffi::lua_setmetatable(state, -2); ffi::lua_setmetatable(state, -2);
ffi::lua_error(state) ffi::lua_error(state)
@@ -678,7 +689,14 @@ 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")]
if MemoryState::limit_reached(state) {
return 0;
}
if ffi::lua_checkstack(state, 2) == 0 { if ffi::lua_checkstack(state, 2) == 0 {
// If we don't have enough stack space to even check the error type, do // If we don't have enough stack space to even check the error type, do
// nothing so we don't risk shadowing a rust panic. // nothing so we don't risk shadowing a rust panic.
@@ -696,8 +714,22 @@ pub unsafe extern "C" fn error_traceback(state: *mut ffi::lua_State) -> c_int {
1 1
} }
// A variant of `error_traceback` that can safely inspect another (yielded) thread stack
pub unsafe fn error_traceback_thread(state: *mut ffi::lua_State, thread: *mut ffi::lua_State) {
// Move error object to the main thread to safely call `__tostring` metamethod if present
ffi::lua_xmove(thread, state, 1);
if get_gc_userdata::<WrappedFailure>(state, -1, ptr::null()).is_null() {
let s = ffi::luaL_tolstring(state, -1, ptr::null_mut());
if ffi::lua_checkstack(state, ffi::LUA_TRACEBACK_STACK) != 0 {
ffi::luaL_traceback(state, thread, s, 0);
ffi::lua_remove(state, -2);
}
}
}
// A variant of `pcall` that does not allow Lua to catch Rust panics from `callback_error`. // 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()); ffi::luaL_checkstack(state, 2, ptr::null());
let top = ffi::lua_gettop(state); let top = ffi::lua_gettop(state);
@@ -723,8 +755,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`. // 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 { pub unsafe extern "C-unwind" fn safe_xpcall(state: *mut ffi::lua_State) -> c_int {
unsafe extern "C" fn xpcall_msgh(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()); ffi::luaL_checkstack(state, 2, ptr::null());
if let Some(WrappedFailure::Panic(_)) = if let Some(WrappedFailure::Panic(_)) =
@@ -838,7 +870,7 @@ pub unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<()> {
// Create error and panic metatables // 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, |_| { callback_error(state, |_| {
check_stack(state, 3)?; check_stack(state, 3)?;
@@ -853,7 +885,7 @@ pub unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<()> {
// Depending on how the API is used and what error types scripts are given, it may // Depending on how the API is used and what error types scripts are given, it may
// be possible to make this consume arbitrary amounts of memory (for example, some // be possible to make this consume arbitrary amounts of memory (for example, some
// kind of recursive error structure?) // kind of recursive error structure?)
let _ = write!(&mut (*err_buf), "{}", error); let _ = write!(&mut (*err_buf), "{error}");
Ok(err_buf) Ok(err_buf)
} }
Some(WrappedFailure::Panic(Some(ref panic))) => { Some(WrappedFailure::Panic(Some(ref panic))) => {
@@ -864,9 +896,9 @@ pub unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<()> {
ffi::lua_pop(state, 2); ffi::lua_pop(state, 2);
if let Some(msg) = panic.downcast_ref::<&str>() { if let Some(msg) = panic.downcast_ref::<&str>() {
let _ = write!(&mut (*err_buf), "{}", msg); let _ = write!(&mut (*err_buf), "{msg}");
} else if let Some(msg) = panic.downcast_ref::<String>() { } else if let Some(msg) = panic.downcast_ref::<String>() {
let _ = write!(&mut (*err_buf), "{}", msg); let _ = write!(&mut (*err_buf), "{msg}");
} else { } else {
let _ = write!(&mut (*err_buf), "<panic>"); let _ = write!(&mut (*err_buf), "<panic>");
}; };
@@ -879,7 +911,7 @@ pub unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<()> {
} }
}?; }?;
push_string(state, &*err_buf, true)?; push_string(state, (*err_buf).as_bytes(), true)?;
(*err_buf).clear(); (*err_buf).clear();
Ok(1) Ok(1)
@@ -896,7 +928,7 @@ pub unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<()> {
// Create destructed userdata metatable // 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)) callback_error(state, |_| Err(Error::CallbackDestructed))
} }
@@ -910,7 +942,7 @@ pub unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<()> {
"__mod", "__mod",
"__pow", "__pow",
"__unm", "__unm",
#[cfg(any(feature = "lua54", feature = "lua53"))] #[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))]
"__idiv", "__idiv",
#[cfg(any(feature = "lua54", feature = "lua53"))] #[cfg(any(feature = "lua54", feature = "lua53"))]
"__band", "__band",
@@ -976,16 +1008,10 @@ pub(crate) enum WrappedFailure {
impl WrappedFailure { impl WrappedFailure {
pub(crate) unsafe fn new_userdata(state: *mut ffi::lua_State) -> *mut Self { pub(crate) unsafe fn new_userdata(state: *mut ffi::lua_State) -> *mut Self {
let size = mem::size_of::<WrappedFailure>();
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
let ud = { let ud = ffi::lua_newuserdata_t::<Self>(state);
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
};
#[cfg(not(feature = "luau"))] #[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); ptr::write(ud, WrappedFailure::None);
ud ud
} }
@@ -1015,7 +1041,10 @@ pub(crate) unsafe fn to_string(state: *mut ffi::lua_State, index: c_int) -> Stri
let v = ffi::lua_tovector(state, index); let v = ffi::lua_tovector(state, index);
mlua_debug_assert!(!v.is_null(), "vector is null"); mlua_debug_assert!(!v.is_null(), "vector is null");
let (x, y, z) = (*v, *v.add(1), *v.add(2)); 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 => { ffi::LUA_TSTRING => {
let mut size = 0; let mut size = 0;
@@ -1028,6 +1057,10 @@ 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_TFUNCTION => format!("<function {:?}>", ffi::lua_topointer(state, index)),
ffi::LUA_TUSERDATA => format!("<userdata {:?}>", 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)), ffi::LUA_TTHREAD => format!("<thread {:?}>", ffi::lua_topointer(state, index)),
#[cfg(feature = "luau")]
ffi::LUA_TBUFFER => format!("<buffer {:?}>", ffi::lua_topointer(state, index)),
#[cfg(feature = "luajit")]
ffi::LUA_TCDATA => format!("<cdata {:?}>", ffi::lua_topointer(state, index)),
_ => "<unknown>".to_string(), _ => "<unknown>".to_string(),
} }
} }
@@ -1037,14 +1070,30 @@ pub(crate) unsafe fn get_destructed_userdata_metatable(state: *mut ffi::lua_Stat
ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, key); 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() { if input.is_null() {
return None; 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; static DESTRUCTED_USERDATA_METATABLE: u8 = 0;
static ERROR_PRINT_BUFFER_KEY: u8 = 0; static ERROR_PRINT_BUFFER_KEY: u8 = 0;
static USERDATA_METATABLE_INDEX: u8 = 0; static USERDATA_METATABLE_INDEX: u8 = 0;
static USERDATA_METATABLE_NEWINDEX: u8 = 0; static USERDATA_METATABLE_NEWINDEX: u8 = 0;
mod short_names;
+85
View File
@@ -0,0 +1,85 @@
//! 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;
/// Returns a short version of a type name `T` without all module paths.
///
/// The short name of a type is its full name as returned by
/// [`std::any::type_name`], but with the prefix of all paths removed. For
/// example, the short name of `alloc::vec::Vec<core::option::Option<u32>>`
/// would be `Vec<Option<u32>>`.
pub(crate) fn short_type_name<T: ?Sized>() -> String {
let full_name = type_name::<T>();
// Generics result in nested paths within <..> blocks.
// Consider "core::option::Option<alloc::string::String>".
// To tackle this, we parse the string from left to right, collapsing as we go.
let mut index: usize = 0;
let end_of_string = full_name.len();
let mut parsed_name = String::new();
while index < end_of_string {
let rest_of_string = full_name.get(index..end_of_string).unwrap_or_default();
// Collapse everything up to the next special character,
// then skip over it
if let Some(special_character_index) = rest_of_string
.find(|c: char| [' ', '<', '>', '(', ')', '[', ']', ',', ';'].contains(&c))
{
let segment_to_collapse = rest_of_string
.get(0..special_character_index)
.unwrap_or_default();
parsed_name += collapse_type_name(segment_to_collapse);
// Insert the special character
let special_character =
&rest_of_string[special_character_index..=special_character_index];
parsed_name.push_str(special_character);
match special_character {
">" | ")" | "]"
if rest_of_string[special_character_index + 1..].starts_with("::") =>
{
parsed_name.push_str("::");
// Move the index past the "::"
index += special_character_index + 3;
}
// Move the index just past the special character
_ => index += special_character_index + 1,
}
} else {
// If there are no special characters left, we're done!
parsed_name += collapse_type_name(rest_of_string);
index = end_of_string;
}
}
parsed_name
}
#[inline(always)]
fn collapse_type_name(string: &str) -> &str {
string.rsplit("::").next().unwrap()
}
#[cfg(test)]
mod tests {
use super::short_type_name;
use std::collections::HashMap;
#[test]
fn tests() {
assert_eq!(short_type_name::<String>(), "String");
assert_eq!(short_type_name::<Option<String>>(), "Option<String>");
assert_eq!(short_type_name::<(String, &str)>(), "(String, &str)");
assert_eq!(short_type_name::<[i32; 3]>(), "[i32; 3]");
assert_eq!(
short_type_name::<HashMap<String, Option<[i32; 3]>>>(),
"HashMap<String, Option<[i32; 3]>>"
);
assert_eq!(
short_type_name::<dyn Fn(i32) -> i32>(),
"dyn Fn(i32) -> i32"
);
}
}
+724 -73
View File
@@ -1,29 +1,37 @@
use std::iter::{self, FromIterator}; use std::borrow::Cow;
use std::cmp::Ordering;
use std::collections::HashSet;
use std::iter;
use std::ops::Index; use std::ops::Index;
use std::os::raw::c_void; use std::os::raw::{c_int, c_void};
use std::{ptr, slice, str, vec}; use std::string::String as StdString;
use std::sync::Arc;
use std::{fmt, mem, ptr, slice, str, vec};
use num_traits::FromPrimitive;
#[cfg(feature = "serialize")] #[cfg(feature = "serialize")]
use { use {
crate::table::SerializableTable,
rustc_hash::FxHashSet,
serde::ser::{self, Serialize, Serializer}, serde::ser::{self, Serialize, Serializer},
std::convert::TryInto, std::{cell::RefCell, rc::Rc, result::Result as StdResult},
std::result::Result as StdResult,
}; };
use crate::error::{Error, Result}; use crate::error::{Error, Result};
use crate::ffi;
use crate::function::Function; use crate::function::Function;
use crate::lua::Lua; use crate::lua::Lua;
use crate::string::String; use crate::string::String;
use crate::table::Table; use crate::table::Table;
use crate::thread::Thread; use crate::thread::Thread;
use crate::types::{Integer, LightUserData, Number}; use crate::types::{Integer, LightUserData, Number, SubtypeId};
use crate::userdata::AnyUserData; use crate::userdata::AnyUserData;
use crate::util::{check_stack, StackGuard};
/// A dynamically typed Lua value. The `String`, `Table`, `Function`, `Thread`, and `UserData` /// A dynamically typed Lua value. The `String`, `Table`, `Function`, `Thread`, and `UserData`
/// variants contain handle types into the internal Lua state. It is a logic error to mix handle /// variants contain handle types into the internal Lua state. It is a logic error to mix handle
/// types between separate `Lua` instances, and doing so will result in a panic. /// types between separate `Lua` instances, and doing so will result in a panic.
#[derive(Debug, Clone)] #[derive(Clone)]
pub enum Value<'lua> { pub enum Value<'lua> {
/// The Lua value `nil`. /// The Lua value `nil`.
Nil, Nil,
@@ -40,7 +48,7 @@ pub enum Value<'lua> {
/// A Luau vector. /// A Luau vector.
#[cfg(any(feature = "luau", doc))] #[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))] #[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
Vector(f32, f32, f32), Vector(crate::types::Vector),
/// An interned string, managed by Lua. /// An interned string, managed by Lua.
/// ///
/// Unlike Rust strings, Lua strings may not be valid UTF-8. /// Unlike Rust strings, Lua strings may not be valid UTF-8.
@@ -61,6 +69,12 @@ pub enum Value<'lua> {
pub use self::Value::Nil; pub use self::Value::Nil;
impl<'lua> Value<'lua> { impl<'lua> Value<'lua> {
/// A special value (lightuserdata) to represent null value.
///
/// It can be used in Lua tables without downsides of `nil`.
pub const NULL: Value<'static> = Value::LightUserData(LightUserData(ptr::null_mut()));
/// Returns type name of this value.
pub const fn type_name(&self) -> &'static str { pub const fn type_name(&self) -> &'static str {
match *self { match *self {
Value::Nil => "nil", Value::Nil => "nil",
@@ -69,12 +83,16 @@ impl<'lua> Value<'lua> {
Value::Integer(_) => "integer", Value::Integer(_) => "integer",
Value::Number(_) => "number", Value::Number(_) => "number",
#[cfg(feature = "luau")] #[cfg(feature = "luau")]
Value::Vector(_, _, _) => "vector", Value::Vector(_) => "vector",
Value::String(_) => "string", Value::String(_) => "string",
Value::Table(_) => "table", Value::Table(_) => "table",
Value::Function(_) => "function", Value::Function(_) => "function",
Value::Thread(_) => "thread", 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", Value::Error(_) => "error",
} }
} }
@@ -93,7 +111,7 @@ impl<'lua> Value<'lua> {
match (self, other.as_ref()) { match (self, other.as_ref()) {
(Value::Table(a), Value::Table(b)) => a.equals(b), (Value::Table(a), Value::Table(b)) => a.equals(b),
(Value::UserData(a), Value::UserData(b)) => a.equals(b), (Value::UserData(a), Value::UserData(b)) => a.equals(b),
_ => Ok(self == other.as_ref()), (a, b) => Ok(a == b),
} }
} }
@@ -106,19 +124,425 @@ impl<'lua> Value<'lua> {
/// Typically this function is used only for hashing and debug information. /// Typically this function is used only for hashing and debug information.
#[inline] #[inline]
pub fn to_pointer(&self) -> *const c_void { pub fn to_pointer(&self) -> *const c_void {
unsafe { match self {
match self { Value::LightUserData(ud) => ud.0,
Value::LightUserData(ud) => ud.0, Value::String(String(r))
Value::Table(t) => t.to_pointer(), | Value::Table(Table(r))
Value::String(s) => s.to_pointer(), | Value::Function(Function(r))
Value::Function(Function(v)) | Value::Thread(Thread(r, ..))
| Value::Thread(Thread(v)) | Value::UserData(AnyUserData(r, ..)) => r.to_pointer(),
| Value::UserData(AnyUserData(v)) => v _ => ptr::null(),
.lua }
.ref_thread_exec(|refthr| ffi::lua_topointer(refthr, v.index)), }
_ => ptr::null(),
/// Converts the value to a string.
///
/// If the value has a metatable with a `__tostring` method, then it will be called to get the result.
pub fn to_string(&self) -> Result<StdString> {
match self {
Value::Nil => Ok("nil".to_string()),
Value::Boolean(b) => Ok(b.to_string()),
Value::LightUserData(ud) if ud.0.is_null() => Ok("null".to_string()),
Value::LightUserData(ud) => Ok(format!("lightuserdata: {:p}", ud.0)),
Value::Integer(i) => Ok(i.to_string()),
Value::Number(n) => Ok(n.to_string()),
#[cfg(feature = "luau")]
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 {
let state = r.lua.state();
let _guard = StackGuard::new(state);
check_stack(state, 3)?;
r.lua.push_ref(r);
protect_lua!(state, 1, 1, fn(state) {
ffi::luaL_tolstring(state, -1, ptr::null_mut());
})?;
Ok(String(r.lua.pop_ref()).to_str()?.to_string())
},
Value::Error(err) => Ok(err.to_string()),
}
}
/// 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> {
self.as_integer().map(i64::from)
}
/// 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 {
fn cmp_num(a: Number, b: Number) -> Ordering {
match (a, b) {
_ if a < b => Ordering::Less,
_ if a > b => Ordering::Greater,
_ => Ordering::Equal,
} }
} }
match (self, other) {
// Nil
(Value::Nil, Value::Nil) => Ordering::Equal,
(Value::Nil, _) => Ordering::Less,
(_, Value::Nil) => Ordering::Greater,
// Null (a special case)
(Value::LightUserData(ud1), Value::LightUserData(ud2)) if ud1 == ud2 => Ordering::Equal,
(Value::LightUserData(ud1), _) if ud1.0.is_null() => Ordering::Less,
(_, Value::LightUserData(ud2)) if ud2.0.is_null() => Ordering::Greater,
// Boolean
(Value::Boolean(a), Value::Boolean(b)) => a.cmp(b),
(Value::Boolean(_), _) => Ordering::Less,
(_, Value::Boolean(_)) => Ordering::Greater,
// Integer && Number
(Value::Integer(a), Value::Integer(b)) => a.cmp(b),
(&Value::Integer(a), &Value::Number(b)) => cmp_num(a as Number, b),
(&Value::Number(a), &Value::Integer(b)) => cmp_num(a, b as Number),
(&Value::Number(a), &Value::Number(b)) => cmp_num(a, b),
(Value::Integer(_) | Value::Number(_), _) => Ordering::Less,
(_, Value::Integer(_) | Value::Number(_)) => Ordering::Greater,
// String
(Value::String(a), Value::String(b)) => a.as_bytes().cmp(b.as_bytes()),
(Value::String(_), _) => Ordering::Less,
(_, Value::String(_)) => Ordering::Greater,
// Other variants can be randomly ordered
(a, b) => a.to_pointer().cmp(&b.to_pointer()),
}
}
pub(crate) fn fmt_pretty(
&self,
fmt: &mut fmt::Formatter,
recursive: bool,
ident: usize,
visited: &mut HashSet<*const c_void>,
) -> fmt::Result {
match self {
Value::Nil => write!(fmt, "nil"),
Value::Boolean(b) => write!(fmt, "{b}"),
Value::LightUserData(ud) if ud.0.is_null() => write!(fmt, "null"),
Value::LightUserData(ud) => write!(fmt, "lightuserdata: {:?}", ud.0),
Value::Integer(i) => write!(fmt, "{i}"),
Value::Number(n) => write!(fmt, "{n}"),
#[cfg(feature = "luau")]
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)
}
t @ Value::Table(_) => write!(fmt, "table: {:?}", t.to_pointer()),
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 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"),
}
}
}
impl fmt::Debug for Value<'_> {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
if fmt.alternate() {
return self.fmt_pretty(fmt, true, 0, &mut HashSet::new());
}
match self {
Value::Nil => write!(fmt, "Nil"),
Value::Boolean(b) => write!(fmt, "Boolean({b})"),
Value::LightUserData(ud) => write!(fmt, "{ud:?}"),
Value::Integer(i) => write!(fmt, "Integer({i})"),
Value::Number(n) => write!(fmt, "Number({n})"),
#[cfg(feature = "luau")]
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:?}"),
Value::Thread(t) => write!(fmt, "{t:?}"),
Value::UserData(ud) => write!(fmt, "{ud:?}"),
Value::Error(e) => write!(fmt, "Error({e:?})"),
}
} }
} }
@@ -133,7 +557,7 @@ impl<'lua> PartialEq for Value<'lua> {
(Value::Number(a), Value::Integer(b)) => *a == *b as Number, (Value::Number(a), Value::Integer(b)) => *a == *b as Number,
(Value::Number(a), Value::Number(b)) => *a == *b, (Value::Number(a), Value::Number(b)) => *a == *b,
#[cfg(feature = "luau")] #[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::String(a), Value::String(b)) => a == b,
(Value::Table(a), Value::Table(b)) => a == b, (Value::Table(a), Value::Table(b)) => a == b,
(Value::Function(a), Value::Function(b)) => a == b, (Value::Function(a), Value::Function(b)) => a == b,
@@ -151,61 +575,218 @@ 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")] #[cfg(feature = "serialize")]
impl<'lua> Serialize for Value<'lua> { 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> fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
where where
S: Serializer, S: Serializer,
{ {
match self { match self.value {
Value::Nil => serializer.serialize_unit(), Value::Nil => serializer.serialize_unit(),
Value::Boolean(b) => serializer.serialize_bool(*b), Value::Boolean(b) => serializer.serialize_bool(*b),
#[allow(clippy::useless_conversion)] #[allow(clippy::useless_conversion)]
Value::Integer(i) => serializer Value::Integer(i) => serializer.serialize_i64((*i).into()),
.serialize_i64((*i).try_into().expect("cannot convert lua_Integer to i64")),
#[allow(clippy::useless_conversion)]
Value::Number(n) => serializer.serialize_f64(*n), Value::Number(n) => serializer.serialize_f64(*n),
#[cfg(feature = "luau")] #[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::String(s) => s.serialize(serializer),
Value::Table(t) => t.serialize(serializer), Value::Table(t) => {
Value::UserData(ud) => ud.serialize(serializer), 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::LightUserData(ud) if ud.0.is_null() => serializer.serialize_none(),
Value::Error(_) | Value::LightUserData(_) | Value::Function(_) | Value::Thread(_) => { Value::UserData(ud) if ud.is_serializable() || self.options.deny_unsupported_types => {
let msg = format!("cannot serialize <{}>", self.type_name()); ud.serialize(serializer)
Err(ser::Error::custom(msg)) }
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`. /// Trait for types convertible to `Value`.
pub trait ToLua<'lua> { pub trait IntoLua<'lua>: Sized {
/// Performs the conversion. /// Performs the conversion.
fn to_lua(self, lua: &'lua Lua) -> Result<Value<'lua>>; 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`. /// Trait for types convertible from `Value`.
pub trait FromLua<'lua>: Sized { pub trait FromLua<'lua>: Sized {
/// Performs the conversion. /// Performs the conversion.
fn from_lua(lua_value: Value<'lua>, lua: &'lua Lua) -> Result<Self>; fn from_lua(value: Value<'lua>, lua: &'lua Lua) -> Result<Self>;
/// Performs the conversion for an argument (eg. function argument).
///
/// `i` is the argument index (position),
/// `to` is a function name that received the argument.
#[doc(hidden)]
#[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_stack(idx, lua).map_err(|err| Error::BadArgument {
to: to.map(|s| s.to_string()),
pos: i,
name: None,
cause: Arc::new(err),
})
}
} }
/// Multiple Lua values used for both argument passing and also for multiple return values. /// Multiple Lua values used for both argument passing and also for multiple return values.
#[derive(Debug, Clone)] #[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> { impl<'lua> MultiValue<'lua> {
/// Creates an empty `MultiValue` containing no values. /// Creates an empty `MultiValue` containing no values.
#[inline]
pub const fn new() -> MultiValue<'lua> { 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] #[inline]
pub(crate) fn new_or_cached(lua: &'lua Lua) -> MultiValue<'lua> { pub(crate) fn with_lua_and_capacity(lua: &'lua Lua, capacity: usize) -> MultiValue<'lua> {
lua.new_or_cached_multivalue() 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),
}
} }
} }
@@ -228,8 +809,10 @@ impl<'lua> IntoIterator for MultiValue<'lua> {
type IntoIter = iter::Rev<vec::IntoIter<Value<'lua>>>; type IntoIter = iter::Rev<vec::IntoIter<Value<'lua>>>;
#[inline] #[inline]
fn into_iter(self) -> Self::IntoIter { fn into_iter(mut self) -> Self::IntoIter {
self.0.into_iter().rev() let vec = mem::take(&mut self.vec);
mem::forget(self);
vec.into_iter().rev()
} }
} }
@@ -239,7 +822,7 @@ impl<'a, 'lua> IntoIterator for &'a MultiValue<'lua> {
#[inline] #[inline]
fn into_iter(self) -> Self::IntoIter { fn into_iter(self) -> Self::IntoIter {
self.0.iter().rev() self.vec.iter().rev()
} }
} }
@@ -262,61 +845,60 @@ impl<'lua> Index<usize> for MultiValue<'lua> {
impl<'lua> MultiValue<'lua> { impl<'lua> MultiValue<'lua> {
#[inline] #[inline]
pub fn from_vec(mut v: Vec<Value<'lua>>) -> MultiValue<'lua> { pub fn from_vec(mut vec: Vec<Value<'lua>>) -> MultiValue<'lua> {
v.reverse(); vec.reverse();
MultiValue(v) MultiValue { vec, lua: None }
} }
#[inline] #[inline]
pub fn into_vec(self) -> Vec<Value<'lua>> { pub fn into_vec(mut self) -> Vec<Value<'lua>> {
let mut v = self.0; let mut vec = mem::take(&mut self.vec);
v.reverse(); mem::forget(self);
v vec.reverse();
vec
} }
#[inline] #[inline]
pub fn get(&self, index: usize) -> Option<&Value<'lua>> { pub fn get(&self, index: usize) -> Option<&Value<'lua>> {
self.0.get(self.0.len() - index - 1) if index < self.vec.len() {
} return self.vec.get(self.vec.len() - index - 1);
}
#[inline] None
pub(crate) fn reserve(&mut self, size: usize) {
self.0.reserve(size);
} }
#[inline] #[inline]
pub fn pop_front(&mut self) -> Option<Value<'lua>> { pub fn pop_front(&mut self) -> Option<Value<'lua>> {
self.0.pop() self.vec.pop()
} }
#[inline] #[inline]
pub fn push_front(&mut self, value: Value<'lua>) { pub fn push_front(&mut self, value: Value<'lua>) {
self.0.push(value); self.vec.push(value);
} }
#[inline] #[inline]
pub fn clear(&mut self) { pub fn clear(&mut self) {
self.0.clear(); self.vec.clear();
} }
#[inline] #[inline]
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
self.0.len() self.vec.len()
} }
#[inline] #[inline]
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.0.is_empty() self.vec.is_empty()
} }
#[inline] #[inline]
pub fn iter(&self) -> iter::Rev<slice::Iter<Value<'lua>>> { pub fn iter(&self) -> iter::Rev<slice::Iter<Value<'lua>>> {
self.0.iter().rev() self.vec.iter().rev()
} }
#[inline] #[inline]
pub(crate) fn drain_all(&mut self) -> iter::Rev<vec::Drain<Value<'lua>>> { pub(crate) fn drain_all(&mut self) -> iter::Rev<vec::Drain<Value<'lua>>> {
self.0.drain(..).rev() self.vec.drain(..).rev()
} }
#[inline] #[inline]
@@ -324,22 +906,39 @@ impl<'lua> MultiValue<'lua> {
&mut self, &mut self,
iter: impl IntoIterator<Item = Result<Value<'lua>>>, iter: impl IntoIterator<Item = Result<Value<'lua>>>,
) -> Result<()> { ) -> Result<()> {
self.0.clear(); self.vec.clear();
for value in iter { for value in iter {
self.0.push(value?); self.vec.push(value?);
} }
self.0.reverse(); self.vec.reverse();
Ok(()) Ok(())
} }
} }
/// Trait for types convertible to any number of Lua values. /// Trait for types convertible to any number of Lua values.
/// ///
/// This is a generalization of `ToLua`, allowing any number of resulting Lua values instead of just /// This is a generalization of `IntoLua`, allowing any number of resulting Lua values instead of just
/// one. Any type that implements `ToLua` will automatically implement this trait. /// one. Any type that implements `IntoLua` will automatically implement this trait.
pub trait ToLuaMulti<'lua> { pub trait IntoLuaMulti<'lua>: Sized {
/// Performs the conversion. /// Performs the conversion.
fn to_lua_multi(self, lua: &'lua Lua) -> Result<MultiValue<'lua>>; 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. /// Trait for types that can be created from an arbitrary number of Lua values.
@@ -354,4 +953,56 @@ pub trait FromLuaMulti<'lua>: Sized {
/// assigning values. Similarly, if not enough values are given, conversions should assume that /// assigning values. Similarly, if not enough values are given, conversions should assume that
/// any missing values are nil. /// any missing values are nil.
fn from_lua_multi(values: MultiValue<'lua>, lua: &'lua Lua) -> Result<Self>; fn from_lua_multi(values: MultiValue<'lua>, lua: &'lua Lua) -> Result<Self>;
/// Performs the conversion for a list of arguments.
///
/// `i` is an index (position) of the first argument,
/// `to` is a function name that received the arguments.
#[doc(hidden)]
#[inline]
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)]
mod assertions {
use super::*;
static_assertions::assert_not_impl_any!(Value: Send);
static_assertions::assert_not_impl_any!(MultiValue: Send);
} }
+8
View File
@@ -0,0 +1,8 @@
[lua54_coverage]
features = "lua54,vendored,async,serialize,macros,unstable"
[lua51_coverage]
features = "lua51,vendored,async,serialize,macros,unstable"
[luau_coverage]
features = "luau,async,serialize,macros,unstable"
+210 -153
View File
@@ -1,21 +1,26 @@
#![cfg(feature = "async")] #![cfg(feature = "async")]
use std::cell::Cell; use std::sync::{Arc, Mutex};
use std::rc::Rc;
use std::sync::{
atomic::{AtomicI64, AtomicU64, Ordering},
Arc,
};
use std::time::Duration; use std::time::Duration;
use futures_timer::Delay;
use futures_util::stream::TryStreamExt; use futures_util::stream::TryStreamExt;
use mlua::{ use mlua::{
Error, Function, Lua, LuaOptions, Result, StdLib, Table, TableExt, Thread, UserData, AnyUserDataExt, Error, Function, Lua, LuaOptions, MultiValue, Result, StdLib, Table, TableExt,
UserDataMethods, Value, 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] #[tokio::test]
async fn test_async_function() -> Result<()> { async fn test_async_function() -> Result<()> {
let lua = Lua::new(); let lua = Lua::new();
@@ -30,12 +35,25 @@ async fn test_async_function() -> Result<()> {
Ok(()) Ok(())
} }
#[tokio::test]
async fn test_async_function_wrap() -> Result<()> {
let lua = Lua::new();
let f = Function::wrap_async(|_, s: String| async move { Ok(s) });
lua.globals().set("f", f)?;
let res: String = lua.load(r#"f("hello")"#).eval_async().await?;
assert_eq!(res, "hello");
Ok(())
}
#[tokio::test] #[tokio::test]
async fn test_async_sleep() -> Result<()> { async fn test_async_sleep() -> Result<()> {
let lua = Lua::new(); let lua = Lua::new();
let sleep = lua.create_async_function(move |_lua, n: u64| async move { 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)) Ok(format!("elapsed:{}ms", n))
})?; })?;
lua.globals().set("sleep", sleep)?; lua.globals().set("sleep", sleep)?;
@@ -51,7 +69,7 @@ async fn test_async_call() -> Result<()> {
let lua = Lua::new(); let lua = Lua::new();
let hello = lua.create_async_function(|_lua, name: String| async move { 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)) Ok(format!("hello, {}!", name))
})?; })?;
@@ -71,6 +89,25 @@ async fn test_async_call() -> Result<()> {
Ok(()) 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] #[tokio::test]
async fn test_async_bind_call() -> Result<()> { async fn test_async_bind_call() -> Result<()> {
let lua = Lua::new(); let lua = Lua::new();
@@ -94,7 +131,7 @@ async fn test_async_handle_yield() -> Result<()> {
let lua = Lua::new(); let lua = Lua::new();
let sum = lua.create_async_function(|_lua, (a, b): (i64, i64)| async move { 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) Ok(a + b)
})?; })?;
@@ -152,10 +189,10 @@ async fn test_async_return_async_closure() -> Result<()> {
let lua = Lua::new(); let lua = Lua::new();
let f = lua.create_async_function(|lua, a: i64| async move { 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 { 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); return Ok(a + b);
})?; })?;
@@ -174,6 +211,38 @@ async fn test_async_return_async_closure() -> Result<()> {
Ok(()) Ok(())
} }
#[cfg(feature = "lua54")]
#[tokio::test]
async fn test_async_lua54_to_be_closed() -> Result<()> {
let lua = Lua::new();
let globals = lua.globals();
globals.set("close_count", 0)?;
let code = r#"
local t <close> = setmetatable({}, {
__close = function()
close_count = close_count + 1
end
})
error "test"
"#;
let f = lua.load(code).into_function()?;
// Test close using call_async
let _ = f.call_async::<_, ()>(()).await;
assert_eq!(globals.get::<_, usize>("close_count")?, 1);
// Don't close by default when awaiting async threads
let co = lua.create_thread(f.clone())?;
let _ = co.clone().into_async::<_, ()>(()).await;
assert_eq!(globals.get::<_, usize>("close_count")?, 1);
let _ = co.reset(f);
assert_eq!(globals.get::<_, usize>("close_count")?, 2);
Ok(())
}
#[tokio::test] #[tokio::test]
async fn test_async_thread_stream() -> Result<()> { async fn test_async_thread_stream() -> Result<()> {
let lua = Lua::new(); let lua = Lua::new();
@@ -213,7 +282,7 @@ async fn test_async_thread() -> Result<()> {
let f = lua.create_async_function(move |_lua, ()| { let f = lua.create_async_function(move |_lua, ()| {
let cnt3 = cnt2.clone(); let cnt3 = cnt2.clone();
async move { async move {
Delay::new(Duration::from_millis(*cnt3.as_ref())).await; sleep_ms(*cnt3.as_ref()).await;
Ok("done") Ok("done")
} }
})?; })?;
@@ -229,82 +298,115 @@ async fn test_async_thread() -> Result<()> {
Ok(()) Ok(())
} }
#[test]
fn test_async_thread_capture() -> Result<()> {
let lua = Lua::new();
let f = lua.create_async_function(move |_lua, v: Value| async move {
tokio::task::yield_now().await;
drop(v);
Ok(())
})?;
let thread = lua.create_thread(f)?;
// After first resume, `v: Value` is captured in the coroutine
thread.resume::<_, ()>("abc").unwrap();
drop(thread);
Ok(())
}
#[tokio::test] #[tokio::test]
async fn test_async_table() -> Result<()> { async fn test_async_table() -> Result<()> {
let options = LuaOptions::new().thread_cache_size(4); let options = LuaOptions::new().thread_pool_size(4);
let lua = Lua::new_with(StdLib::ALL_SAFE, options)?; let lua = Lua::new_with(StdLib::ALL_SAFE, options)?;
let table = lua.create_table()?; let table = lua.create_table()?;
table.set("val", 10)?; table.set("val", 10)?;
let get_value = lua.create_async_function(|_, table: Table| async move { 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.get::<_, i64>("val")
})?; })?;
table.set("get_value", get_value)?; table.set("get_value", get_value)?;
let set_value = lua.create_async_function(|_, (table, n): (Table, i64)| async move { 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("val", n)
})?; })?;
table.set("set_value", set_value)?; table.set("set_value", set_value)?;
let sleep = lua.create_async_function(|_, n| async move { 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)) Ok(format!("elapsed:{}ms", n))
})?; })?;
table.set("sleep", sleep)?; table.set("sleep", sleep)?;
assert_eq!( assert_eq!(
table table.call_async_method::<_, i64>("get_value", ()).await?,
.call_async_method::<_, _, i64>("get_value", ())
.await?,
10 10
); );
table.call_async_method("set_value", 15).await?; table.call_async_method("set_value", 15).await?;
assert_eq!( assert_eq!(
table table.call_async_method::<_, i64>("get_value", ()).await?,
.call_async_method::<_, _, i64>("get_value", ())
.await?,
15 15
); );
assert_eq!( assert_eq!(
table table.call_async_function::<_, String>("sleep", 7).await?,
.call_async_function::<_, _, String>("sleep", 7)
.await?,
"elapsed:7ms" "elapsed:7ms"
); );
Ok(()) Ok(())
} }
#[tokio::test]
async fn test_async_thread_pool() -> Result<()> {
let options = LuaOptions::new().thread_pool_size(4);
let lua = Lua::new_with(StdLib::ALL_SAFE, options)?;
let error_f = lua.create_async_function(|_, ()| async move {
sleep_ms(10).await;
Err::<(), _>(Error::runtime("test"))
})?;
let sleep = lua.create_async_function(|_, n| async move {
sleep_ms(n).await;
Ok(format!("elapsed:{}ms", n))
})?;
assert!(error_f.call_async::<_, ()>(()).await.is_err());
// Next call should use cached thread
assert_eq!(sleep.call_async::<_, String>(3).await?, "elapsed:3ms");
Ok(())
}
#[tokio::test] #[tokio::test]
async fn test_async_userdata() -> Result<()> { async fn test_async_userdata() -> Result<()> {
#[derive(Clone)] struct MyUserData(u64);
struct MyUserData(Arc<AtomicU64>);
impl UserData for MyUserData { impl UserData for MyUserData {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_async_method("get_value", |_, data, ()| async move { methods.add_async_method("get_value", |_, data, ()| async move {
Delay::new(Duration::from_millis(10)).await; sleep_ms(10).await;
Ok(data.0.load(Ordering::Relaxed)) Ok(data.0)
}); });
methods.add_async_method("set_value", |_, data, n| async move { methods.add_async_method_mut("set_value", |_, data, n| async move {
Delay::new(Duration::from_millis(10)).await; sleep_ms(10).await;
data.0.store(n, Ordering::Relaxed); data.0 = n;
Ok(()) Ok(())
}); });
methods.add_async_function("sleep", |_, n| async move { methods.add_async_function("sleep", |_, n| async move {
Delay::new(Duration::from_millis(n)).await; sleep_ms(n).await;
Ok(format!("elapsed:{}ms", n)) Ok(format!("elapsed:{}ms", n))
}); });
#[cfg(not(any(feature = "lua51", feature = "luau")))] #[cfg(not(any(feature = "lua51", feature = "luau")))]
methods.add_async_meta_method(mlua::MetaMethod::Call, |_, data, ()| async move { methods.add_async_meta_method(mlua::MetaMethod::Call, |_, data, ()| async move {
let n = data.0.load(Ordering::Relaxed); let n = data.0;
Delay::new(Duration::from_millis(n)).await; sleep_ms(n).await;
Ok(format!("elapsed:{}ms", n)) Ok(format!("elapsed:{}ms", n))
}); });
@@ -312,25 +414,26 @@ async fn test_async_userdata() -> Result<()> {
methods.add_async_meta_method( methods.add_async_meta_method(
mlua::MetaMethod::Index, mlua::MetaMethod::Index,
|_, data, key: String| async move { |_, data, key: String| async move {
Delay::new(Duration::from_millis(10)).await; sleep_ms(10).await;
match key.as_str() { match key.as_str() {
"ms" => Ok(Some(data.0.load(Ordering::Relaxed) as f64)), "ms" => Ok(Some(data.0 as f64)),
"s" => Ok(Some((data.0.load(Ordering::Relaxed) as f64) / 1000.0)), "s" => Ok(Some((data.0 as f64) / 1000.0)),
_ => Ok(None), _ => Ok(None),
} }
}, },
); );
#[cfg(not(any(feature = "lua51", feature = "luau")))] #[cfg(not(any(feature = "lua51", feature = "luau")))]
methods.add_async_meta_method( methods.add_async_meta_method_mut(
mlua::MetaMethod::NewIndex, mlua::MetaMethod::NewIndex,
|_, data, (key, value): (String, f64)| async move { |_, data, (key, value): (String, f64)| async move {
Delay::new(Duration::from_millis(10)).await; sleep_ms(10).await;
match key.as_str() { match key.as_str() {
"ms" => Ok(data.0.store(value as u64, Ordering::Relaxed)), "ms" => data.0 = value as u64,
"s" => Ok(data.0.store((value * 1000.0) as u64, Ordering::Relaxed)), "s" => data.0 = (value * 1000.0) as u64,
_ => Err(Error::external(format!("key '{}' not found", key))), _ => return Err(Error::external(format!("key '{}' not found", key))),
} }
Ok(())
}, },
); );
} }
@@ -339,8 +442,8 @@ async fn test_async_userdata() -> Result<()> {
let lua = Lua::new(); let lua = Lua::new();
let globals = lua.globals(); 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())?; globals.set("userdata", &userdata)?;
lua.load( lua.load(
r#" r#"
@@ -369,123 +472,77 @@ async fn test_async_userdata() -> Result<()> {
.exec_async() .exec_async()
.await?; .await?;
Ok(()) userdata.call_async_method("set_value", 24).await?;
} let n: u64 = userdata.call_async_method("get_value", ()).await?;
assert_eq!(n, 24);
userdata.call_async_function("sleep", 15).await?;
#[tokio::test] #[cfg(not(any(feature = "lua51", feature = "luau")))]
async fn test_async_scope() -> Result<()> { assert_eq!(userdata.call_async::<_, String>(()).await?, "elapsed:24ms");
let ref lua = Lua::new();
let ref rc = Rc::new(Cell::new(0));
let fut = lua.async_scope(|scope| async move {
let f = scope.create_async_function(move |_, n: u64| {
let rc2 = rc.clone();
async move {
rc2.set(42);
Delay::new(Duration::from_millis(n)).await;
assert_eq!(Rc::strong_count(&rc2), 2);
Ok(())
}
})?;
lua.globals().set("f", f.clone())?;
assert_eq!(Rc::strong_count(rc), 1);
let _ = f.call_async::<u64, ()>(10).await?;
assert_eq!(Rc::strong_count(rc), 1);
// Create future in partialy polled state (Poll::Pending)
let g = lua.create_thread(f)?;
g.resume::<u64, ()>(10)?;
lua.globals().set("g", g)?;
assert_eq!(Rc::strong_count(rc), 2);
Ok(())
});
assert_eq!(Rc::strong_count(rc), 1);
let _ = fut.await?;
assert_eq!(Rc::strong_count(rc), 1);
match lua
.globals()
.get::<_, Function>("f")?
.call_async::<_, ()>(10)
.await
{
Err(Error::CallbackError { ref cause, .. }) => match cause.as_ref() {
Error::CallbackDestructed => {}
e => panic!("expected `CallbackDestructed` error cause, got {:?}", e),
},
r => panic!("improper return for destructed function: {:?}", r),
};
match lua.globals().get::<_, Thread>("g")?.resume::<_, Value>(()) {
Err(Error::CallbackError { ref cause, .. }) => match cause.as_ref() {
Error::CallbackDestructed => {}
e => panic!("expected `CallbackDestructed` error cause, got {:?}", e),
},
r => panic!("improper return for destructed function: {:?}", r),
};
Ok(()) Ok(())
} }
#[tokio::test] #[tokio::test]
async fn test_async_scope_userdata() -> Result<()> { async fn test_async_thread_error() -> Result<()> {
#[derive(Clone)] struct MyUserData;
struct MyUserData(Arc<AtomicI64>);
impl UserData for MyUserData { impl UserData for MyUserData {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) { fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_async_method("get_value", |_, data, ()| async move { methods.add_meta_method("__tostring", |_, _this, ()| Ok("myuserdata error"))
Delay::new(Duration::from_millis(10)).await;
Ok(data.0.load(Ordering::Relaxed))
});
methods.add_async_method("set_value", |_, data, n| async move {
Delay::new(Duration::from_millis(10)).await;
data.0.store(n, Ordering::Relaxed);
Ok(())
});
methods.add_async_function("sleep", |_, n| async move {
Delay::new(Duration::from_millis(n)).await;
Ok(format!("elapsed:{}ms", n))
});
} }
} }
let ref lua = Lua::new(); let lua = Lua::new();
let result = lua
let ref arc = Arc::new(AtomicI64::new(11)); .load("function x(...) error(...) end x(...)")
.set_name("chunk")
lua.async_scope(|scope| async move { .call_async::<_, ()>(MyUserData)
let ud = scope.create_userdata(MyUserData(arc.clone()))?; .await;
lua.globals().set("userdata", ud)?; assert!(
lua.load( matches!(result, Err(Error::RuntimeError(cause)) if cause.contains("myuserdata error")),
r#" "improper error traceback from dead thread"
assert(userdata:get_value() == 11) );
userdata:set_value(12)
assert(userdata.sleep(5) == "elapsed:5ms") Ok(())
assert(userdata:get_value() == 12) }
"#,
) #[cfg(all(feature = "unstable", not(feature = "send")))]
.exec_async() #[tokio::test]
.await async fn test_owned_async_call() -> Result<()> {
}) let lua = Lua::new();
.await?;
let hello = lua
assert_eq!(Arc::strong_count(arc), 1); .create_async_function(|_, name: String| async move {
sleep_ms(10).await;
match lua.load("userdata:get_value()").exec_async().await { Ok(format!("hello, {}!", name))
Err(Error::CallbackError { ref cause, .. }) => match cause.as_ref() { })?
Error::CallbackDestructed => {} .into_owned();
e => panic!("expected `CallbackDestructed` error cause, got {:?}", e), drop(lua);
},
r => panic!("improper return for destructed userdata: {:?}", r), assert_eq!(hello.call_async::<_, String>("alex").await?, "hello, alex!");
};
Ok(())
}
#[tokio::test]
async fn test_async_terminate() -> Result<()> {
let lua = Lua::new();
let mutex = Arc::new(Mutex::new(0u32));
let mutex2 = mutex.clone();
let func = lua.create_async_function(move |_, ()| {
let mutex = mutex2.clone();
async move {
let _guard = mutex.lock();
sleep_ms(100).await;
Ok(())
}
})?;
let _ = tokio::time::timeout(Duration::from_millis(30), func.call_async::<_, ()>(())).await;
lua.gc_collect()?;
assert!(mutex.try_lock().is_ok());
Ok(()) Ok(())
} }
+22 -5
View File
@@ -1,12 +1,18 @@
use std::fs; use std::fs;
use std::io; use std::io;
use mlua::{Error, Lua, Result}; use mlua::{Lua, Result};
#[test] #[test]
fn test_chunk_path() -> Result<()> { fn test_chunk_path() -> Result<()> {
let lua = Lua::new(); 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(); let temp_dir = tempfile::tempdir().unwrap();
fs::write( fs::write(
temp_dir.path().join("module.lua"), temp_dir.path().join("module.lua"),
@@ -14,12 +20,11 @@ fn test_chunk_path() -> Result<()> {
return 321 return 321
"#, "#,
)?; )?;
let i: i32 = lua.load(&temp_dir.path().join("module.lua")).eval()?; let i: i32 = lua.load(&*temp_dir.path().join("module.lua")).eval()?;
assert_eq!(i, 321); assert_eq!(i, 321);
match lua.load(&temp_dir.path().join("module2.lua")).exec() { match lua.load(&*temp_dir.path().join("module2.lua")).exec() {
Err(Error::ExternalError(err)) Err(err) if err.downcast_ref::<io::Error>().unwrap().kind() == io::ErrorKind::NotFound => {}
if err.downcast_ref::<io::Error>().unwrap().kind() == io::ErrorKind::NotFound => {}
res => panic!("expected io::Error, got {:?}", res), res => panic!("expected io::Error, got {:?}", res),
}; };
@@ -37,12 +42,24 @@ fn test_chunk_macro() -> Result<()> {
let data = lua.create_table()?; let data = lua.create_table()?;
data.raw_set("num", 1)?; data.raw_set("num", 1)?;
let ud = mlua::AnyUserData::wrap("hello");
let f = mlua::Function::wrap(|_lua, ()| Ok(()));
lua.globals().set("g", 123)?; lua.globals().set("g", 123)?;
let string = String::new();
let str = string.as_str();
lua.load(mlua::chunk! { lua.load(mlua::chunk! {
assert($name == "Rustacean") assert($name == "Rustacean")
assert(type($table) == "table")
assert($table[1] == 1) assert($table[1] == 1)
assert(type($data) == "table")
assert($data.num == 1) assert($data.num == 1)
assert(type($ud) == "userdata")
assert(type($f) == "function")
assert(type($str) == "string")
assert($str == "")
assert(g == 123) assert(g == 123)
s = 321 s = 321
}) })
+5 -1
View File
@@ -15,7 +15,11 @@ fn test_compilation() {
t.compile_fail("tests/compile/static_callback_args.rs"); t.compile_fail("tests/compile/static_callback_args.rs");
#[cfg(feature = "async")] #[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")] #[cfg(feature = "send")]
t.compile_fail("tests/compile/non_send.rs"); 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,83 @@
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
|
7 | let s = String::new();
| - binding `s` declared here
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 {
| ++++
+1 -3
View File
@@ -1,8 +1,6 @@
use mlua::{Lua, UserData, UserDataMethods}; use mlua::{UserData, UserDataMethods};
fn main() { fn main() {
let ref lua = Lua::new();
#[derive(Clone)] #[derive(Clone)]
struct MyUserData<'a>(&'a i64); struct MyUserData<'a>(&'a i64);
+11 -41
View File
@@ -1,41 +1,11 @@
error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements error: lifetime may not live long enough
--> tests/compile/async_nonstatic_userdata.rs:11:72 --> tests/compile/async_nonstatic_userdata.rs:9:13
| |
11 | methods.add_async_method("print", |_, data, ()| async move { 7 | impl<'a> UserData for MyUserData<'a> {
| ________________________________________________________________________^ | -- lifetime `'a` defined here
12 | | println!("{}", data.0); 8 | fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
13 | | Ok(()) 9 | / methods.add_async_method("print", |_, data, ()| async move {
14 | | }); 10 | | println!("{}", data.0);
| |_____________^ 11 | | Ok(())
| 12 | | });
note: first, the lifetime cannot outlive the lifetime `'a` as defined here... | |______________^ requires that `'a` must outlive `'static`
--> tests/compile/async_nonstatic_userdata.rs:9:10
|
9 | impl<'a> UserData for MyUserData<'a> {
| ^^
note: ...so that the types are compatible
--> tests/compile/async_nonstatic_userdata.rs:11:72
|
11 | methods.add_async_method("print", |_, data, ()| async move {
| ________________________________________________________________________^
12 | | println!("{}", data.0);
13 | | Ok(())
14 | | });
| |_____________^
= note: expected `(MyUserData<'_>,)`
found `(MyUserData<'a>,)`
note: but, the lifetime must be valid for the lifetime `'lua` as defined here...
--> tests/compile/async_nonstatic_userdata.rs:10:24
|
10 | fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
| ^^^^
note: ...so that the type `impl Future<Output = [async output]>` will meet its required lifetime bounds...
--> tests/compile/async_nonstatic_userdata.rs:11:21
|
11 | methods.add_async_method("print", |_, data, ()| async move {
| ^^^^^^^^^^^^^^^^
note: ...that is required by this bound
--> src/userdata.rs
|
| MR: 'lua + Future<Output = Result<R>>;
| ^^^^
+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`
+4 -4
View File
@@ -1,10 +1,10 @@
error[E0373]: closure may outlive the current function, but it borrows `test`, which is owned by the current function error[E0373]: closure may outlive the current function, but it borrows `test.0`, which is owned by the current function
--> tests/compile/function_borrow.rs:9:33 --> tests/compile/function_borrow.rs:9:33
| |
9 | let _ = lua.create_function(|_, ()| -> Result<i32> { 9 | let _ = lua.create_function(|_, ()| -> Result<i32> {
| ^^^^^^^^^^^^^^^^^^^^^^ may outlive borrowed value `test` | ^^^^^^^^^^^^^^^^^^^^^^ may outlive borrowed value `test.0`
10 | Ok(test.0) 10 | Ok(test.0)
| ------ `test` is borrowed here | ------ `test.0` is borrowed here
| |
note: function requires argument type to outlive `'static` note: function requires argument type to outlive `'static`
--> tests/compile/function_borrow.rs:9:13 --> tests/compile/function_borrow.rs:9:13
@@ -14,7 +14,7 @@ note: function requires argument type to outlive `'static`
10 | | Ok(test.0) 10 | | Ok(test.0)
11 | | }); 11 | | });
| |______^ | |______^
help: to force the closure to take ownership of `test` (and any other referenced variables), use the `move` keyword help: to force the closure to take ownership of `test.0` (and any other referenced variables), use the `move` keyword
| |
9 | let _ = lua.create_function(move |_, ()| -> Result<i32> { 9 | let _ = lua.create_function(move |_, ()| -> Result<i32> {
| ++++ | ++++
+59 -17
View File
@@ -1,18 +1,60 @@
error[E0277]: the type `UnsafeCell<mlua::lua::LuaInner>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary error[E0277]: the type `UnsafeCell<mlua::lua::ExtraData>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
--> tests/compile/lua_norefunwindsafe.rs:7:5 --> tests/compile/lua_norefunwindsafe.rs:7:18
| |
7 | catch_unwind(|| lua.create_table().unwrap()); 7 | catch_unwind(|| lua.create_table().unwrap());
| ^^^^^^^^^^^^ `UnsafeCell<mlua::lua::LuaInner>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary | ------------ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<mlua::lua::ExtraData>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
| | |
= help: within `Lua`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<mlua::lua::LuaInner>` | required by a bound introduced by this call
= note: required because it appears within the type `alloc::sync::ArcInner<UnsafeCell<mlua::lua::LuaInner>>` |
= note: required because it appears within the type `PhantomData<alloc::sync::ArcInner<UnsafeCell<mlua::lua::LuaInner>>>` = help: within `Lua`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<mlua::lua::ExtraData>`
= note: required because it appears within the type `Arc<UnsafeCell<mlua::lua::LuaInner>>` note: required because it appears within the type `ArcInner<UnsafeCell<ExtraData>>`
= note: required because it appears within the type `Lua` --> $RUST/alloc/src/sync.rs
= note: required because of the requirements on the impl of `UnwindSafe` for `&Lua` |
= note: required because it appears within the type `[closure@$DIR/tests/compile/lua_norefunwindsafe.rs:7:18: 7:48]` | struct ArcInner<T: ?Sized> {
| ^^^^^^^^
note: required because it appears within the type `PhantomData<ArcInner<UnsafeCell<ExtraData>>>`
--> $RUST/core/src/marker.rs
|
| pub struct PhantomData<T: ?Sized>;
| ^^^^^^^^^^^
note: required because it appears within the type `Arc<UnsafeCell<ExtraData>>`
--> $RUST/alloc/src/sync.rs
|
| pub struct Arc<
| ^^^
note: required because it appears within the type `LuaInner`
--> src/lua.rs
|
| pub struct LuaInner {
| ^^^^^^^^
note: required because it appears within the type `ArcInner<LuaInner>`
--> $RUST/alloc/src/sync.rs
|
| struct ArcInner<T: ?Sized> {
| ^^^^^^^^
note: required because it appears within the type `PhantomData<ArcInner<LuaInner>>`
--> $RUST/core/src/marker.rs
|
| pub struct PhantomData<T: ?Sized>;
| ^^^^^^^^^^^
note: required because it appears within the type `Arc<LuaInner>`
--> $RUST/alloc/src/sync.rs
|
| pub struct Arc<
| ^^^
note: required because it appears within the type `Lua`
--> src/lua.rs
|
| pub struct Lua(Arc<LuaInner>);
| ^^^
= note: required for `&Lua` to implement `UnwindSafe`
note: required because it's used within this closure
--> tests/compile/lua_norefunwindsafe.rs:7:18
|
7 | catch_unwind(|| lua.create_table().unwrap());
| ^^
note: required by a bound in `catch_unwind` note: required by a bound in `catch_unwind`
--> $RUST/std/src/panic.rs --> $RUST/std/src/panic.rs
| |
| pub fn catch_unwind<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> Result<R> { | pub fn catch_unwind<F: FnOnce() -> R + UnwindSafe, R>(f: F) -> Result<R> {
| ^^^^^^^^^^ required by this bound in `catch_unwind` | ^^^^^^^^^^ required by this bound in `catch_unwind`
+26 -17
View File
@@ -1,19 +1,28 @@
error[E0277]: `Rc<Cell<i32>>` cannot be sent between threads safely error[E0277]: `Rc<Cell<i32>>` cannot be sent between threads safely
--> tests/compile/non_send.rs:11:9 --> tests/compile/non_send.rs:11:25
| |
11 | lua.create_function(move |_, ()| { 11 | lua.create_function(move |_, ()| {
| _________^^^^^^^^^^^^^^^_- | --------------- ^-----------
| | | | | |
| | `Rc<Cell<i32>>` cannot be sent between threads safely | _________|_______________within this `{closure@$DIR/tests/compile/non_send.rs:11:25: 11:37}`
12 | | Ok(data.get()) | | |
13 | | })? | | required by a bound introduced by this call
| |_____- within this `[closure@$DIR/tests/compile/non_send.rs:11:25: 13:6]` 12 | | Ok(data.get())
| 13 | | })?
= help: within `[closure@$DIR/tests/compile/non_send.rs:11:25: 13:6]`, the trait `Send` is not implemented for `Rc<Cell<i32>>` | |_____^ `Rc<Cell<i32>>` cannot be sent between threads safely
= note: required because it appears within the type `[closure@$DIR/tests/compile/non_send.rs:11:25: 13:6]` |
= note: required because of the requirements on the impl of `mlua::types::MaybeSend` for `[closure@$DIR/tests/compile/non_send.rs:11:25: 13:6]` = help: within `{closure@$DIR/tests/compile/non_send.rs:11:25: 11:37}`, the trait `Send` is not implemented for `Rc<Cell<i32>>`
note: required because it's used within this closure
--> tests/compile/non_send.rs:11:25
|
11 | lua.create_function(move |_, ()| {
| ^^^^^^^^^^^^
= note: required for `{closure@$DIR/tests/compile/non_send.rs:11:25: 11:37}` to implement `mlua::types::MaybeSend`
note: required by a bound in `Lua::create_function` note: required by a bound in `Lua::create_function`
--> src/lua.rs --> src/lua.rs
| |
| F: 'static + MaybeSend + Fn(&'lua Lua, A) -> Result<R>, | pub fn create_function<'lua, A, R, F>(&'lua self, func: F) -> Result<Function<'lua>>
| ^^^^^^^^^ required by this bound in `Lua::create_function` | --------------- required by a bound in this associated function
...
| F: Fn(&'lua Lua, A) -> Result<R> + MaybeSend + 'static,
| ^^^^^^^^^ required by this bound in `Lua::create_function`

Some files were not shown because too many files have changed in this diff Show More