Compare commits

..

459 Commits

Author SHA1 Message Date
Alex Orlenko 928d94d255 v0.11.0 2025-07-14 15:33:02 +01:00
Alex Orlenko 583c35a172 Prepare for v0.11.0 2025-07-12 22:47:20 +01:00
Alex Orlenko 1791c599f4 mlua-sys: v0.8.2 2025-07-12 22:46:37 +01:00
Alex Orlenko 1e48817a64 Fix deregistering previously-registered userdata 2025-07-12 19:20:15 +01:00
Alex Orlenko 8d219503dd Opt-out from R: MaybeSend in AsyncThread<R> 2025-07-12 15:34:04 +01:00
Alex Orlenko 95367855c1 Return AsyncCallFuture<R> instead of opaque impl Future from ObjectLike trait. 2025-07-12 15:30:58 +01:00
Alex Orlenko 49389c4aa4 Wrap Function::coverage callback to RefCell (Luau) 2025-07-12 13:34:39 +01:00
Alex Orlenko 13dc2b5352 Don't release Lua lock prematurely when when accessing Buffer bytes (Luau) 2025-07-12 13:08:33 +01:00
Alex Orlenko 7afbf74128 Add MaybeSend bound to async methods on ObjectLike trait (sealed) 2025-07-12 12:45:36 +01:00
Alex Orlenko 06c3bd9d69 Fix serde README section (close #613) 2025-07-12 11:50:11 +01:00
Alex Orlenko 1ddaea60ce Bump luau-src to 0.15.4+luau682 2025-07-12 11:27:41 +01:00
Alex Orlenko a653d08768 Simplify Compiler::add_library_constant (combine lib and member) 2025-07-09 00:11:46 +01:00
Alex Orlenko 2b6b0144a1 Merge Compiler::set_vector_lib into set_vector_ctor 2025-07-08 23:55:45 +01:00
Alex Orlenko 4cfe0be945 Update CHANGELOG 2025-07-08 23:22:18 +01:00
Alex Orlenko a9a4814c3c Use StdString for consistency in chunk.rs 2025-07-08 22:25:23 +01:00
Alex Orlenko b1f73ec29d Update Luau Compiler methods to better control extra options:
- Add `add_mutable_global`
- Add `add_userdata_type`
- Replace `set_library_constants` with `add_library_constant`
- Add `add_disabled_builtin`
2025-07-08 22:22:44 +01:00
Alex Orlenko 61a2141151 Don't panic when fourth library searcher does not exists.
When disabling C modules, we remove the last two searchers (C & C all-in-one).
In Pluto the C searches may not exist by design, in this case check that 4th searcher is present before removing it.
Closes #530
2025-07-08 13:37:48 +01:00
Alex Orlenko dea38f27a5 Change !cfg!(..) to cfg!(not(..)) for better readability 2025-07-08 10:42:15 +01:00
Sculas 04aaa18dc8 feat: Allow external build scripts to link Lua libraries (#529)
Allow external build scripts to link Lua libraries
2025-07-08 10:30:26 +01:00
Alex Orlenko d8455c038a Update CHANGELOG 2025-07-08 10:12:03 +01:00
Alex Orlenko ca22ea3be7 Deprecate Debug::curr_line() in favour of Debug::current_line() that returns Option 2025-07-08 10:09:26 +01:00
Alex Orlenko cf05593d66 Fix Debug::is_tail_call 2025-07-08 10:05:37 +01:00
Alex Orlenko d011a1f851 Update CHANGELOG 2025-07-07 23:15:46 +01:00
Alex Orlenko 1ec4661bf9 mlua_derive: v0.11.0 2025-07-07 22:58:41 +01:00
Alex Orlenko d3b2999d2f Remove MaybeSend requirement from Require trait and add to Lua::create_require_function instead 2025-07-07 22:58:24 +01:00
Alex Orlenko 72f6536efb Check table requested capacity limits before enabling unprotected mode.
Lua tables have limits and can overflow, which must be captured in protected mode.
2025-07-07 22:57:30 +01:00
Alex Orlenko 646827a6bb Update Table::set_metatable
- Return Err (instead of panic) when trying to change readonly table (Luau)
- Slightly optimize performance
2025-07-06 11:37:02 +01:00
Alex Orlenko 1882931cd9 Optimize Table::metatable 2025-07-06 10:57:26 +01:00
Alex Orlenko c90cac5189 Add Lua::set_globals method to replace global environment.
Closes #611
2025-07-06 10:57:25 +01:00
Alex Orlenko c0d839d8d2 Make Thread::state pub (hidden) 2025-07-05 22:36:47 +01:00
Alex Orlenko 80471c6dad Optimize AnyUserData::metatable 2025-07-05 11:33:11 +01:00
Alex Orlenko 4b9d1cf271 Replace impl ToString with Into<StdString>
This is a more canonical way to accept any types of stirng but not arbitrary types that implement `Display`
2025-07-05 11:21:18 +01:00
Alex Orlenko 55c07f3b28 Some minor changes in Luau TextRequirer (comments, naming, etc) 2025-07-03 14:42:24 +01:00
Alex Orlenko ef4eabd327 Don't use Value::as_str() internally 2025-07-01 22:51:16 +01:00
Alex Orlenko a3302afdc1 Deprecate Value::as_str and Value::as_string_lossy
These methods don't follow Rust naming convention, see
https://rust-lang.github.io/api-guidelines/naming.html#ad-hoc-conversions-follow-as_-to_-into_-conventions-c-conv
2025-07-01 22:35:40 +01:00
Alex Orlenko dfb4e9a668 Fix LuaJIT stack inspection tests 2025-07-01 21:40:06 +01:00
Alex Orlenko 92db0f6d3a Fix Lua::inspect_stack callback proto 2025-06-30 23:28:04 +01:00
Alex Orlenko a3697ab1db Add Debug::function method to get function running at a given level.
Close #607
2025-06-30 23:25:36 +01:00
Alex Orlenko 052740db15 Save lua_State at the moment of constructing Debug instead of resolving it dynamically 2025-06-30 22:38:46 +01:00
Alex Orlenko faf547c154 Refactor Lua::inspect_stack and debug interface.
It was possible to cause a crash when getting a `Debug` instance and keeping it while deallocating the Lua stack frames.
2025-06-30 12:21:10 +01:00
Alex Orlenko 0de7cd1c7d Don't move or wrap ffi::lua_Debug struct when inspecting stack
This can cause a crash if `ffi::lua_Debug` changed between `lua_getstack` and `lua_getinfo` calls.
Fixes #610
2025-06-29 11:49:34 +01:00
Alex Orlenko 58953e5635 v0.11.0-beta.3 2025-06-23 23:23:07 +01:00
Alex Orlenko 6406de405d mlua-sys: v0.8.1 2025-06-23 23:22:53 +01:00
Alex Orlenko 727096dd3b Handle OOM error during luau_load (Luau >= 0.679) 2025-06-23 23:22:36 +01:00
Alex Orlenko 2445230759 Update CHANGELOG 2025-06-23 22:44:36 +01:00
Alex Orlenko 1cd2bdc808 Ignore test_async_require on windows 2025-06-23 16:37:46 +01:00
Alex Orlenko f8ed33a2aa Fix tests 2025-06-23 16:23:53 +01:00
Alex Orlenko 25955893e0 (Luau Require) Resolve Lua file path relative to the current directory
and unrelated to Rust source file location.
When a Lua file is required inside a Rust file (in a chunk), we should resolve the Lua file relative to the current directory,
instead of relative to the Rust chunk path.
The Rust file location is an internal information that does not exist when the compiled binary runs.
Fixes #605
2025-06-23 15:53:35 +01:00
Alex Orlenko 3f0c69b70b Fix logic to terminate futures on drop.
The underlying Lua thread must stay in yielded state rather than finished.
2025-06-17 15:25:21 +01:00
Alex Orlenko 9da98d42c7 Move ref_stack_pop into ExtraData method. 2025-06-16 22:25:26 +01:00
Alex Orlenko f539f60987 Fix Function::deep_clone() method (Luau).
The `lua_clonefunction` function can fail (and trigger GC) so we need to return Result instead of allowing longjmp
2025-06-16 21:41:29 +01:00
Alex Orlenko aa187e6663 Increase REF_STACK_RESERVE to 3 slots 2025-06-16 21:37:16 +01:00
Alex Orlenko ec10bf2a39 Revert 2e0e86dab2 (Update stack_value helper) 2025-06-16 21:33:47 +01:00
Alex Orlenko 3ea80b763d cargo fmt 2025-06-16 11:21:51 +01:00
Alex Orlenko 2e0e86dab2 Update stack_value helper.
It uses zero stack spaces in Luau, and ref thread for `WrappedError` check in ther Lua versions.
Close #597
2025-06-16 11:12:53 +01:00
Alex Orlenko 0ac7aebb27 Update Lua::sandbox doc 2025-06-13 15:51:31 +01:00
Alex Orlenko 634e5d4550 Reduce collectgarbage options in sandboxed mode
See https://luau.org/sandbox#library
2025-06-13 15:47:54 +01:00
Alex Orlenko 05d6c20520 One more mismatched-lifetime-syntaxes 2025-06-12 23:44:47 +01:00
Alex Orlenko 63e7cfd31b Satisfy mismatched-lifetime-syntaxes lint (nightly)
See rust-lang/rust#141787
2025-06-12 16:30:48 +01:00
Alex Orlenko 9c24c99cbe v0.11.0-beta.2 2025-06-12 14:12:46 +01:00
Alex Orlenko 62f84828f2 Open some doc(hidden) functionality 2025-06-12 13:42:26 +01:00
Alex Orlenko 7bc72be7d3 Use serde feature flag instead of serialize.
The old one is still supported.
2025-06-12 13:35:22 +01:00
Ron Tseytlin 2fbbbe4238 Fix minor grammar mistakes in README.md (#591) 2025-06-12 12:57:11 +01:00
Alex Orlenko f00208373e Bump lua-src 2025-06-12 00:17:29 +01:00
Alex Orlenko a2dc662a92 Add RawLua::create_table_from (internal) 2025-06-11 23:55:06 +01:00
Alex Orlenko caeac2e9a3 Add private app_data container for mlua internal use 2025-06-11 22:24:26 +01:00
Alex Orlenko 3d5261640d Fix doc warnings 2025-06-06 23:25:36 +01:00
Alex Orlenko b57a6239a6 mlua_derive: v0.11.0-beta.2 2025-06-06 23:20:01 +01:00
Alex Orlenko 031424f6ce Update dev dependencies 2025-06-06 23:17:58 +01:00
Alex Orlenko c4c9609ac6 mlua-sys: v0.8.0 2025-06-06 23:09:30 +01:00
Alex Orlenko 6fcd18e434 Update CHANGELOG 2025-06-06 16:03:59 +01:00
Alex Orlenko 39cac5699a Add unwinding support (returning an Error) to Luau Require implementation 2025-06-06 13:46:51 +01:00
Alex Orlenko e6e1ef014f Some cosmetic changes (Luau "require") 2025-06-05 23:11:14 +01:00
Alex Orlenko 942a443592 Bump Lua 5.4 to 5.4.8 2025-06-05 15:02:47 +01:00
Alex Orlenko 65e292dac4 More Luau require tests 2025-06-03 23:01:50 +01:00
Alex Orlenko 1a82f83644 Move parking_lot/send_guard from send to userdata-wrappers feature.
Related to #553
2025-05-30 23:20:06 +01:00
Alex Orlenko 38fbd08c72 Use luau0-src v0.15.0 (Luau 0.676) 2025-05-30 21:56:27 +01:00
Alex Orlenko b3854d2f1d Add '=' prefix to __mlua_bind name 2025-05-28 14:03:10 +01:00
Alex Orlenko 00a56b115b Add LuaBorrowedBytes/LuaBorrowedStr to prelude 2025-05-28 14:01:35 +01:00
Alex Orlenko 2fefaafaa6 Update "AnyUserData::take" to work on ref thread without need to push into stack. 2025-05-28 12:13:34 +01:00
Alex Orlenko 76a8f8cc71 Add __type to Error's userdata metatable.
Close #585
2025-05-27 01:31:54 +01:00
Alex Orlenko 13395e9c3d Sync mlua_derive with AsChunk trait 2025-05-27 00:54:24 +01:00
Alex Orlenko c61219dd93 Update Luau Require trait (sync with 0.674) 2025-05-27 00:12:45 +01:00
Alex Orlenko 2e7c654cfe Make AsChunk trait dyn-friendly 2025-05-26 23:29:59 +01:00
Alex Orlenko f36aaa5ce1 Add loadstring function to Luau
Closes #578
2025-05-15 11:10:42 +01:00
Alex Orlenko df38878278 Update __mlua_index/__mlua_newindex chunk names 2025-05-15 11:03:30 +01:00
Alex Orlenko c5c1fe3b85 Fix tests 2025-05-14 00:19:29 +01:00
Alex Orlenko 0cc4b15f6b Generate doc for lua_module macro using doc cfg instead of docsrs 2025-05-13 21:56:11 +01:00
Alex Orlenko f16aca687d Terminate underlying Rust future when AsyncThread is dropped.
Before this change, Lua GC was responsible to collect and destroy the future if
`AsyncThread` dropped in yielded state.
Now we will propagate "drop" event immediately so Lua GC need to only free the memory.
2025-05-09 12:48:40 +01:00
Alex Orlenko 9b45663afd v0.11.0-beta.1 2025-05-06 23:25:26 +01:00
Alex Orlenko c0c802262e Add must_use to AsyncCallFuture 2025-05-06 22:48:48 +01:00
Alex Orlenko dda344bfc6 Rename _typos.toml to typos.toml 2025-05-06 22:42:19 +01:00
Alex Orlenko 93adfa42d1 Update doc comments 2025-05-06 22:36:23 +01:00
Alex Orlenko 5dc6c3214b Prepare for 2024 edition 2025-05-06 21:28:21 +01:00
Alex Orlenko da08f7210f Revert disabling "module" feature flag for Luau in mlua-sys
Dynamic linking should still be okay
2025-05-06 15:48:52 +01:00
Alex Orlenko 8b7a85076a mlua-sys: Prepare to 2024 edition 2025-05-06 15:47:14 +01:00
Alex Orlenko a4ff68a120 v0.10.4 2025-05-06 15:47:09 +01:00
Alex Orlenko 5454cfa5ed mlua-sys: v0.7.0 2025-05-06 15:46:35 +01:00
Alex Orlenko f9d9dc74a7 Update CHANGELOG 2025-05-06 15:46:29 +01:00
Alex Orlenko ed796fff51 Support async require loaders for Luau 2025-05-04 11:00:59 +01:00
Alex Orlenko 65e5be81ba Update __mlua_async_poll chunk name 2025-05-04 11:00:27 +01:00
Alex Orlenko 4fa8e645b7 Fix Luau "require" tests 2025-05-03 21:45:39 +01:00
Alex Orlenko 05a0abb8c2 Replace Requite::load with Require::loader 2025-05-03 18:00:02 +01:00
Alex Orlenko f0c0527c2b Update Lua* dependencies 2025-05-03 17:58:33 +01:00
Alex Orlenko 2490dfeb38 Silence clippy false positives 2025-05-01 13:18:40 +01:00
Alex Orlenko 4a3dbafb6e Update scoped userdata mismatch tests 2025-05-01 13:12:00 +01:00
Alex Orlenko 97ca3e2c08 Check that type passed to scoped userdata self argument is userdata.
If passed type is non-userdata we try to get a pointer (which will be null) that triggers an assertion.
Having a check also allow us to generate right error message.
Fixes #569
2025-05-01 13:09:23 +01:00
Alex Orlenko d6fb328660 Don't use Lua::get_or_init_from_ptr in Require::load wrapper 2025-04-28 10:07:11 +01:00
Alex Orlenko edd508ed1f Make StateGuard automatically enabled inside callback_error_ext.
Remove manual usage of `StateGuard` in other places.
Closes #567
2025-04-27 23:09:20 +01:00
Alex Orlenko a30a736291 Don't borrow mutably appdata container when working with require content cache 2025-04-27 00:38:30 +01:00
Alex Orlenko 8e1df48e81 Add encode_empty_tables_as_array serialize option.
This will change the behaviour of encoding empty Lua tables into array instead of map.
2025-04-26 17:53:56 +01:00
Alex Orlenko efd192b816 Optimize Table::is_empty 2025-04-26 15:37:12 +01:00
krakow10 5e1451a465 Fix Reversed Comments & Typo (#560)
* Fix reversed comments

* Fix typos
2025-04-26 13:49:50 +01:00
Alex Orlenko 64b1d152b9 Deprecate Lua::load_from_finction and replace it with Lua::register_module and Lua::preload_module instead. 2025-04-26 13:42:07 +01:00
Alex Orlenko a8a4aa8c93 Switch to "proxyrequire" function (follow up Luau 0.671) 2025-04-25 23:41:11 +01:00
Alex Orlenko 9fda2ecfcc mlua-sys: Update Luau to 0.671 2025-04-25 23:38:49 +01:00
Alex Orlenko 21fc924457 Add Lua::create_require_function method 2025-04-25 20:01:37 +01:00
Alex Orlenko 30d5e08a8a Add MaybeSend to Require trait 2025-04-25 15:51:33 +01:00
Alex Orlenko 560a30ca02 Support new Luau require system
This commit introduces the `Require` trait that be used to change `require` behaviour.
By default mlua implements behaviour same as `ReplRequirer` in the original Luau.
Unfortunately binary Luau modules are no longer supported by the new system.
2025-04-25 14:19:41 +01:00
Alex Orlenko 117f8377e2 mlua-sys: Add definitions for Luau require library (since 0.669) 2025-04-20 21:44:16 +02:00
Alex Orlenko 8173ef2fa8 Update README 2025-04-06 17:51:54 +01:00
Alex Orlenko 4e5677ae54 Update references to mlua repo 2025-04-06 15:37:38 +01:00
Alex Orlenko 49958f4827 Split Luau thread event callback to creation and collection callbacks.
We need this because they have different requirements:
- Thread creation callback can return Error or panic
- Thread collection callback runs during Luau GC cycle and cannot make any Lua calls or trigger panics.
2025-04-06 13:57:15 +01:00
Alex Orlenko 39b3af2ff2 Fix warnings when testing documentation 2025-04-05 23:44:25 +01:00
Alex Orlenko b805939320 Make Lua::weak() method and WeakLua struct public.
This can be useful to prevent circular dependencies between Rust and Lua
or check that Lua instance is still alive.
2025-04-05 23:40:05 +01:00
Alex Orlenko bd13300288 Update dependencies 2025-04-05 18:30:22 +01:00
Alex Orlenko 12e61e01f6 Restrict access to Luau VM from UserData destructors.
It's unsafe to make almost any Lua calls when userdata destructor is running.
This can cause recursive GC run and crash.
See https://github.com/luau-lang/luau/pull/510 for some details.
2025-04-05 10:31:39 +01:00
Alex Orlenko 53ff494ab9 Prepare for custom (Luau) userdata destructors.
We need to add logic later to prevent calling any Lua functions when UserData destructor is running.
2025-04-04 16:36:06 +01:00
Alex Orlenko 1cb081d20a Do not propagate collect_garbage flag when clonning Lua 2025-04-04 10:58:08 +01:00
Alex Orlenko c1a018a60d Add AnyUserData::type_id method 2025-03-31 22:41:42 +01:00
Alex Orlenko 4d2ff45a8b Update Lua::poll_pending doc (still hidden) 2025-03-31 22:26:47 +01:00
Alex Orlenko 72682198eb Bump Luau to 0.667 2025-03-30 11:12:14 +01:00
Alex Orlenko 87fa663ac1 Remove deprecated functions 2025-03-28 13:24:15 +00:00
Alex Orlenko 1f970824b4 Support user thread creation callback (Luau) 2025-03-28 11:51:35 +00:00
Alex Orlenko 8d864e2687 Reduce stack operations when creating userdata 2025-03-22 14:23:59 +00:00
Alex Orlenko b0140cdb9e Add Chunk::name(), Chunk::environment() and Chunk::mode() functions.
They can be used to retrieve existing chunk params.
2025-03-22 09:31:24 +00:00
Alex Orlenko 806817212e Rename Lua::init_from_ptr to Lua::get_or_init_from_ptr.
Return reference to `&Lua` with unbound lifetime (chosen by user).
2025-03-21 21:28:30 +00:00
Alex Orlenko 95c623e94d Use c string literal where appropriate 2025-03-21 15:57:15 +00:00
Alex Orlenko 0a1a1fe0b6 Move some userdata helpers from crate::util to crate::userdata::util 2025-03-21 15:13:53 +00:00
Alex Orlenko 4fe7d151a1 Refactor userdata-wrappers feature.
Support borrowing underlying data in `UserDataRef` and `UserDataRefMut`.
2025-03-21 14:19:38 +00:00
Alex Orlenko edbf1e9150 Change Lua library name constants from &str to *const c_char.
It makes easier to pass them to Lua API functions.
2025-03-14 23:10:14 +00:00
Alex Orlenko 69011a89d2 Revert "Add initial pluto support"
This reverts commit 0ed11e4134.
2025-03-14 22:59:06 +00:00
Alex Orlenko 0ed11e4134 Add initial pluto support
Pluto is a superset of Lua 5.4 with a focus on general-purpose programming.
`pluto` feature also enables `lua54` feature since they are compatible and share the same API.
2025-03-14 11:38:07 +00:00
Alex Orlenko 0ce599aff2 Update test case 2025-03-13 17:41:13 +00:00
Alex Orlenko 1ebb4b468a Support 52-bit integers for Luau
Simply to float conversion (it actually never fails or goes out of range)
2025-03-13 16:18:44 +00:00
Alex Orlenko 5ab97666d7 Fix clippy warnings 2025-03-07 22:34:50 +00:00
Alex Orlenko e706ae4fcb Remove Roblox from references to Luau
Closes #537
2025-02-24 19:03:36 +00:00
Alex Orlenko ae7cdcb934 Remove (internal) borrow counter and use instead "locked" flag and strong reference counter 2025-02-14 14:41:37 +00:00
Alex Orlenko dacddfa967 Add Variadic to prelude 2025-02-14 12:08:57 +00:00
Alex Orlenko a01b032c35 Add bstr/serde dependency if serialize feature flag is enabled 2025-02-11 13:39:21 +00:00
Alex Orlenko 18497f1528 Add library constants support for Luau compiler.
Add ability to specify compile-time constants for known library members.
2025-02-11 00:16:22 +00:00
Alex Orlenko 28e8f56989 Fix tests 2025-02-09 22:17:25 +00:00
Alex Orlenko c1f8abba9e Do not allow recursive warnings (Lua 5.4) 2025-02-09 15:08:52 +00:00
Alex Orlenko d1a587f49a Wrap hooks in refcounter instead of box, same as previously.
This allows to obtain independent clone of closure.
Ensure that stack is always clean when running thread hook.
2025-02-08 22:11:11 +00:00
Alex Orlenko 5bbd23ed1a Fix hook tests 2025-02-08 11:51:38 +00:00
Alex Orlenko 74b4601b48 Rework Lua hooks:
- Support global hooks inherited by new threads
- Support thread hooks, where each thread can have its own hook

This should also allow to enable hooks for async calls.
Related to #489 #347
2025-02-07 22:42:17 +00:00
Joel Natividad a89800b949 Typos config and ci (#523)
* typos configuration file
* typos CI
2025-02-05 19:02:48 +00:00
Alex Orlenko 2b6172ef38 Fix tests 2025-02-05 19:01:07 +00:00
Joel Natividad 20f7ce097d Fix typos (#522)
* fix various typos in the codebase

* fix typos in CHANGELOG.md
2025-02-05 16:16:20 +00:00
Andrew Farkas 6882e0434e Fix version number in changelog (#521) 2025-02-04 07:33:33 +00:00
Alex Orlenko fca38a6637 Imporove BorrowedStr/BorrowedBytes ergonomic.
Implement `FromLua` and `IntoLua` for these types to allow working with them directly.
2025-01-30 23:07:02 +00:00
Alex Orlenko cdd6a99136 Support Thread::resume_error call (Luau)
This method uses Luau-specific C API extension `lua_resumerror`
that allow to throw an error immediately when resuming a thead.
Closes #500 #513
2025-01-28 22:57:26 +00:00
Alex Orlenko 21d39a069d Pass &Scope instead of &mut Scope to Lua::scope closure.
It was a leftover from an experimental implementation that has been removed.
2025-01-28 18:23:18 +00:00
Alex Orlenko 58965c6255 Move lifetime from AsChunk<'a> to AsChunk::source 2025-01-28 18:17:29 +00:00
Alex Orlenko 8574682ffc Improve From/Into Lua char conversion 2025-01-28 10:41:10 +00:00
tk 47bc372096 impl FromLua/IntoLua for char (#516) 2025-01-28 10:10:28 +00:00
Alex Orlenko d376cb9403 Enable Thread::reset() for all Lua versions 2025-01-28 10:08:16 +00:00
Alex Orlenko ea5ecccf02 Change AsyncThread<A, R> to AsyncThread<R>.
Push arguments in `Thread::into_async()` to the thread during the call instead of first poll.
The pushed arguments will be automatically used on resume.
Fixes #508 and relates to #500.
2025-01-28 10:08:08 +00:00
Alex Orlenko cf71edc492 Update README (set main branch for development) 2025-01-28 10:07:41 +00:00
Alex Orlenko 9caf3542d9 v0.10.3 2025-01-27 21:33:51 +00:00
Alex Orlenko cb45db05fa Update README/CHANGELOG 2025-01-27 21:33:24 +00:00
Alex Orlenko d1cb2a9a96 mlua-sys: v0.6.7 2025-01-27 21:20:47 +00:00
Alex Orlenko aa3f6ba46c Fix prototype of new Luau compiler options and methods 2025-01-27 21:19:31 +00:00
Alex Orlenko cc57bed4c8 Update Luau to 0.657 2025-01-27 14:19:40 +00:00
Radiant b5d38ab2e3 Set Default for LuaValue to be nil. (#512) 2025-01-19 23:52:58 +00:00
Alex Orlenko cd4091f64d Allow exhaustive match on Value.
It was not possible because `ValueRef` variant in `Value::Other` was private.
Closes #502 and #503
2024-12-10 23:37:19 +00:00
Alex Orlenko 91e069a77e Optimize (and simplify) protected mode for Rust function calls 2024-12-07 00:04:12 +00:00
Alex Orlenko cacd3dc70f Add Table::set_safeenv method (Luau) 2024-12-04 10:40:49 +00:00
Alex Orlenko 6f6cda0099 v0.10.2 2024-12-01 13:04:06 +00:00
Alex Orlenko d51ce86142 mlua-sys: v0.6.6 2024-12-01 12:56:39 +00:00
Alex Orlenko aa061bce6f mlua_derive: v0.10.1 2024-12-01 12:55:50 +00:00
Alex Orlenko 1c6b6ad801 Fix tests 2024-12-01 12:54:35 +00:00
Evie 031854fa2a Switch proc-macro-error to proc-macro-error2 (#493) 2024-11-30 22:37:40 +00:00
Alex Orlenko 5fd96c7908 Don't run GC finalizers on ref thread.
If this happen then we don't have full access to the ref thread stack.
Fixes #491
2024-11-30 01:00:31 +00:00
Alex Orlenko 7a3f19b857 Ensure that buffer with Luau compiled code is always freed 2024-11-29 13:36:51 +00:00
Alex Orlenko 55a5d7ef10 Protect Lua(u) during chunk loading if memory limit is enforced
Relates to #488
2024-11-27 00:34:21 +00:00
Alex Orlenko af31dbd180 Use c string literal in few places 2024-11-25 22:53:59 +00:00
Alex Orlenko 9ae3cb0a7c Add doc about possible chunk name prefixes 2024-11-24 12:00:06 +00:00
Alex Orlenko fc1c80c142 Mark Chunk::wrap as non-public 2024-11-22 20:03:17 +00:00
Alex Orlenko ee7ced6334 Add Chunk::wrap method 2024-11-22 14:40:52 +00:00
Alex Orlenko d8307d0e4c Reduce visibility of Wrapped* structs 2024-11-22 13:08:57 +00:00
Alex Orlenko bf9fcc5aca Simplify WrappedString 2024-11-22 13:06:50 +00:00
Alex Orlenko 7ce6b97da9 Add String::wrap method to wrap arbitrary AsRef<[u8]> 2024-11-22 11:48:20 +00:00
Alex Orlenko 4891a6ac10 Fix utf-8 test when bstr v1.11 switchted to LowerHex 2024-11-17 18:01:05 +00:00
Alex Orlenko 4ef0d583fc impl Send for UserDataTypeId (in send mode) 2024-11-16 01:56:18 +00:00
cos 30b0122f5d Make build script work with OpenBSD
On OpenBSD the pkg-config names takes a minimalist format. No dash, and
no separator between the major and minor version number. Build script is
thus adapted to have also these in their set of alt_probes.

Signed-off-by: Alex Orlenko <zxteam@protonmail.com>
2024-11-16 01:52:43 +00:00
cos c31c72076f Make build script work with FreeBSD
On FreeBSD the pkg-config names takes the format with a dash, rather
than without (e.g. lua-5.4, not lua5.4). Thus adapt build script to
iterate over an array of alt_probes until finding a match.

Signed-off-by: Alex Orlenko <zxteam@protonmail.com>
2024-11-16 01:49:02 +00:00
Alex Orlenko 3bfaee4ecc Delay "any" userdata metatable creation until first instance 2024-11-16 01:47:04 +00:00
Alex Orlenko 89b68e2a24 Remove generic from RawLua::push_userdata_metatable.
This should help to reduce amount of generated code.
2024-11-16 01:44:17 +00:00
Alex Orlenko cbf805f492 Avoid ptr->usize->ptr conversion to comply strict provenance 2024-11-15 21:47:19 +00:00
Alex Orlenko c926327a6a v0.10.1 2024-11-09 20:06:04 +00:00
Alex Orlenko 7c099500d0 mlua-sys: v0.6.5 2024-11-09 14:30:33 +00:00
Alex Orlenko 8c889cc353 Add String::display method 2024-11-09 14:24:43 +00:00
Alex Orlenko 958abd050e Update String::to_string_lossy doc 2024-11-09 14:10:45 +00:00
Alex Orlenko 92a8203e1c Fix formatting 2024-11-09 13:58:06 +00:00
Alex Orlenko b34b90eca3 Fix wrong formatting table with string keys that are numbers 2024-11-09 13:51:55 +00:00
Alex Orlenko 7aad0adcb4 Update links to luau.org 2024-11-09 12:48:15 +00:00
Alex Orlenko a3cd25db7a Support Luau 0.650 native vector library 2024-11-09 12:44:00 +00:00
Alex Orlenko a4bfeb7752 clippy 2024-11-09 12:38:05 +00:00
vhyrro 0fda512938 feat(table): improve pretty-printing for simple tables and lists (#478) 2024-11-09 12:08:49 +00:00
Alex Orlenko 58e0661086 Merge Scope::attach_destructor into Scope::seal_userdata 2024-11-08 15:04:46 +00:00
Alex Orlenko c7094d470f Add Scope::create_any_userdata to create Lua objects from any non-static Rust types. 2024-11-07 19:44:18 +00:00
Alex Orlenko a7d0691e10 Add AnyUserData::destroy method 2024-11-07 16:12:20 +00:00
Alex Orlenko 05778fbe6f Don't store and use wrong main Lua state in module mode (Lua 5.1/JIT only).
When mlua module is loaded from a non-main coroutine we store a reference to it to use later.
If the coroutine is destroyed by GC we can pass a wrong pointer to Lua that will trigger a segfault.
Instead, set main_state as Option and use current (active) state if needed.
Relates to #479
2024-11-04 15:48:22 +00:00
Alex Orlenko b34d67ec41 Update Luau to 0.650 (luau0-src 0.11.1) 2024-11-03 14:49:12 +00:00
Alex Orlenko 46ee7ea772 Update tarpaulin.toml 2024-11-03 12:54:21 +00:00
Alex Orlenko 15738dda1f Update tarpaulin.toml to include userdata-wrappers 2024-11-03 12:19:22 +00:00
Alex Orlenko 1f32754f05 Relax UserDataBorrowRef restrictions to allow recursive calls 2024-11-03 12:18:00 +00:00
Alex Orlenko c2eab173c5 Add userdata-wrappers feature
This feature allow to opt into `impl UserData` for `Rc<T>`/`Arc<T>`/`Rc<RefCell<T>>`/`Arc<Mutex<T>>` where `T: UserData`
Close #470
2024-11-03 11:48:47 +00:00
Alex Orlenko bb311349ec Switch between shared and exclusive lock for UserDataRef depending if T: Sync or not. 2024-11-02 10:38:25 +00:00
Alex Orlenko 928e1d9221 Revert &Scope to &mut Scope 2024-10-31 18:41:47 +00:00
Alex Orlenko 5b8681dcf2 Add Scope::add_destructor to attach custom destructors 2024-10-31 14:35:21 +00:00
Alex Orlenko 4e9a17707b Fix tests 2024-10-31 09:23:03 +00:00
Alex Orlenko 6066089cc1 Skip setting Send/Sync in non-send mode for UserDataCell 2024-10-30 23:32:13 +00:00
Alex Orlenko a8d5f23818 Add Lua::try_app_data_ref and Lua::try_app_data_mut 2024-10-30 15:22:55 +00:00
Alex Orlenko 5ec4e0338a Add From<Vec> and Into<Vec> support to MultiValue and Variadic types 2024-10-30 13:03:08 +00:00
Alex Orlenko d27d1365b5 Update v0.10 release notes (add breaking changes) 2024-10-30 00:42:31 +00:00
Alex Orlenko 76b896edcc Fix attaching __gc metamethod
Bug introdused in ddebf56
2024-10-29 22:56:29 +00:00
Alex Orlenko ddebf56b41 Defer metatable return on userdata creation until the end
Relates to #477
2024-10-29 21:22:34 +00:00
Alex Orlenko 4f56575e05 v0.10.0 2024-10-25 13:36:55 +02:00
Alex Orlenko 35fa76263e Update docs 2024-10-25 11:05:21 +02:00
Alex Orlenko 446d63a77e More tests 2024-10-23 15:50:58 +01:00
Alex Orlenko 8d8d521721 Add error-send feature flag 2024-10-23 10:40:25 +01:00
Alex Orlenko 0d31a1caa6 Rename Error::MemoryLimitNotAvailable to Error::MemoryControlNotAvailable 2024-10-22 22:44:52 +01:00
Alex Orlenko 3dc58cdfc9 Move Luau Vector type to top level 2024-10-22 22:41:26 +01:00
Alex Orlenko 5724b5f112 cargo fmt 2024-10-20 13:23:57 +01:00
Alex Orlenko d64d9719c6 Replace Either enum with implementation from either crate 2024-10-20 12:06:08 +01:00
Alex Orlenko 75475fc9a8 Add missing serde::{de, ser} top level comment 2024-10-20 11:52:13 +01:00
Alex Orlenko f8fe9246bb Bump TARGET_MLUA_LUAU_ABI_VERSION 2024-10-20 10:42:59 +01:00
Alex Orlenko 5c54361236 Keep stack in FromLuaMulti::from_stack_multi 2024-10-20 00:47:20 +01:00
Alex Orlenko ec227f9056 Optimize Table readonly check (Luau)
Optimize `Table::has_metatable` check.
2024-10-19 23:58:57 +01:00
Alex Orlenko 93a1a55aaa Update docs 2024-10-19 23:10:43 +01:00
Alex Orlenko a020b2b5b2 Remove functions deprecated in v0.9 2024-10-19 15:14:09 +01:00
Alex Orlenko 2c756e5958 Add back Lua::load_from_std_lib (with deprecated flag) 2024-10-19 15:13:42 +01:00
Alex Orlenko c702077028 More Lua values conversion tests 2024-10-19 15:08:51 +01:00
Alex Orlenko 08545224f4 clippy 2024-10-19 11:49:40 +01:00
Alex Orlenko e122f90837 More Either tests 2024-10-19 11:20:25 +01:00
Alex Orlenko c638d90b02 Fix test_inspect_stack 2024-10-19 00:11:05 +01:00
Alex Orlenko 930fd9c00f Fix Value::String::to_pointer for Lua < 5.4 2024-10-18 23:12:04 +01:00
Alex Orlenko 2a8db87132 Update Value tests 2024-10-18 22:50:48 +01:00
Alex Orlenko cbae4fe59c More async tests 2024-10-18 22:50:15 +01:00
Alex Orlenko c68e3c4f41 Some DebugStack improvements 2024-10-18 22:48:41 +01:00
Alex Orlenko 02d4ceff34 Make Thread::state non-const (private api) 2024-10-18 21:50:57 +01:00
Alex Orlenko 98339c57e6 Update userdata tests 2024-10-18 21:38:02 +01:00
Alex Orlenko 2331995e28 Update coverage ci options 2024-10-18 21:36:49 +01:00
Alex Orlenko c07bdce250 More scope tests 2024-10-17 17:03:31 +01:00
Alex Orlenko 084a85c3d8 Update error tests 2024-10-16 23:57:48 +01:00
Alex Orlenko 735aa22be9 Fix typo in chunk tests 2024-10-16 23:57:07 +01:00
Alex Orlenko 5479546b27 Update chunk tests 2024-10-16 21:54:22 +01:00
Alex Orlenko 179c54f297 Remove generic from Table::equals and Value::equals 2024-10-16 16:11:48 +01:00
Alex Orlenko f9ae4bf05f Update table tests 2024-10-16 16:00:01 +01:00
Alex Orlenko 9e16e18132 Update string tests 2024-10-16 15:59:23 +01:00
Alex Orlenko 7535a23fa2 Update function tests 2024-10-16 15:59:13 +01:00
Alex Orlenko 3787ff9e8c Optimize metatable pointer lookup for userdata (Luau) 2024-10-12 21:38:07 +01:00
Alex Orlenko 0a2a70c15a mlua-sys: v0.6.4 2024-10-12 21:32:08 +01:00
Alex Orlenko 81d7c81532 Update Luau to 0.647 2024-10-12 21:30:56 +01:00
Alex Orlenko 0453029765 v0.10.0-rc.1 2024-10-08 23:00:04 +01:00
Alex Orlenko 7b777d074e Update README 2024-10-08 22:57:32 +01:00
Alex Orlenko c6cd1c53c3 Update CHANGELOG 2024-10-08 22:57:18 +01:00
Alex Orlenko 669349d704 mlua_derive: v0.10.0-rc.1 2024-10-08 22:40:07 +01:00
Alex Orlenko c086c144d0 Use impl IntoIterator in Lua::create_table_from/create_sequence_from 2024-10-08 11:53:31 +01:00
Alex Orlenko 640cb2c182 Add _unguarded to RawLua::app_data_ref (for internal use only) 2024-10-07 13:37:41 +01:00
Alex Orlenko 03a4068d55 Move MultiValue from value to multi module 2024-10-07 13:37:39 +01:00
Alex Orlenko 9f6c78532f Move IntoLua/FromLua and IntoLuaMulti/FromLuaMulti to traits module 2024-10-07 10:32:50 +01:00
Alex Orlenko 8aecc83f53 clippy 2024-10-06 22:48:58 +01:00
Alex Orlenko 4891b6535c Remove const from Value::type_name 2024-10-06 22:45:56 +01:00
Alex Orlenko fa343c2c69 More optimal OsStr/Path conversion to Lua 2024-10-06 22:45:36 +01:00
psentee 6d5e735bed Add IntoLua/FromLua for OsString/OsStr and PathBuf/Path (#459) 2024-10-06 13:43:20 +01:00
Alex Orlenko ac315fd80b Add AnyUserData::wrap_ser function 2024-10-06 11:12:44 +01:00
Alex Orlenko f95161c6e0 Add missing documentation for Function::wrap_raw* functions 2024-10-05 23:26:03 +01:00
Alex Orlenko 4bc846a119 Add optional anyhow dependency (under the same feature flag) to implement IntoLua for anyhow::Error 2024-10-05 23:16:36 +01:00
Alex Orlenko a3ca95fc8f Include Value::Other variant into Value::to_pointer() helper.
Closes #465
2024-10-04 10:33:56 +01:00
Alex Orlenko ae4897ab2e Add Value::is_error and Value::as_error helpers 2024-10-02 12:59:04 +01:00
Alex Orlenko 4ac87c7208 Derive PartialEq instead of implementing manually 2024-10-02 12:21:04 +01:00
Alex Orlenko 04d8106676 Remove SubtypeId from AnyUserData and instead add Value::Other variant that will cover any unknown types (eg. LuaJIT CData) 2024-10-02 12:16:48 +01:00
Alex Orlenko b6cdf32f16 Update MSRV in README 2024-10-01 23:21:28 +01:00
Alex Orlenko ad9bc36764 impl Eq/Ord for Lua String 2024-10-01 23:20:19 +01:00
Alex Orlenko 4b8c26e682 Invoke __tostring metamethod when calling Value::to_string() for Buffer type 2024-10-01 22:59:19 +01:00
Alex Orlenko 7839c4438c Fix compilation warnings 2024-10-01 22:28:47 +01:00
Alex Orlenko 529361fcbc Add new Buffer type for Luau.
Previously it was represented as `AnyUserData` which is not always convenient.
2024-10-01 15:21:19 +01:00
Alex Orlenko 4dddf3c18d Use fmt::Debug implementation for Lua string from bstr 2024-09-26 22:46:02 +01:00
Alex Orlenko fb0c0d9ee9 Add Either<L, R> enum to combine two types into a single one.
It implements `FromLua` and `IntoLua` traits for easy type conversions..
2024-09-26 18:58:28 +01:00
Alex Orlenko 235c32006c Rename Lua::with_raw_state to Lua::exec_raw 2024-09-24 23:11:16 +01:00
Alex Orlenko b65901e444 Add Error::chain method to return iterator over nested errors 2024-09-24 22:48:19 +01:00
Alex Orlenko 91fe02da45 Add LuaNativeFn/LuaNativeFnMut/LuaNativeAsyncFn traits for using in Function::wrap 2024-09-24 14:35:03 +01:00
Alex Orlenko 8274b5fa88 More user-friendly error message on userdata mismatch 2024-09-23 15:56:29 +01:00
Alex Orlenko 762e677a70 Update Error matching code
This is mostly cosmetic change.
2024-09-23 15:53:08 +01:00
Alex Orlenko 5b5f1e4669 Remove undocumented Lua::push in favour of Lua::with_raw_state 2024-09-23 11:14:24 +01:00
Alex Orlenko e582e7c57f Rename get_metatable to metatable for Table/AnyUserData types 2024-09-23 11:13:16 +01:00
Alex Orlenko 3714da5ec8 Fix doc test for Lua::set_type_metatable 2024-09-23 11:01:32 +01:00
Alex Orlenko ca69be07ff Support setting metatable for Lua builtin types.
Closes #445
2024-09-23 10:45:57 +01:00
Alex Orlenko 16951e3628 Move ValueRef to a new module 2024-09-22 23:58:53 +01:00
Alex Orlenko 8bb2b444ab Run tests with forced memory limit checks 2024-09-22 23:20:18 +01:00
Alex Orlenko fc1570d2d7 Support yielding from hooks for Lua 5.3+ 2024-09-22 19:04:32 +01:00
Alex Orlenko fce85381c6 Fix clippy warnings 2024-09-22 19:02:16 +01:00
Alex Orlenko 3088516851 Update luaL_checkstack messages 2024-09-22 17:33:28 +01:00
Alex Orlenko 5162a0f46e Add Lua::with_raw_state to provide easy low-level access to the Lua state. 2024-09-22 11:25:38 +01:00
Alex Orlenko 640d27697d Fix compile error in non-send mode 2024-09-20 21:20:52 +01:00
Alex Orlenko da4404baa5 Add Lua::scope back 2024-09-20 13:01:55 +01:00
Alex Orlenko 7c2e9b5a7c v0.10.0-beta.2 2024-09-07 23:47:21 +01:00
Alex Orlenko 5db545e7b4 mlua_derive: v0.10.0-beta.1 2024-09-07 23:46:13 +01:00
Alex Orlenko b88228b3d4 Update CHANGELOG 2024-09-07 23:43:20 +01:00
Alex Orlenko 8677b57847 Update README 2024-09-07 23:27:35 +01:00
Alex Orlenko 7543b0674e mlua-sys: v0.6.3 2024-09-07 23:23:47 +01:00
Alex Orlenko 8e111058c3 Make BorrowedBytes/BorrowedStr: Send + Sync
They are immutable and don't require holding a lock
2024-09-07 23:17:25 +01:00
Alex Orlenko e1c0aa8491 Fix test test_integer_from_lua 2024-09-07 17:01:04 +01:00
Alex Orlenko 7957c6868d Fastpath for LuaString/integer/float conversion from Lua 2024-09-07 12:25:35 +01:00
Caleb Maclennan 9c86eefb76 Update documentation of traits to match the expected argument name (#447) 2024-09-06 19:28:40 +01:00
Alex Orlenko 7272e40c23 Faster non-scoped callbacks 2024-09-03 01:34:55 +01:00
Alex Orlenko c6ef393ce9 Turn Lua::entrypoint() to constructor (don't require initializing Lua first) 2024-09-02 23:43:52 +01:00
Alex Orlenko d25f2fc07c Take Table instead of impl IntoLua in Chunk::set_environment() 2024-09-01 23:06:27 +01:00
Alex Orlenko 104e242ddd Some cosmetic changes 2024-09-01 22:48:56 +01:00
Alex Orlenko 825bdbfa04 Use dynamic Lua state ownership instead of compile-time module cfg flag to allow
creating new VMs in module mode (and destructing them properly).
2024-08-31 22:52:55 +01:00
Alex Orlenko 1634c43f0a Disable send feature in module mode
We don't have exclusive access to Lua VM and cannot provide `Sync` soundness
2024-08-30 23:37:26 +01:00
Alex Orlenko d6b27de34e Optimize ObjectLike::to_string for tables and userdata 2024-08-30 22:55:53 +01:00
Alex Orlenko 4018a17e26 Combine TableExt and AnyUserDataExt traits into ObjectLike 2024-08-30 22:50:03 +01:00
Alex Orlenko 5ebbc0868c More inline const expressions 2024-08-29 22:05:28 +01:00
Alex Orlenko 3774296835 Run gargabe collection on main Lua instance drop
This should help preventing leaking memory when capturing Lua in async block
and dropping future without finishing polling.
2024-08-29 11:59:52 +01:00
Alex Orlenko ece66c46bf Remove unstable feature flag 2024-08-26 23:51:58 +01:00
Alex Orlenko 66b4a865c2 Remove MultiValue pool 2024-08-26 11:26:12 +01:00
Alex Orlenko 21149106ee Remove drop field from ValueRef 2024-08-26 11:18:17 +01:00
Alex Orlenko 74bebe6da3 Add optional Send requirement to internall callbacks 2024-08-26 11:13:06 +01:00
Alex Orlenko 9891e86d16 Remove Clone requirement from UserDataFields::add_field() 2024-08-26 11:10:34 +01:00
Alex Orlenko e3c5cfdf19 Extract registry_key and vector modules from types 2024-08-26 00:27:41 +01:00
Alex Orlenko 4977b91a98 Detect compilation error and return Result when using Compiler::compile() interface.
Closes #387
2024-08-25 23:07:13 +01:00
Alex Orlenko 6317b8e0c8 Test GC for nested userdata (userdata in userdata) 2024-08-25 21:02:12 +01:00
Alex Orlenko ecc09c4387 Add luaL_loadbufferenv helper to all Lua versions 2024-08-25 17:29:58 +01:00
bjcscat 7bfd32750d Change chunk env to use luau's load env parameter (#442) 2024-08-25 16:50:41 +01:00
Alex Orlenko 23d4e2519b Switch to Mutex from RwLock for userdata access in send mode.
Unfortunately RwLock allow access to the userdata from multiple threads
without enforcing `Sync` marker.
2024-08-24 09:44:39 +01:00
Alex Orlenko 2857cb76c6 Add A param to AsyncThread<A, R>.
This reduces internal dependency on `MultiValue` container and delay args conversion to the future `poll()` stage.
2024-08-23 01:04:06 +01:00
Alex Orlenko fdc50bffc9 Fix memory leak when polling async futures 2024-08-23 01:03:54 +01:00
Alex Orlenko 9931709ecd Remove explicit lifetime from UserDataMethods and UserDataFields traits.
Pass `'static` arguments to async functions and require `'static` Future.
(in future we can use async closures to make it more elegant).
2024-08-23 00:10:29 +01:00
Alex Orlenko 8092f00930 Do not require Lua to be alive when dropping AsyncThread 2024-08-22 23:41:51 +01:00
Alex Orlenko d2e87943ac Skip extra lock when resuming thread 2024-08-22 23:41:50 +01:00
Sven Niederberger 26b9bdb362 serde_userdata: Remove map_err to reduce compile time impact (#441) 2024-08-21 12:06:49 +01:00
Alex Orlenko c58f67b140 Add MaybeSend requirement to Lua futures 2024-08-10 17:55:39 +01:00
Sven Niederberger 0c08cdaf7c Reduce compile time contribution of next_key_seed and next_value_seed (#436)
* factor out common code

* changelog entry
2024-08-06 20:04:17 +01:00
Alex Orlenko 10999babe0 Replace MultiValue::extend_from_values with from_lua_iter 2024-08-06 01:32:20 +01:00
Alex Orlenko f0a995a357 Make MultiValue::with_capacity public 2024-08-05 23:36:17 +01:00
Alex Orlenko aa47324ee9 Use pool for MultiValue container 2024-08-05 22:07:19 +01:00
Alex Orlenko ac6a391426 Remove Lua::into_static and Lua::from_static (undocumented). 2024-08-05 14:57:15 +01:00
Alex Orlenko 8e14b6e40b Fix tests 2024-08-04 22:22:02 +01:00
Alex Orlenko c117a4c1af Fix loading (fetching) stdlib modules when using require in Luau.
Fixes #435.
2024-08-04 18:55:17 +01:00
Alex Orlenko 4082b354fe clippy 2024-08-01 00:55:10 +01:00
Alex Orlenko 3641c98959 Prepare for Rust 2024 edition (see rust-lang/rust#123748)
Replace `IntoLua(Multi)` generic with positional arg (impl trait) where possible
This allow to shorten syntax from `a.get::<_, T>` to `a.get::<T>`
2024-07-31 23:42:43 +01:00
Alex Orlenko b7d170ab9b Refactor ThreadStatus:
- Add `ThreadStatus::Running`
- Replace `ThreadStatus::Unresumable` with `ThreadStatus::Finished`
Change `Error::CoroutineInactive` to `Error::CoroutineUnresumable`
2024-07-31 22:34:45 +01:00
Alex Orlenko 5acf9d758d Update CHANGELOG for v0.10.0-beta.1 2024-07-31 13:42:39 +01:00
Alex Orlenko 833790967b Update async examples (use send feature) 2024-07-31 13:42:39 +01:00
Alex Orlenko a86d6ab330 Mark LightUserData as Send+Sync (send feature flag) 2024-07-31 13:42:39 +01:00
Alex Orlenko 2f8755dcc7 Update README 2024-07-31 13:42:38 +01:00
Alex Orlenko 94415065c0 Fix Lua String soundness when borrowing &str or &[u8].
Make borrowing using new `BorrowedStr` and `BorrowedBytes` types that holds strong reference to Lua.
2024-07-31 13:42:38 +01:00
Alex Orlenko bba644e83f Fix compilation 2024-07-31 13:42:38 +01:00
Alex Orlenko d5173380e3 Split util module
Refactor internal userdata types to switch to the new `TypeKey` trait
2024-07-31 13:42:38 +01:00
Alex Orlenko d9941ef409 Take &mut RegistryKey in Lua::replace_registry_value. 2024-07-31 13:42:38 +01:00
Alex Orlenko c715aec1f7 Do not consume self in Table::pairs and Table::sequence_values methods. 2024-07-31 13:42:38 +01:00
Alex Orlenko cd3f45f31f Rust nightly rustfmt 2024-07-31 13:42:37 +01:00
Alex Orlenko b4892c228c Add rustfmt.toml 2024-07-31 13:42:37 +01:00
Alex Orlenko 7a75c73052 Move AppData to types::app_data mod 2024-07-31 13:42:37 +01:00
Alex Orlenko 658f2a13ea Support multi threads under send feature flag 2024-07-31 13:42:37 +01:00
Alex Orlenko c1395ab543 clippy 2024-07-31 13:42:37 +01:00
Alex Orlenko cd6d86a5ce Split single lua module to multiple submodules under state 2024-07-31 13:42:37 +01:00
Alex Orlenko 8b2d067196 Fix Lua::set_vector_metatable 2024-07-31 13:42:36 +01:00
Alex Orlenko 07a5538e50 Don't run destructors if Lua is gone 2024-07-31 13:42:36 +01:00
Alex Orlenko d17dc54645 Rewrite call_async in traits to use impl Future instead of LocalBoxFuture.
More use `std::future` types instead of `future-utils`.
2024-07-31 13:42:36 +01:00
Alex Orlenko baa8895cfb Optimize creation of userdata callbacks (registry) 2024-07-31 13:42:36 +01:00
Alex Orlenko 550d6b2991 Remove wrapped UserData impl (Rc/Arc/etc) 2024-07-31 13:42:36 +01:00
Alex Orlenko b3649a44e0 Move userdata-related top-level modules into the userdata module 2024-07-31 13:42:36 +01:00
Alex Orlenko 313117095c Remove parking_lot feature flag 2024-07-31 13:42:35 +01:00
Alex Orlenko 7221051683 Refactor:
- Remove the rest of `'lua` lifetimes
- Remove Owned types
2024-07-31 13:42:35 +01:00
Alex Orlenko 1eb2ecb3b0 Drop lifetime from IntoLua 2024-07-31 13:42:35 +01:00
Alex Orlenko 24b0672d99 Rename LuaRef to ValueRef 2024-07-31 13:42:35 +01:00
Alex Orlenko 08c7429531 Optimize various parts of the code to remove unnecessary clone calls.
This is became possible after implementing `IntoLua` for references.
2024-07-31 13:42:35 +01:00
Alex Orlenko aa05eb4c81 Switch MultiValue to use VecDeque under the hood. 2024-07-31 13:42:35 +01:00
Alex Orlenko 4aa178fcc0 Take &Value in Lua::push_value() 2024-07-31 13:42:35 +01:00
Alex Orlenko 98ca880f8a Change Value::Error variant to store Box<Error> instead of Error.
This will help to reduce size of `Value` enum on the stack.
2024-07-31 13:42:34 +01:00
Alex Orlenko fe21ef43ba Replace AtomicPtr with Cell 2024-07-31 13:42:34 +01:00
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
187 changed files with 19129 additions and 13348 deletions
+4 -4
View File
@@ -6,18 +6,18 @@ jobs:
name: coverage
runs-on: ubuntu-latest
container:
image: xd009642/tarpaulin
image: xd009642/tarpaulin:develop-nightly
options: --security-opt seccomp=unconfined
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@main
- name: Generate coverage report
run: |
cargo tarpaulin --out xml --tests --exclude-files benches/* --exclude-files mlua-sys/src/*/*
cargo +nightly tarpaulin --verbose --out xml --tests --exclude-files benches/* --exclude-files mlua-sys/src/*/*
- name: Upload report to codecov.io
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v4
with:
token: ${{secrets.CODECOV_TOKEN}}
fail_ci_if_error: false
+66 -40
View File
@@ -7,18 +7,18 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-22.04, macos-latest, windows-latest]
os: [ubuntu-latest, macos-latest, windows-latest]
rust: [stable]
lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4]
include:
- os: ubuntu-22.04
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
- os: macos-latest
target: x86_64-apple-darwin
- os: windows-latest
target: x86_64-pc-windows-msvc
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@main
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ matrix.rust }}
@@ -27,10 +27,11 @@ jobs:
- name: Build ${{ matrix.lua }} vendored
run: |
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,serde,macros,anyhow,userdata-wrappers"
cargo build --features "${{ matrix.lua }},vendored,async,serde,macros,anyhow,userdata-wrappers,send"
shell: bash
- name: Build ${{ matrix.lua }} pkg-config
if: ${{ matrix.os == 'ubuntu-22.04' }}
if: ${{ matrix.os == 'ubuntu-latest' }}
run: |
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
@@ -44,23 +45,23 @@ jobs:
matrix:
lua: [lua54, lua53, lua52, lua51, luajit]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@main
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
target: aarch64-apple-darwin
- 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,serde,macros,anyhow,userdata-wrappers"
build_aarch64_cross_ubuntu:
name: Cross-compile to aarch64-unknown-linux-gnu
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
needs: build
strategy:
matrix:
lua: [lua54, lua53, lua52, lua51, luajit]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@main
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
@@ -71,18 +72,18 @@ jobs:
sudo apt-get install -y --no-install-recommends gcc-aarch64-linux-gnu libc6-dev-arm64-cross
shell: bash
- 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,serde,macros,anyhow,userdata-wrappers"
shell: bash
build_armv7_cross_ubuntu:
name: Cross-compile to armv7-unknown-linux-gnueabihf
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
needs: build
strategy:
matrix:
lua: [lua54, lua53, lua52, lua51]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@main
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
@@ -93,7 +94,7 @@ jobs:
sudo apt-get install -y --no-install-recommends gcc-arm-linux-gnueabihf libc-dev-armhf-cross
shell: bash
- 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,serde,macros,anyhow,userdata-wrappers"
shell: bash
test:
@@ -102,18 +103,18 @@ jobs:
needs: build
strategy:
matrix:
os: [ubuntu-22.04, macos-latest, windows-latest]
os: [ubuntu-latest, macos-latest, windows-latest]
rust: [stable, nightly]
lua: [lua54, lua53, lua52, lua51, luajit, luajit52, luau, luau-jit, luau-vector4]
include:
- os: ubuntu-22.04
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
- os: macos-latest
target: x86_64-apple-darwin
- os: windows-latest
target: x86_64-pc-windows-msvc
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@main
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ matrix.rust }}
@@ -122,14 +123,14 @@ jobs:
- name: Run ${{ matrix.lua }} tests
run: |
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,serialize,macros,parking_lot,unstable"
cargo test --features "${{ matrix.lua }},vendored,async,serde,macros,anyhow,userdata-wrappers"
cargo test --features "${{ matrix.lua }},vendored,async,serde,macros,anyhow,userdata-wrappers,send"
shell: bash
- name: Run compile tests (macos lua54)
if: ${{ matrix.os == 'macos-latest' && matrix.lua == 'lua54' }}
run: |
TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored" -- --ignored
TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot,unstable" -- --ignored
TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored" --tests -- --ignored
TRYBUILD=overwrite cargo test --features "${{ matrix.lua }},vendored,async,send,serde,macros" --tests -- --ignored
shell: bash
test_with_sanitizer:
@@ -138,14 +139,14 @@ jobs:
needs: build
strategy:
matrix:
os: [ubuntu-22.04]
os: [ubuntu-latest]
rust: [nightly]
lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4]
include:
- os: ubuntu-22.04
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@main
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ matrix.rust }}
@@ -153,27 +154,54 @@ jobs:
- uses: Swatinem/rust-cache@v2
- name: Run ${{ matrix.lua }} tests with address sanitizer
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
cargo test --tests --features "${{ matrix.lua }},vendored,async,serde,macros,anyhow" --target x86_64-unknown-linux-gnu -- --skip test_too_many_recursions
cargo test --tests --features "${{ matrix.lua }},vendored,async,serde,macros,anyhow,userdata-wrappers,send" --target x86_64-unknown-linux-gnu -- --skip test_too_many_recursions
shell: bash
env:
RUSTFLAGS: -Z sanitizer=address
test_with_memory_limit:
name: Test with memory limit
runs-on: ${{ matrix.os }}
needs: build
strategy:
matrix:
os: [ubuntu-latest]
rust: [nightly]
lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4]
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
steps:
- uses: actions/checkout@main
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ matrix.rust }}
target: ${{ matrix.target }}
- uses: Swatinem/rust-cache@v2
- name: Run ${{ matrix.lua }} tests with forced memory limit
run: |
cargo test --tests --features "${{ matrix.lua }},vendored,async,send,serde,macros,anyhow,userdata-wrappers"
shell: bash
env:
RUSTFLAGS: --cfg=force_memory_limit
test_modules:
name: Test modules
runs-on: ${{ matrix.os }}
needs: build
strategy:
matrix:
os: [ubuntu-22.04, macos-latest]
os: [ubuntu-latest, macos-latest]
rust: [stable]
lua: [lua54, lua53, lua52, lua51, luajit, luau]
lua: [lua54, lua53, lua52, lua51, luajit]
include:
- os: ubuntu-22.04
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
- os: macos-latest
target: x86_64-apple-darwin
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@main
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ matrix.rust }}
@@ -197,7 +225,7 @@ jobs:
shell: msys2 {0}
steps:
- uses: msys2/setup-msys2@v2
- uses: actions/checkout@v4
- uses: actions/checkout@main
- name: Install Rust & Lua
run: |
pacman -S --noconfirm mingw-w64-x86_64-rust mingw-w64-x86_64-lua mingw-w64-x86_64-luajit mingw-w64-x86_64-pkg-config
@@ -208,13 +236,13 @@ jobs:
test_wasm32_emscripten:
name: Test on wasm32-unknown-emscripten
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
needs: build
strategy:
matrix:
lua: [lua54, lua53, lua52, lua51, luau]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@main
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
@@ -226,28 +254,26 @@ jobs:
- 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"
cargo test --tests --features "${{ matrix.lua }},vendored,async,serde,macros,anyhow,userdata-wrappers"
rustfmt:
name: Rustfmt
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: actions/checkout@main
- uses: dtolnay/rust-toolchain@nightly
with:
toolchain: stable
components: rustfmt
- run: cargo fmt -- --check
clippy:
name: Clippy
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
strategy:
matrix:
lua: [lua54, lua53, lua52, lua51, luajit, luau, luau-jit, luau-vector4]
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@main
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: nightly
@@ -255,4 +281,4 @@ jobs:
- uses: giraffate/clippy-action@v1
with:
reporter: 'github-pr-review'
clippy_flags: --features "${{ matrix.lua }},vendored,async,send,serialize,macros,parking_lot,unstable"
clippy_flags: --features "${{ matrix.lua }},vendored,async,send,serde,macros,anyhow,userdata-wrappers"
+17
View File
@@ -0,0 +1,17 @@
name: Typos Check
on:
pull_request:
workflow_dispatch:
jobs:
run:
name: Spell Check with Typos
runs-on: ubuntu-latest
steps:
- name: Checkout Actions Repository
uses: actions/checkout@v4
- name: Check spelling
uses: crate-ci/typos@master
with:
config: ./typos.toml
+191 -4
View File
@@ -1,3 +1,190 @@
## v0.11.0 (Jul 14, 2025)
Changes since v0.11.0-beta.3
- Allow linking external Lua libraries in a build script (e.g. pluto) using `external` mlua-sys feature flag
- `Lua::inspect_stack` takes a callback with `&Debug` argument, instead of returning `Debug` directly
- Added `Debug::function` method to get function running at a given level
- `Debug::curr_line` is deprecated in favour of `Debug::current_line` that returns `Option<usize>`
- Added `Lua::set_globals` method to replace global environment
- `Table::set_metatable` now returns `Result<()>` (this operation can fail in sandboxed Luau mode)
- `impl ToString` replaced with `Into<StdString>` in `UserData` registration
- `Value::as_str` and `Value::as_string_lossy` methods are deprecated (as they are non-idiomatic)
- Bugfixes and improvements
## v0.11.0-beta.3 (Jun 23, 2025)
- Luau in sandboxed mode has reduced options in `collectgarbage` function (to follow the official doc)
- `Function::deep_clone` now returns `Result<Function>` as this operation can trigger memory errors
- Luau "Require" resolves included Lua files relative to the current directory (#605)
- Fixed bug when finalizing `AsyncThread` on drop (`call_async` methods family)
## v0.11.0-beta.2 (Jun 12, 2025)
- Lua 5.4 updated to 5.4.8
- Terminate Rust `Future` when `AsyncThread` is dropped (without relying on Lua GC)
- Added `loadstring` function to Luau
- Make `AsChunk` trait dyn-friendly
- Luau `Require` trait synced with Luau 0.674
- Luau `Require` trait methods now can return `Error` variant (in `NavigateError` enum)
- Added `__type` to `Error`'s userdata metatable (for `typeof` function)
- `parking_log/send_guard` is moved to `userdata-wrappers` feature flag
- New `serde` feature flag to replace `serialize` (the old one is still available)
## v0.11.0-beta.1 (May 7th, 2025)
- New "require-by-string" for Luau (with `Require` trait and async support)
- Added `Thread::resume_error` support for Luau
- 52 bit integers support for Luau (this is a breaking change)
- New features for Luau compiler (constants, disabled builtins, known members)
- `AsyncThread<A, R>` changed to `AsyncThread<R>` (`A` pushed to stack immediately)
- Lifetime `'a` moved from `AsChunk<'a>` to `AsChunk::source where Self: 'a`
- `Lua::scope` pass `&Scope` instead of `&mut Scope` to closure
- Added global hooks support (Lua 5.1+)
- Added per-thread hooks support (Lua 5.1+)
- `Lua::init_from_ptr` renamed to `Lua::get_or_init_from_ptr` and returns `&Lua`
- `Lua:load_from_function` is deprecated (this is `register_module` now)
- Added `Lua::register_module` and `Lua::preload_module`
## v0.10.4 (May 5th, 2025)
- Luau updated to 0.672
- New serde option `encode_empty_tables_as_array` to serialize empty tables as arrays
- Added `WeakLua` and `Lua::weak()` to create weak references to Lua state
- Trigger abort when Luau userdata destructors are panic (Luau GC does not support it)
- Added `AnyUserData::type_id()` method to get the type id of the userdata
- Added `Chunk::name()`, `Chunk::environment()` and `Chunk::mode()` functions
- Support borrowing underlying wrapped types for `UserDataRef` and `UserDataRefMut` (under `userdata-wrappers` feature)
- Added large (52bit) integers support for Luau
- Enable `serde` for `bstr` if `serialize` feature flag is enabled
- Recursive warnings (Lua 5.4) are no longer allowed
- Implemented `IntoLua`/`FromLua` for `BorrowedString` and `BorrowedBytes`
- Implemented `IntoLua`/`FromLua` for `char`
- Enable `Thread::reset()` for all Lua versions (limited support for 5.1-5.3)
- Bugfixes and improvements
## v0.10.3 (Jan 27th, 2025)
- Set `Default` for `Value` to be `Nil`
- Allow exhaustive match on `Value` (#502)
- Add `Table::set_safeenv` method (Luau)
## v0.10.2 (Dec 1st, 2024)
- Switch proc-macro-error to proc-macro-error2 (#493)
- Do not allow Lua to run GC finalizers on ref thread (#491)
- Fix chunks loading in Luau when memory limit is enforced (#488)
- Added `String::wrap` method to wrap arbitrary `AsRef<[u8]>` into `impl IntoLua`
- Better FreeBSD/OpenBSD support (thanks to cos)
- Delay "any" userdata metatable creation until first instance is created (#482)
- Reduce amount of generated code for `UserData` (less generics)
## v0.10.1 (Nov 9th, 2024)
- Minimal Luau updated to 0.650
- Added Luau native vector library support (this can change behavior if you use `vector` function!)
- Added Lua `String::display` method
- Improved pretty-printing for Lua tables (#478)
- Added `Scope::create_any_userdata` to create Lua objects from any non-`'static` Rust types
- Added `AnyUserData::destroy` method
- New `userdata-wrappers` feature to `impl UserData` for `Rc<T>`/`Arc<T>`/`Rc<RefCell<T>>`/`Arc<Mutex<T>>` (similar to v0.9)
- `UserDataRef` in `send` mode now uses shared lock if `T: Sync` (and exclusive lock otherwise)
- Added `Scope::add_destructor` to attach custom destructors
- Added `Lua::try_app_data_ref` and `Lua::try_app_data_mut` methods
- Added `From<Vec>` and `Into<Vec>` support to `MultiValue` and `Variadic` types
- Bug fixes and improvements (#477 #479)
## v0.10.0 (Oct 25th, 2024)
Changes since v0.10.0-rc.1
- Added `error-send` feature flag (disabled by default) to require `Send + Sync` for `Error`
- Some performance improvements
## v0.10.0-rc.1
- `Lua::scope` is back
- Support yielding from hooks for Lua 5.3+
- Support setting metatable for Lua builtin types (number/string/function/etc)
- Added `LuaNativeFn`/`LuaNativeFnMut`/`LuaNativeAsyncFn` traits for using in `Function::wrap`
- Added `Error::chain` method to return iterator over nested errors
- Added `Lua::exec_raw` helper to execute low-level Lua C API code
- Added `Either<L, R>` enum to combine two types into a single one
- Added a new `Buffer` type for Luau
- Added `Value::is_error` and `Value::as_error` helpers
- Added `Value::Other` variant to represent unknown Lua types (eg LuaJIT CDATA)
- Added (optional) `anyhow` feature to implement `IntoLua` for `anyhow::Error`
- Added `IntoLua`/`FromLua` for `OsString`/`OsStr` and `PathBuf`/`Path`
## v0.10.0-beta.2
- Updated `ThreadStatus` enum to include `Running` and `Finished` variants.
- `Error::CoroutineInactive` renamed to `Error::CoroutineUnresumable`.
- `IntoLua`/`IntoLuaMulti` now uses `impl trait` syntax for args (shorten from `a.get::<_, T>` to `a.get::<T>`).
- Removed undocumented `Lua::into_static`/`from_static` methods.
- Futures now require `Send` bound if `send` feature is enabled.
- Dropped lifetime from `UserDataMethods` and `UserDataFields` traits.
- `Compiler::compile()` now returns `Result` (Luau).
- Removed `Clone` requirement from `UserDataFields::add_field()`.
- `TableExt` and `AnyUserDataExt` traits were combined into `ObjectLike` trait.
- Disabled `send` feature in module mode (since we don't have exclusive access to Lua).
- `Chunk::set_environment()` takes `Table` instead of `IntoLua` type.
- Reduced the compile time contribution of `next_key_seed` and `next_value_seed`.
- Reduced the compile time contribution of `serde_userdata`.
- Performance improvements.
## v0.10.0-beta.1
- Dropped `'lua` lifetime (subtypes now store a weak reference to Lua)
- Removed (experimental) owned types (they no longer needed)
- Make Lua types truly `Send` and `Sync` (when enabling `send` feature flag)
- Removed `UserData` impl for Rc/Arc types ("any" userdata functions can be used instead)
- `Lua::replace_registry_value` takes `&mut RegistryKey`
- `Lua::scope` temporary disabled (will be re-added in the next release)
## 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)
@@ -182,7 +369,7 @@ Other:
## v0.8.0
Changes since 0.7.4
- Roblox Luau support
- Luau support
- Removed C glue
- Added async support to `__index` and `__newindex` metamethods
- Added `Function::info()` to get information about functions (#149).
@@ -232,7 +419,7 @@ Breaking changes:
## v0.8.0-beta.1
- Roblox Luau support
- Luau support
- Refactored ffi module. C glue is no longer required
- Added async support to `__index` and `__newindex` metamethods
@@ -345,7 +532,7 @@ Breaking changes:
- [**Breaking**] Removed `AnyUserData::has_metamethod()`
- Added `Thread::reset()` for luajit/lua54 to recycle threads.
It's possible to attach a new function to a thread (coroutine).
- Added `chunk!` macro support to load chunks of Lua code using the Rust tokenizer and optinally capturing Rust variables.
- Added `chunk!` macro support to load chunks of Lua code using the Rust tokenizer and optionally capturing Rust variables.
- Improved error reporting (`Error`'s `__tostring` method formats full stacktraces). This is useful in the module mode.
## v0.6.0-beta.1
@@ -401,7 +588,7 @@ Breaking changes:
- Lua 5.4 support with `MetaMethod::Close`.
- `lua53` feature is disabled by default. Now preferred Lua version have to be chosen explicitly.
- Provide safety guaraness for Lua state, which means that potenially unsafe operations, like loading C modules (using `require` or `package.loadlib`) are disabled. Equalient for the previous `Lua::new()` function is `Lua::unsafe_new()`.
- Provide safety guarantees for Lua state, which means that potentially unsafe operations, like loading C modules (using `require` or `package.loadlib`) are disabled. Equivalent to the previous `Lua::new()` function is `Lua::unsafe_new()`.
- New `send` feature to require `Send`.
- New `module` feature, that disables linking to Lua Core Libraries. Required for modules.
- Don't allow `'callback` outlive `'lua` in `Lua::create_function()` to fix [the unsoundness](tests/compile/static_callback_args.rs).
+40 -32
View File
@@ -1,22 +1,22 @@
[package]
name = "mlua"
version = "0.9.3" # remember to update mlua_derive
authors = ["Aleksandr Orlenko <zxteam@pm.me>", "kyren <catherine@chucklefish.org>"]
rust-version = "1.71"
version = "0.11.0" # remember to update mlua_derive
authors = ["Aleksandr Orlenko <zxteam@pm.me>", "kyren <catherine@kyju.org>"]
rust-version = "1.79.0"
edition = "2021"
repository = "https://github.com/khvzak/mlua"
repository = "https://github.com/mlua-rs/mlua"
documentation = "https://docs.rs/mlua"
readme = "README.md"
keywords = ["lua", "luajit", "luau", "async", "scripting"]
categories = ["api-bindings", "asynchronous"]
license = "MIT"
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 Luau
with async/await features and support of writing native Lua modules in Rust.
"""
[package.metadata.docs.rs]
features = ["lua54", "vendored", "async", "send", "serialize", "macros", "parking_lot", "unstable"]
features = ["lua54", "vendored", "async", "send", "serde", "macros"]
rustdoc-args = ["--cfg", "docsrs"]
[workspace]
@@ -32,51 +32,59 @@ lua52 = ["ffi/lua52"]
lua51 = ["ffi/lua51"]
luajit = ["ffi/luajit"]
luajit52 = ["luajit", "ffi/luajit52"]
luau = ["ffi/luau", "libloading"]
luau = ["ffi/luau"]
luau-jit = ["luau", "ffi/luau-codegen"]
luau-vector4 = ["luau", "ffi/luau-vector4"]
vendored = ["ffi/vendored"]
module = ["mlua_derive", "ffi/module"]
async = ["futures-util"]
send = []
serialize = ["serde", "erased-serde", "serde-value"]
async = ["dep:futures-util"]
send = ["error-send"]
error-send = []
serde = ["dep:serde", "dep:erased-serde", "dep:serde-value", "bstr/serde"]
macros = ["mlua_derive/macros"]
unstable = []
anyhow = ["dep:anyhow", "error-send"]
userdata-wrappers = ["parking_lot/send_guard"]
# deprecated features
serialize = ["serde"]
[dependencies]
mlua_derive = { version = "=0.9.0", optional = true, path = "mlua_derive" }
bstr = { version = "1.0", features = ["std"], default_features = false }
once_cell = { version = "1.0" }
mlua_derive = { version = "=0.11.0", optional = true, path = "mlua_derive" }
bstr = { version = "1.0", features = ["std"], default-features = false }
either = "1.0"
num-traits = { version = "0.2.14" }
rustc-hash = "1.0"
rustc-hash = "2.0"
futures-util = { version = "0.3", optional = true, default-features = false, features = ["std"] }
serde = { version = "1.0", optional = true }
erased-serde = { version = "0.4", optional = true }
serde-value = { version = "0.7", optional = true }
parking_lot = { version = "0.12", optional = true }
parking_lot = { version = "0.12", features = ["arc_lock"] }
anyhow = { version = "1.0", optional = true }
rustversion = "1.0"
ffi = { package = "mlua-sys", version = "0.5.0", path = "mlua-sys" }
[target.'cfg(unix)'.dependencies]
libloading = { version = "0.8", optional = true }
ffi = { package = "mlua-sys", version = "0.8.0", path = "mlua-sys" }
[dev-dependencies]
trybuild = "1.0"
futures = "0.3.5"
hyper = { version = "0.14", features = ["client", "server"] }
reqwest = { version = "0.11", features = ["json"] }
hyper = { version = "1.2", features = ["full"] }
hyper-util = { version = "0.1.3", features = ["full"] }
http-body-util = "0.1.1"
reqwest = { version = "0.12", features = ["json"] }
tokio = { version = "1.0", features = ["macros", "rt", "time"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_json = { version = "1.0", features = ["arbitrary_precision"] }
maplit = "1.0"
tempfile = "3"
static_assertions = "1.0"
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
criterion = { version = "0.5", features = ["async_tokio"] }
rustyline = "13.0"
criterion = { version = "0.6", features = ["async_tokio"] }
rustyline = "16.0"
tokio = { version = "1.0", features = ["full"] }
[lints.rust]
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(tarpaulin_include)'] }
[[bench]]
name = "benchmark"
harness = false
@@ -85,7 +93,7 @@ required-features = ["async"]
[[bench]]
name = "serde"
harness = false
required-features = ["serialize"]
required-features = ["serde"]
[[example]]
name = "async_http_client"
@@ -93,23 +101,23 @@ required-features = ["async", "macros"]
[[example]]
name = "async_http_reqwest"
required-features = ["async", "serialize", "macros"]
required-features = ["async", "serde", "macros"]
[[example]]
name = "async_http_server"
required-features = ["async", "macros"]
required-features = ["async", "macros", "send"]
[[example]]
name = "async_tcp_server"
required-features = ["async", "macros"]
required-features = ["async", "macros", "send"]
[[example]]
name = "guided_tour"
required-features = ["macros"]
[[example]]
name = "serialize"
required-features = ["serialize"]
name = "serde"
required-features = ["serde"]
[[example]]
name = "userdata"
+83 -69
View File
@@ -1,15 +1,15 @@
# mlua
[![Build Status]][github-actions] [![Latest Version]][crates.io] [![API Documentation]][docs.rs] [![Coverage Status]][codecov.io] ![MSRV]
[Build Status]: https://github.com/khvzak/mlua/workflows/CI/badge.svg
[github-actions]: https://github.com/khvzak/mlua/actions
[Build Status]: https://github.com/mlua-rs/mlua/workflows/CI/badge.svg
[github-actions]: https://github.com/mlua-rs/mlua/actions
[Latest Version]: https://img.shields.io/crates/v/mlua.svg
[crates.io]: https://crates.io/crates/mlua
[API Documentation]: https://docs.rs/mlua/badge.svg
[docs.rs]: https://docs.rs/mlua
[Coverage Status]: https://codecov.io/gh/khvzak/mlua/branch/master/graph/badge.svg?token=99339FS1CG
[codecov.io]: https://codecov.io/gh/khvzak/mlua
[MSRV]: https://img.shields.io/badge/rust-1.71+-brightgreen.svg?&logo=rust
[Coverage Status]: https://codecov.io/gh/mlua-rs/mlua/branch/main/graph/badge.svg?token=99339FS1CG
[codecov.io]: https://codecov.io/gh/mlua-rs/mlua
[MSRV]: https://img.shields.io/badge/rust-1.79+-brightgreen.svg?&logo=rust
[Guided Tour] | [Benchmarks] | [FAQ]
@@ -17,66 +17,62 @@
[Benchmarks]: https://github.com/khvzak/script-bench-rs
[FAQ]: FAQ.md
> **Note**
>
> See v0.9 [release notes](https://github.com/khvzak/mlua/blob/master/docs/release_notes/v0.9.md).
`mlua` is a set of bindings to the [Lua](https://www.lua.org) programming language for Rust with a goal to provide a
_safe_ (as much as possible), high level, easy to use, practical and flexible API.
`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.
Started as an `rlua` fork, `mlua` supports Lua 5.4, 5.3, 5.2, 5.1 (including LuaJIT) and [Luau] and allows writing native Lua modules in Rust as well as using Lua in a standalone mode.
Started as `rlua` fork, `mlua` supports Lua 5.4, 5.3, 5.2, 5.1 (including LuaJIT) and [Roblox Luau] and allows to write native Lua modules in Rust as well as use Lua in a standalone mode.
`mlua` is tested on Windows/macOS/Linux including module mode in [GitHub Actions] on `x86_64` platforms 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/Luau versions excluding JIT.
WebAssembly (WASM) is supported through `wasm32-unknown-emscripten` target for all Lua versions excluding JIT.
[GitHub Actions]: https://github.com/khvzak/mlua/actions
[Roblox Luau]: https://luau-lang.org
[GitHub Actions]: https://github.com/mlua-rs/mlua/actions
[Luau]: https://luau.org
## Usage
### Feature flags
`mlua` uses feature flags to reduce the amount of dependencies, compiled code and allow to choose only required set of features.
`mlua` uses feature flags to reduce the amount of dependencies and compiled code, and allow to choose only required set of features.
Below is a list of the available feature flags. By default `mlua` does not enable any features.
* `lua54`: activate Lua [5.4] support
* `lua53`: activate Lua [5.3] support
* `lua52`: activate Lua [5.2] support
* `lua51`: activate Lua [5.1] support
* `luajit`: activate [LuaJIT] support
* `luajit52`: activate [LuaJIT] support with partial compatibility with Lua 5.2
* `luau`: activate [Luau] support (auto vendored mode)
* `luau-jit`: activate [Luau] support with 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
* `lua54`: enable Lua [5.4] support
* `lua53`: enable Lua [5.3] support
* `lua52`: enable Lua [5.2] support
* `lua51`: enable Lua [5.1] support
* `luajit`: enable [LuaJIT] support
* `luajit52`: enable [LuaJIT] support with partial compatibility with Lua 5.2
* `luau`: enable [Luau] support (auto vendored mode)
* `luau-jit`: enable [Luau] support with JIT backend.
* `luau-vector4`: enable [Luau] support with 4-dimensional vector.
* `vendored`: build static Lua(JIT) libraries from sources during `mlua` compilation using [lua-src] or [luajit-src]
* `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])
* `send`: make `mlua::Lua` transferable across thread boundaries (adds [`Send`] requirement to `mlua::Function` and `mlua::UserData`)
* `serialize`: add serialization and deserialization support to `mlua` types using [serde] framework
* `send`: make `mlua::Lua: Send + Sync` (adds [`Send`] requirement to `mlua::Function` and `mlua::UserData`)
* `error-send`: make `mlua:Error: Send + Sync`
* `serde`: add serialization and deserialization support to `mlua` types using [serde]
* `macros`: enable procedural macros (such as `chunk!`)
* `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.
* `anyhow`: enable `anyhow::Error` conversion into Lua
* `userdata-wrappers`: opt into `impl UserData` for `Rc<T>`/`Arc<T>`/`Rc<RefCell<T>>`/`Arc<Mutex<T>>` where `T: UserData`
[5.4]: https://www.lua.org/manual/5.4/manual.html
[5.3]: https://www.lua.org/manual/5.3/manual.html
[5.2]: https://www.lua.org/manual/5.2/manual.html
[5.1]: https://www.lua.org/manual/5.1/manual.html
[LuaJIT]: https://luajit.org/
[Luau]: https://github.com/Roblox/luau
[lua-src]: https://github.com/khvzak/lua-src-rs
[luajit-src]: https://github.com/khvzak/luajit-src-rs
[Luau]: https://github.com/luau-lang/luau
[lua-src]: https://github.com/mlua-rs/lua-src-rs
[luajit-src]: https://github.com/mlua-rs/luajit-src-rs
[tokio]: https://github.com/tokio-rs/tokio
[async-std]: https://github.com/async-rs/async-std
[`Send`]: https://doc.rust-lang.org/std/marker/trait.Send.html
[serde]: https://github.com/serde-rs/serde
[parking_lot]: https://github.com/Amanieu/parking_lot
### Async/await support
`mlua` supports async/await for all Lua versions including Luau.
This works using Lua [coroutines](https://www.lua.org/manual/5.3/manual.html#2.6) and require running [Thread](https://docs.rs/mlua/latest/mlua/struct.Thread.html) along with enabling `feature = "async"` in `Cargo.toml`.
This works using Lua [coroutines](https://www.lua.org/manual/5.3/manual.html#2.6) and requires running [Thread](https://docs.rs/mlua/latest/mlua/struct.Thread.html) along with enabling `feature = "async"` in `Cargo.toml`.
**Examples**:
- [HTTP Client](examples/async_http_client.rs)
@@ -84,11 +80,25 @@ This works using Lua [coroutines](https://www.lua.org/manual/5.3/manual.html#2.6
- [HTTP Server](examples/async_http_server.rs)
- [TCP Server](examples/async_tcp_server.rs)
### 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).
**shell command examples**:
```shell
# async http client (hyper)
cargo run --example async_http_client --features=lua54,async,macros
[Example](examples/serialize.rs)
# async http client (reqwest)
cargo run --example async_http_reqwest --features=lua54,async,macros,serde
# async http server
cargo run --example async_http_server --features=lua54,async,macros,send
curl -v http://localhost:3000
```
### Serde support
With the `serde` 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 the [`serde::Serialize`] trait implementation for `mlua::Value` (including `UserData` support).
[Example](examples/serde.rs)
[`serde::Serialize`]: https://docs.serde.rs/serde/ser/trait.Serialize.html
[`serde::Deserialize`]: https://docs.serde.rs/serde/de/trait.Deserialize.html
@@ -98,28 +108,28 @@ With `serialize` feature flag enabled, `mlua` allows you to serialize/deserializ
You have to enable one of the features: `lua54`, `lua53`, `lua52`, `lua51`, `luajit(52)` or `luau`, according to 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.
To achieve this, mlua supports `LUA_LIB`, `LUA_LIB_NAME` and `LUA_LINK` environment variables.
By default `mlua` uses `pkg-config` to find Lua includes and libraries for the chosen Lua version.
In most cases it works as desired, although sometimes it may be preferable to use a custom Lua library.
To achieve this, mlua supports the `LUA_LIB`, `LUA_LIB_NAME` and `LUA_LINK` environment variables.
`LUA_LINK` is optional and may be `dylib` (a dynamic library) or `static` (a static library, `.a` archive).
An example how to use them:
An example of how to use them:
``` sh
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
[luajit-src](https://crates.io/crates/luajit-src).
Just enable the `vendored` feature and cargo will automatically build and link specified lua/luajit version. This is the easiest way to get started with `mlua`.
Just enable the `vendored` feature and cargo will automatically build and link the specified Lua/LuaJIT version. This is the easiest way to get started with `mlua`.
### Standalone mode
In a standalone mode `mlua` allows to add to your application scripting support with a gently configured Lua runtime to ensure safety and soundness.
In standalone mode, `mlua` allows adding scripting support to your application with a gently configured Lua runtime to ensure safety and soundness.
Add to `Cargo.toml` :
Add to `Cargo.toml`:
``` toml
[dependencies]
mlua = { version = "0.9.1", features = ["lua54", "vendored"] }
mlua = { version = "0.11", features = ["lua54", "vendored"] }
```
`main.rs`
@@ -143,21 +153,21 @@ fn main() -> LuaResult<()> {
```
### Module mode
In a module mode `mlua` allows to create a compiled Lua module that can be loaded from Lua code using [`require`](https://www.lua.org/manual/5.4/manual.html#pdf-require). In this case `mlua` uses an external Lua runtime which could lead to potential unsafety due to unpredictability of the Lua environment and usage of libraries such as [`debug`](https://www.lua.org/manual/5.4/manual.html#6.10).
In module mode, `mlua` allows creating a compiled Lua module that can be loaded from Lua code using [`require`](https://www.lua.org/manual/5.4/manual.html#pdf-require). In this case `mlua` uses an external Lua runtime which could lead to potential unsafety due to the unpredictability of the Lua environment and usage of libraries such as [`debug`](https://www.lua.org/manual/5.4/manual.html#6.10).
[Example](examples/module)
Add to `Cargo.toml` :
Add to `Cargo.toml`:
``` toml
[lib]
crate-type = ["cdylib"]
[dependencies]
mlua = { version = "0.9.1", features = ["lua54", "module"] }
mlua = { version = "0.11", features = ["lua54", "module"] }
```
`lib.rs` :
`lib.rs`:
``` rust
use mlua::prelude::*;
@@ -184,7 +194,7 @@ $ lua5.4 -e 'require("my_module").hello("world")'
hello, world!
```
On macOS, you need to set additional linker arguments. One option is to compile with `cargo rustc --release -- -C link-arg=-undefined -C link-arg=dynamic_lookup`, the other is to create a `.cargo/config` with the following content:
On macOS, you need to set additional linker arguments. One option is to compile with `cargo rustc --release -- -C link-arg=-undefined -C link-arg=dynamic_lookup`, the other is to create a `.cargo/config.toml` with the following content:
``` toml
[target.x86_64-apple-darwin]
rustflags = [
@@ -200,26 +210,30 @@ rustflags = [
```
On Linux you can build modules normally with `cargo build --release`.
On Windows the target module will be linked with `lua5x.dll` library (depending on your feature flags).
On Windows the target module will be linked with the `lua5x.dll` library (depending on your feature flags).
Your main application should provide this library.
Module builds don't require Lua lib or headers to be installed on the system.
Module builds don't require Lua binaries or headers to be installed on the system.
### 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:
- [`decasify`](https://github.com/alerque/decasify)
- [`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
## Safety
One of the `mlua` goals is to provide *safe* API between Rust and Lua.
Every place where the Lua C API may trigger an error longjmp in any way is protected by `lua_pcall`,
and the user of the library is protected from directly interacting with unsafe things like the Lua stack,
and there is overhead associated with this safety.
One of `mlua`'s goals is to provide a *safe* API between Rust and Lua.
Every place where the Lua C API may trigger an error longjmp is protected by `lua_pcall`,
and the user of the library is protected from directly interacting with unsafe things like the Lua stack.
There is overhead associated with this safety.
Unfortunately, `mlua` does not provide absolute safety even without using `unsafe` .
This library contains a huge amount of unsafe code. There are almost certainly bugs still lurking in this library!
@@ -227,8 +241,8 @@ It is surprisingly, fiendishly difficult to use the Lua C API without the potent
## Panic handling
`mlua` wraps panics that are generated inside Rust callbacks in a regular Lua error. Panics could be
resumed then by returning or propagating the Lua error to Rust code.
`mlua` wraps panics that are generated inside Rust callbacks in a regular Lua error. Panics can then be
resumed by returning or propagating the Lua error to Rust code.
For example:
``` rust
@@ -247,12 +261,12 @@ let _ = lua.load(r#"
unreachable!()
```
Optionally `mlua` can disable Rust panics catching in Lua via `pcall`/`xpcall` and automatically resume
Optionally, `mlua` can disable Rust panic catching in Lua via `pcall`/`xpcall` and automatically resume
them across the Lua API boundary. This is controlled via `LuaOptions` and done by wrapping the Lua `pcall`/`xpcall`
functions on a way to prevent catching errors that are wrapped Rust panics.
functions to prevent catching errors that are wrapped Rust panics.
`mlua` should also be panic safe in another way as well, which is that any `Lua` instances or handles
remains usable after a user generated panic, and such panics should not break internal invariants or
remain usable after a user generated panic, and such panics should not break internal invariants or
leak Lua stack space. This is mostly important to safely use `mlua` types in Drop impls, as you should not be
using panics for general error handling.
@@ -269,12 +283,12 @@ If you encounter them, a bug report would be very welcome:
## Sandboxing
Please check the [Luau Sandboxing] page if you are interested in running untrusted Lua scripts in controlled environment.
Please check the [Luau Sandboxing] page if you are interested in running untrusted Lua scripts in a controlled environment.
`mlua` provides `Lua::sandbox` method for enabling sandbox mode (Luau only).
`mlua` provides the `Lua::sandbox` method for enabling sandbox mode (Luau only).
[Luau Sandboxing]: https://luau-lang.org/sandbox
[Luau Sandboxing]: https://luau.org/sandbox
## License
This project is licensed under the [MIT license](LICENSE)
This project is licensed under the [MIT license](LICENSE).
+187 -163
View File
@@ -1,5 +1,7 @@
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::Duration;
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
use tokio::runtime::Runtime;
use tokio::task;
@@ -10,10 +12,10 @@ fn collect_gc_twice(lua: &Lua) {
lua.gc_collect().unwrap();
}
fn create_table(c: &mut Criterion) {
fn table_create_empty(c: &mut Criterion) {
let lua = Lua::new();
c.bench_function("create [table empty]", |b| {
c.bench_function("table [create empty]", |b| {
b.iter_batched(
|| collect_gc_twice(&lua),
|_| {
@@ -24,35 +26,33 @@ fn create_table(c: &mut Criterion) {
});
}
fn create_array(c: &mut Criterion) {
fn table_create_array(c: &mut Criterion) {
let lua = Lua::new();
c.bench_function("create [array] 10", |b| {
c.bench_function("table [create array]", |b| {
b.iter_batched(
|| collect_gc_twice(&lua),
|_| {
let table = lua.create_table().unwrap();
for i in 1..=10 {
table.set(i, i).unwrap();
}
lua.create_sequence_from(1..=10).unwrap();
},
BatchSize::SmallInput,
);
});
}
fn create_string_table(c: &mut Criterion) {
fn table_create_hash(c: &mut Criterion) {
let lua = Lua::new();
c.bench_function("create [table string] 10", |b| {
c.bench_function("table [create hash]", |b| {
b.iter_batched(
|| collect_gc_twice(&lua),
|_| {
let table = lua.create_table().unwrap();
for &s in &["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"] {
let s = lua.create_string(s).unwrap();
table.set(s.clone(), s).unwrap();
}
lua.create_table_from(
["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]
.into_iter()
.map(|s| (s, s)),
)
.unwrap();
},
BatchSize::SmallInput,
);
@@ -62,21 +62,19 @@ fn create_string_table(c: &mut Criterion) {
fn table_get_set(c: &mut Criterion) {
let lua = Lua::new();
let table = lua.create_table().unwrap();
c.bench_function("table raw_get and raw_set [10]", |b| {
c.bench_function("table [get and set]", |b| {
b.iter_batched(
|| {
collect_gc_twice(&lua);
table.clear().unwrap();
lua.create_table().unwrap()
},
|_| {
for (i, &s) in ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]
.iter()
|table| {
for (i, s) in ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]
.into_iter()
.enumerate()
{
table.raw_set(s, i).unwrap();
assert_eq!(table.raw_get::<_, usize>(s).unwrap(), i);
assert_eq!(table.raw_get::<usize>(s).unwrap(), i);
}
},
BatchSize::SmallInput,
@@ -87,7 +85,7 @@ fn table_get_set(c: &mut Criterion) {
fn table_traversal_pairs(c: &mut Criterion) {
let lua = Lua::new();
c.bench_function("table traversal [pairs]", |b| {
c.bench_function("table [traversal pairs]", |b| {
b.iter_batched(
|| lua.globals(),
|globals| {
@@ -103,7 +101,7 @@ fn table_traversal_pairs(c: &mut Criterion) {
fn table_traversal_for_each(c: &mut Criterion) {
let lua = Lua::new();
c.bench_function("table traversal [for_each]", |b| {
c.bench_function("table [traversal for_each]", |b| {
b.iter_batched(
|| lua.globals(),
|globals| globals.for_each::<String, LuaValue>(|_k, _v| Ok(())),
@@ -117,7 +115,7 @@ fn table_traversal_sequence(c: &mut Criterion) {
let table = lua.create_sequence_from(1..1000).unwrap();
c.bench_function("table traversal [sequence]", |b| {
c.bench_function("table [traversal sequence]", |b| {
b.iter_batched(
|| table.clone(),
|table| {
@@ -130,236 +128,257 @@ fn table_traversal_sequence(c: &mut Criterion) {
});
}
fn create_function(c: &mut Criterion) {
fn function_create(c: &mut Criterion) {
let lua = Lua::new();
c.bench_function("create [function] 10", |b| {
c.bench_function("function [create Rust]", |b| {
b.iter_batched(
|| collect_gc_twice(&lua),
|_| {
for i in 0..10 {
lua.create_function(move |_, ()| Ok(i)).unwrap();
}
lua.create_function(|_, ()| Ok(123)).unwrap();
},
BatchSize::SmallInput,
);
});
}
fn call_lua_function(c: &mut Criterion) {
fn function_call_sum(c: &mut Criterion) {
let lua = Lua::new();
c.bench_function("call Lua function [sum] 3 10", |b| {
b.iter_batched_ref(
|| {
collect_gc_twice(&lua);
lua.load("function(a, b, c) return a + b + c end")
.eval::<LuaFunction>()
.unwrap()
},
|function| {
for i in 0..10 {
let _result: i64 = function.call((i, i + 1, i + 2)).unwrap();
}
},
BatchSize::SmallInput,
);
});
}
fn call_sum_callback(c: &mut Criterion) {
let lua = Lua::new();
let callback = lua
.create_function(|_, (a, b, c): (i64, i64, i64)| Ok(a + b + c))
let sum = lua
.create_function(|_, (a, b, c): (i64, i64, i64)| Ok(a + b - c))
.unwrap();
lua.globals().set("callback", callback).unwrap();
c.bench_function("call Rust callback [sum] 3 10", |b| {
b.iter_batched_ref(
|| {
collect_gc_twice(&lua);
lua.load("function() for i = 1,10 do callback(i, i+1, i+2) end end")
.eval::<LuaFunction>()
.unwrap()
},
|function| {
function.call::<_, ()>(()).unwrap();
c.bench_function("function [call Rust sum]", |b| {
b.iter_batched(
|| collect_gc_twice(&lua),
|_| {
assert_eq!(sum.call::<i64>((10, 20, 30)).unwrap(), 0);
},
BatchSize::SmallInput,
);
});
}
fn call_async_sum_callback(c: &mut Criterion) {
fn function_call_lua_sum(c: &mut Criterion) {
let lua = Lua::new();
let sum = lua
.load("function(a, b, c) return a + b - c end")
.eval::<LuaFunction>()
.unwrap();
c.bench_function("function [call Lua sum]", |b| {
b.iter_batched(
|| collect_gc_twice(&lua),
|_| {
assert_eq!(sum.call::<i64>((10, 20, 30)).unwrap(), 0);
},
BatchSize::SmallInput,
);
});
}
fn function_call_concat(c: &mut Criterion) {
let lua = Lua::new();
let concat = lua
.create_function(|_, (a, b): (LuaString, LuaString)| Ok(format!("{}{}", a.to_str()?, b.to_str()?)))
.unwrap();
let i = AtomicUsize::new(0);
c.bench_function("function [call Rust 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_call_lua_concat(c: &mut Criterion) {
let lua = Lua::new();
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 callback = lua
let sum = lua
.create_async_function(|_, (a, b, c): (i64, i64, i64)| async move {
task::yield_now().await;
Ok(a + b + c)
Ok(a + b - c)
})
.unwrap();
lua.globals().set("callback", callback).unwrap();
c.bench_function("call async Rust callback [sum] 3 10", |b| {
c.bench_function("function [async call Rust sum]", |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();
|| collect_gc_twice(&lua),
|_| async {
assert_eq!(sum.call_async::<i64>((10, 20, 30)).await.unwrap(), 0);
},
BatchSize::SmallInput,
);
});
}
fn call_concat_callback(c: &mut Criterion) {
fn registry_value_create(c: &mut Criterion) {
let lua = Lua::new();
let callback = lua
.create_function(|_, (a, b): (LuaString, LuaString)| {
Ok(format!("{}{}", a.to_str()?, b.to_str()?))
})
.unwrap();
lua.globals().set("callback", callback).unwrap();
lua.gc_stop();
c.bench_function("call Rust callback [concat string] 10", |b| {
b.iter_batched_ref(
|| {
collect_gc_twice(&lua);
lua.load("function() for i = 1,10 do callback('a', tostring(i)) end end")
.eval::<LuaFunction>()
.unwrap()
},
|function| {
function.call::<_, ()>(()).unwrap();
},
c.bench_function("registry value [create]", |b| {
b.iter_batched(
|| collect_gc_twice(&lua),
|_| lua.create_registry_value("hello").unwrap(),
BatchSize::SmallInput,
);
});
}
fn create_registry_values(c: &mut Criterion) {
fn registry_value_get(c: &mut Criterion) {
let lua = Lua::new();
lua.gc_stop();
c.bench_function("create [registry value] 10", |b| {
let value = lua.create_registry_value("hello").unwrap();
c.bench_function("registry value [get]", |b| {
b.iter_batched(
|| collect_gc_twice(&lua),
|_| {
for _ in 0..10 {
lua.create_registry_value(lua.pack(true).unwrap()).unwrap();
}
lua.expire_registry_values();
assert_eq!(lua.registry_value::<LuaString>(&value).unwrap(), "hello");
},
BatchSize::SmallInput,
);
});
}
fn create_userdata(c: &mut Criterion) {
struct UserData(i64);
fn userdata_create(c: &mut Criterion) {
struct UserData(#[allow(unused)] i64);
impl LuaUserData for UserData {}
let lua = Lua::new();
c.bench_function("create [table userdata] 10", |b| {
c.bench_function("userdata [create]", |b| {
b.iter_batched(
|| collect_gc_twice(&lua),
|_| {
let table: LuaTable = lua.create_table().unwrap();
for i in 1..11 {
table.set(i, UserData(i)).unwrap();
}
lua.create_userdata(UserData(123)).unwrap();
},
BatchSize::SmallInput,
);
});
}
fn call_userdata_index(c: &mut Criterion) {
struct UserData(i64);
fn userdata_call_index(c: &mut Criterion) {
struct UserData(#[allow(unused)] i64);
impl LuaUserData for UserData {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_method(LuaMetaMethod::Index, move |_, _, index: String| Ok(index));
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
methods.add_meta_method(LuaMetaMethod::Index, move |_, _, key: LuaString| Ok(key));
}
}
let lua = Lua::new();
lua.globals().set("userdata", UserData(10)).unwrap();
let ud = lua.create_userdata(UserData(123)).unwrap();
let index = lua
.load("function(ud) return ud.test end")
.eval::<LuaFunction>()
.unwrap();
c.bench_function("call [userdata index] 10", |b| {
b.iter_batched_ref(
|| {
collect_gc_twice(&lua);
lua.load("function() for i = 1,10 do local v = userdata.test end end")
.eval::<LuaFunction>()
.unwrap()
},
|function| {
function.call::<_, ()>(()).unwrap();
c.bench_function("userdata [call index]", |b| {
b.iter_batched(
|| collect_gc_twice(&lua),
|_| {
assert_eq!(index.call::<LuaString>(&ud).unwrap(), "test");
},
BatchSize::SmallInput,
);
});
}
fn call_userdata_method(c: &mut Criterion) {
fn userdata_call_method(c: &mut Criterion) {
struct UserData(i64);
impl LuaUserData for UserData {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_method("method", |_, this, ()| Ok(this.0));
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
methods.add_method("add", |_, this, i: i64| Ok(this.0 + i));
}
}
let lua = Lua::new();
lua.globals().set("userdata", UserData(10)).unwrap();
let ud = lua.create_userdata(UserData(123)).unwrap();
let method = lua
.load("function(ud, i) return ud:add(i) end")
.eval::<LuaFunction>()
.unwrap();
let i = AtomicUsize::new(0);
c.bench_function("call [userdata method] 10", |b| {
b.iter_batched_ref(
c.bench_function("userdata [call method]", |b| {
b.iter_batched(
|| {
collect_gc_twice(&lua);
lua.load("function() for i = 1,10 do userdata:method() end end")
.eval::<LuaFunction>()
.unwrap()
i.fetch_add(1, Ordering::Relaxed)
},
|function| {
function.call::<_, ()>(()).unwrap();
|i| {
assert_eq!(method.call::<usize>((&ud, i)).unwrap(), 123 + i);
},
BatchSize::SmallInput,
);
});
}
fn call_async_userdata_method(c: &mut Criterion) {
struct UserData(String);
fn userdata_async_call_method(c: &mut Criterion) {
struct UserData(i64);
impl LuaUserData for UserData {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_async_method("method", |_, this, ()| async move { Ok(this.0.clone()) });
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
methods.add_async_method("add", |_, this, i: i64| async move {
task::yield_now().await;
Ok(this.0 + i)
});
}
}
let options = LuaOptions::new().thread_pool_size(1024);
let lua = Lua::new_with(LuaStdLib::ALL_SAFE, options).unwrap();
lua.globals()
.set("userdata", UserData("hello".to_string()))
let ud = lua.create_userdata(UserData(123)).unwrap();
let method = lua
.load("function(ud, i) return ud:add(i) end")
.eval::<LuaFunction>()
.unwrap();
let i = AtomicUsize::new(0);
c.bench_function("call async [userdata method] 10", |b| {
c.bench_function("userdata [async call method] 10", |b| {
let rt = Runtime::new().unwrap();
b.to_async(rt).iter_batched(
|| {
collect_gc_twice(&lua);
lua.load("function() for i = 1,10 do userdata:method() end end")
.eval::<LuaFunction>()
.unwrap()
(method.clone(), ud.clone(), i.fetch_add(1, Ordering::Relaxed))
},
|function| async move {
function.call_async::<_, ()>(()).await.unwrap();
|(method, ud, i)| async move {
assert_eq!(method.call_async::<usize>((ud, i)).await.unwrap(), 123 + i);
},
BatchSize::SmallInput,
);
@@ -369,27 +388,32 @@ fn call_async_userdata_method(c: &mut Criterion) {
criterion_group! {
name = benches;
config = Criterion::default()
.sample_size(300)
.sample_size(500)
.measurement_time(Duration::from_secs(10))
.noise_threshold(0.02);
targets =
create_table,
create_array,
create_string_table,
table_create_empty,
table_create_array,
table_create_hash,
table_get_set,
table_traversal_pairs,
table_traversal_for_each,
table_traversal_sequence,
create_function,
call_lua_function,
call_sum_callback,
call_async_sum_callback,
call_concat_callback,
create_registry_values,
create_userdata,
call_userdata_index,
call_userdata_method,
call_async_userdata_method,
function_create,
function_call_sum,
function_call_lua_sum,
function_call_concat,
function_call_lua_concat,
function_async_call_sum,
registry_value_create,
registry_value_get,
userdata_create,
userdata_call_index,
userdata_call_method,
userdata_async_call_method,
}
criterion_main!(benches);
+59 -33
View File
@@ -1,6 +1,7 @@
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
use std::time::Duration;
use criterion::{criterion_group, criterion_main, BatchSize, Criterion};
use mlua::prelude::*;
fn collect_gc_twice(lua: &Lua) {
@@ -8,43 +9,67 @@ fn collect_gc_twice(lua: &Lua) {
lua.gc_collect().unwrap();
}
fn serialize_json(c: &mut Criterion) {
fn encode_json(c: &mut Criterion) {
let lua = Lua::new();
lua.globals()
.set(
"encode",
LuaFunction::wrap(|_, t: LuaValue| Ok(serde_json::to_string(&t).unwrap())),
let encode = lua
.create_function(|_, t: LuaValue| Ok(serde_json::to_string(&t).unwrap()))
.unwrap();
let table = lua
.load(
r#"{
name = "Clark Kent",
address = {
city = "Smallville",
state = "Kansas",
country = "USA",
},
age = 22,
parents = {"Jonathan Kent", "Martha Kent"},
superman = true,
interests = {"flying", "saving the world", "kryptonite"},
}"#,
)
.eval::<LuaTable>()
.unwrap();
c.bench_function("serialize table to json [10]", |b| {
c.bench_function("serialize json", |b| {
b.iter_batched(
|| {
collect_gc_twice(&lua);
lua.load(
r#"
local encode = encode
return function()
for i = 1, 10 do
encode({
name = "Clark Kent",
nickname = "Superman",
address = {
city = "Metropolis",
},
age = 32,
superman = true,
})
end
end
"#,
)
.eval::<LuaFunction>()
.unwrap()
|| collect_gc_twice(&lua),
|_| {
encode.call::<LuaString>(&table).unwrap();
},
|func| {
func.call::<_, ()>(()).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,
);
@@ -54,11 +79,12 @@ fn serialize_json(c: &mut Criterion) {
criterion_group! {
name = benches;
config = Criterion::default()
.sample_size(300)
.sample_size(500)
.measurement_time(Duration::from_secs(10))
.noise_threshold(0.02);
targets =
serialize_json,
encode_json,
decode_json,
}
criterion_main!(benches);
+195
View File
@@ -0,0 +1,195 @@
## mlua v0.10 release notes
The v0.10 version of mlua has goal to improve the user experience while keeping the same performance and safety guarantees.
This document highlights the most notable features. For a full list of changes, see the [CHANGELOG].
[CHANGELOG]: https://github.com/mlua-rs/mlua/blob/main/CHANGELOG.md
### New features
#### `'static` Lua types
In previous mlua versions, it was required to have a `'lua` lifetime attached to every Lua value. v0.9 introduced (experimental) owned types that are `'static` without a lifetime attached, but they kept strong references to the Lua instance.
In v0.10 all Lua types are `'static` and have only weak reference to the Lua instance. It means they are more flexible and can be used in more places without worrying about memory leaks.
#### Truly `send` feature
In this version Lua is `Send + Sync` when the `send` feature flag is enabled (previously was only `Send`). It means Lua instance and their values can be safely shared between threads and used in multi threaded async contexts.
```rust
let lua = Lua::new();
lua.globals().set("i", 0)?;
let func = lua.load("i = i + ...").into_function()?;
std::thread::scope(|s| {
s.spawn(|| {
for i in 0..5 {
func.call::<()>(i).unwrap();
}
});
s.spawn(|| {
for i in 0..5 {
func.call::<()>(i).unwrap();
}
});
});
assert_eq!(lua.globals().get::<i32>("i")?, 20);
```
Under the hood, to synchronize access to the Lua state, mlua uses [`ReentrantMutex`] which can be recursively locked by a single thread. Only one thread can execute Lua code at a time, but it's possible to share Lua values between threads.
This has some performance penalties (about 10-20%) compared to the lock free mode. This flag is disabled by default and does not supported in module mode.
[`ReentrantMutex`]: https://docs.rs/parking_lot/latest/parking_lot/type.ReentrantMutex.html
#### Register Rust functions with variable number of arguments
The new traits `LuaNativeFn`/`LuaNativeFnMut`/`LuaNativeAsyncFn` have been introduced to provide a way to register Rust functions with variable number of arguments in Lua, without needing to pass all arguments as a tuple.
They are used by `Function::wrap`/`Function::wrap_mut`/`Function::wrap_async` methods:
```rust
let add = Function::wrap(|a: i64, b: i64| Ok(a + b));
lua.globals().set("add", add).unwrap();
// Prints 50
lua.load(r#"print(add(5, 45))"#).exec().unwrap();
```
To wrap functions that return direct value (non-`Result`) you can use `Function::wrap_raw` method.
#### Setting metatable for Lua builtin types
For Lua builtin types (like `string`, `function`, `number`, etc.) that have a shared metatable for all instances, it's now possible to set a custom metatable for them.
```rust
let mt = lua.create_table()?;
mt.set("__tostring", lua.create_function(|_, b: bool| Ok(if b { "2" } else { "0" }))?)?;
lua.set_type_metatable::<bool>(Some(mt));
lua.load("assert(tostring(true) == '2')").exec().unwrap();
```
### Improvements
#### New `ObjectLike` trait
The `ObjectLike` trait is a combination of the `AnyUserDataExt` and `TableExt` traits used in previous versions. It provides a unified interface for working with Lua tables and userdata.
#### `Either<L, R>` enum
The `Either<L, R>` enum is a simple enum that can hold either `L` or `R` value. It's useful when you need to return or receive one of two types in a function.
This type implements `IntoLua` and `FromLua` traits and can generate a meaningful error message when conversion fails.
```rust
let func = Function::wrap(|x: Either<i32, String>| Ok(format!("received: {x}")));
lua.globals().set("func", func).unwrap();
// Prints: received: 123
lua.load(r#"print(func(123))"#).exec().unwrap();
// Prints: bad argument #1: error converting Lua table to Either<i32, String>
lua.load(r#"print(pcall(func, {}))"#).exec().unwrap();
```
#### `Lua::exec_raw` helper to execute low-level Lua C API code
For advanced users, it's now possible to execute low-level Lua C API code using the `Lua::exec_raw` method.
```rust
let t = lua.create_sequence_from([1, 2, 3, 4, 5])?;
let sum: i64 = unsafe {
lua.exec_raw(&t, |state| {
// top of the stack: table `t`
let mut sum = 0;
// push nil as the first key
mlua::ffi::lua_pushnil(state);
while mlua::ffi::lua_next(state, -2) != 0 {
sum += mlua::ffi::lua_tointeger(state, -1);
// Remove the value, keep the key for the next iteration
mlua::ffi::lua_pop(state, 1);
}
mlua::ffi::lua_pop(state, 1);
mlua::ffi::lua_pushinteger(state, sum);
// top of the stack: sum
})
}?;
assert_eq!(sum, 15);
```
The `exec_raw` method is longjmp-safe. It's not recommended to move `Drop` types into the closure to avoid possible memory leaks.
#### `anyhow` feature flag
The new `anyhow` feature flag adds `IntoLua` and `Into<mlua::Error>` implementation for the `anyhow::Error` type.
```rust
let f = lua.create_function(|_, ()| {
Err(anyhow!("error message"))?;
Ok(())
})?;
```
### Breaking changes
#### Scope changes
The following `Scope` methods were changed:
- Removed `Scope::create_any_userdata`
- `Scope::create_nonstatic_userdata` is renamed to `Scope::create_userdata`
Instead, scope has comprehensive support for borrowed userdata: `create_any_userdata_ref`, `create_any_userdata_ref_mut`, `create_userdata_ref`, `create_userdata_ref_mut`.
`UserDataRef` and `UserDataRefMut` are no longer acceptable for scoped userdata access as they require owned underlying data.
In mlua v0.9 this can cause read-after-free bug in some edge cases.
To temporarily borrow underlying data, the `AnyUserData::borrow_scoped` and `AnyUserData::borrow_mut_scoped` methods were introduced:
```rust
let data = "hello".to_string();
lua.scope(|scope| {
let ud = scope.create_any_userdata_ref(&data)?;
// We can only borrow scoped userdata using this method
ud.borrow_scoped::<String, ()>(|s| {
assert_eq!(s, "hello");
})?;
Ok(())
})?;
```
Those methods work for scoped and regular userdata objects (but still require `T: 'static`).
#### String changes
Since `mlua::String` holds a weak reference to Lua without any guarantees about the lifetime of the underlying data, getting a `&str` or `&[u8]` from it is no longer safe.
Lua instance can be destroyed while reference to the data is still alive:
```rust
let lua = Lua::new();
let s: mlua::String = lua.create_string("hello, world")?; // only weak reference to Lua!
let s_ref: &str = s.to_str()?; // this is not safe!
drop(lua);
println!("{s_ref}"); // use after free!
```
To solve this issue, return types of `mlua::String::to_str` and `mlua::String::as_bytes` methods changed to `BorrowedStr` and `BorrowedBytes` respectively.
These new types hold a strong reference to the Lua instance and can be safely converted to `&str` or `&[u8]`:
```rust
let lua = Lua::new();
let s: mlua::String = lua.create_string("hello, world")?;
let s_ref: mlua::BorrowedStr = s.to_str()?; // The strong reference to Lua is held here
drop(lua);
println!("{s_ref}"); // ok
```
The good news is that `BorrowedStr` implements `Deref<Target = str>`/`AsRef<str>` as well as `Display`, `Debug`, `Eq`, `PartialEq` and other traits for easy usage.
The same applies to `BorrowedBytes`.
Unfortunately, `mlua::String::to_string_lossy` cannot return `Cow<'a, str>` anymore, because it requires a strong reference to Lua. It now returns Rust `String` instead.
+2 -2
View File
@@ -3,7 +3,7 @@
The v0.9 version of mlua is a major release that includes a number of API changes and improvements. This release is a stepping stone towards the v1.0.
This document highlights the most important changes. For a full list of changes, see the [CHANGELOG].
[CHANGELOG]: https://github.com/khvzak/mlua/blob/master/CHANGELOG.md
[CHANGELOG]: https://github.com/mlua-rs/mlua/blob/main/CHANGELOG.md
### New features
@@ -304,7 +304,7 @@ assert_eq!(f.call::<_, mlua::String>(())?, "hello");
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
[benchmarks results]: https://github.com/mlua-rs/script-bench-rs
### Changes in `module` mode
+17 -13
View File
@@ -1,18 +1,22 @@
use std::collections::HashMap;
use hyper::body::{Body as HyperBody, HttpBody as _};
use hyper::Client as HyperClient;
use http_body_util::BodyExt as _;
use hyper::body::Incoming;
use hyper_util::client::legacy::Client as HyperClient;
use hyper_util::rt::TokioExecutor;
use mlua::{chunk, ExternalResult, Lua, Result, UserData, UserDataMethods};
struct BodyReader(HyperBody);
struct BodyReader(Incoming);
impl UserData for BodyReader {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_async_method_mut("read", |lua, reader, ()| async move {
if let Some(bytes) = reader.0.data().await {
let bytes = bytes.into_lua_err()?;
return Some(lua.create_string(&bytes)).transpose();
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
// Every call returns a next chunk
methods.add_async_method_mut("read", |lua, mut reader, ()| async move {
if let Some(bytes) = reader.0.frame().await {
if let Some(bytes) = bytes.into_lua_err()?.data_ref() {
return Some(lua.create_string(&bytes)).transpose();
}
}
Ok(None)
});
@@ -24,7 +28,7 @@ async fn main() -> Result<()> {
let lua = Lua::new();
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().into_lua_err()?;
let resp = client.get(uri).await.into_lua_err()?;
@@ -55,11 +59,11 @@ async fn main() -> Result<()> {
end
end
repeat
local body = res.body:read()
if body then
print(body)
local chunk = res.body:read()
if chunk then
print(chunk)
end
until not body
until not chunk
})
.into_function()?;
+7 -13
View File
@@ -1,11 +1,9 @@
use mlua::{chunk, ExternalResult, Lua, LuaSerdeExt, Result};
use mlua::{chunk, ExternalResult, Lua, LuaSerdeExt, Result, Value};
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<()> {
let lua = Lua::new();
let null = lua.null();
let fetch_json = lua.create_async_function(|lua, uri: String| async move {
let resp = reqwest::get(&uri)
.await
@@ -15,19 +13,15 @@ async fn main() -> Result<()> {
lua.to_value(&json)
})?;
let dbg = lua.create_function(|_, value: Value| {
println!("{value:#?}");
Ok(())
})?;
let f = lua
.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(...)
print_r(res)
$dbg(res)
})
.into_function()?;
+67 -75
View File
@@ -1,60 +1,70 @@
use std::convert::Infallible;
use std::future::Future;
use std::net::SocketAddr;
use std::pin::Pin;
use std::rc::Rc;
use std::task::{Context, Poll};
use hyper::server::conn::AddrStream;
use hyper::service::Service;
use hyper::{Body, Request, Response, Server};
use http_body_util::combinators::BoxBody;
use http_body_util::{BodyExt as _, Empty, Full};
use hyper::body::{Bytes, Incoming};
use hyper::server::conn::http1;
use hyper::{Request, Response};
use hyper_util::rt::TokioIo;
use tokio::net::TcpListener;
use mlua::{
chunk, Error as LuaError, Function, Lua, String as LuaString, Table, UserData, UserDataMethods,
};
use mlua::{chunk, Error as LuaError, Function, Lua, 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 {
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("method", |_lua, req, ()| Ok((req.1).method().to_string()));
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_method("remote_addr", |_, req, ()| Ok((req.0).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 {
handler: Function,
peer_addr: SocketAddr,
}
impl Service<Request<Body>> for Svc {
type Response = Response<Body>;
type Error = LuaError;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
impl Svc {
pub fn new(handler: Function, peer_addr: SocketAddr) -> Self {
Self { handler, peer_addr }
}
}
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 = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
fn call(&self, req: Request<Incoming>) -> Self::Future {
// If handler returns an error then generate 5xx response
let lua = self.0.clone();
let lua_req = LuaRequest(self.1, req);
let handler = self.handler.clone();
let lua_req = LuaRequest(self.peer_addr, req);
Box::pin(async move {
let handler: Function = lua.named_registry_value("http_handler")?;
match handler.call_async::<_, Table>(lua_req).await {
match handler.call_async::<Table>(lua_req).await {
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);
let mut resp = Response::builder().status(status);
// Set headers
if let Some(headers) = lua_resp.get::<_, Option<Table>>("headers")? {
if let Some(headers) = lua_resp.get::<Option<Table>>("headers")? {
for pair in headers.pairs::<String, LuaString>() {
let (h, v) = pair?;
resp = resp.header(&h, v.as_bytes());
resp = resp.header(&h, &*v.as_bytes());
}
}
// Set body
let body = lua_resp
.get::<_, Option<LuaString>>("body")?
.map(|b| Body::from(b.as_bytes().to_vec()))
.unwrap_or_else(Body::empty);
.get::<Option<LuaString>>("body")?
.map(|b| Full::new(Bytes::copy_from_slice(&b.as_bytes())).boxed())
.unwrap_or_else(|| Empty::<Bytes>::new().boxed());
Ok(resp.body(body).unwrap())
}
@@ -62,7 +72,7 @@ impl Service<Request<Body>> for Svc {
eprintln!("{}", err);
Ok(Response::builder()
.status(500)
.body(Body::from("Internal Server Error"))
.body(Full::new(Bytes::from("Internal Server Error")).boxed())
.unwrap())
}
}
@@ -72,65 +82,47 @@ impl Service<Request<Body>> for Svc {
#[tokio::main(flavor = "current_thread")]
async fn main() {
let lua = Rc::new(Lua::new());
let lua = Lua::new();
// Create Lua handler function
let handler: Function = lua
let handler = lua
.load(chunk! {
function(req)
return {
status = 200,
headers = {
["X-Req-Method"] = req:method(),
["X-Req-Path"] = req:path(),
["X-Remote-Addr"] = req:remote_addr(),
},
body = "Hello from Lua!\n"
}
end
})
.eval()
.expect("cannot create Lua handler");
.eval::<Function>()
.expect("Failed to create Lua handler");
// Store it in the Registry
lua.set_named_registry_value("http_handler", handler)
.expect("cannot store Lua handler");
let listen_addr = "127.0.0.1:3000";
let listener = TcpListener::bind(listen_addr).await.unwrap();
println!("Listening on http://{listen_addr}");
let addr = ([127, 0, 0, 1], 3000).into();
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);
// Create `LocalSet` to spawn !Send futures
let local = tokio::task::LocalSet::new();
local.run_until(server).await.expect("cannot run server")
}
struct MakeSvc(Rc<Lua>);
impl Service<&AddrStream> for MakeSvc {
type Response = Svc;
type Error = hyper::Error;
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)) })
}
}
#[derive(Clone, Copy, Debug)]
struct LocalExec;
impl<F> hyper::rt::Executor<F> for LocalExec
where
F: std::future::Future + 'static, // not requiring `Send`
{
fn execute(&self, fut: F) {
tokio::task::spawn_local(fut);
let svc = Svc::new(handler.clone(), peer_addr);
tokio::task::spawn(async move {
if let Err(err) = http1::Builder::new()
.serve_connection(TokioIo::new(stream), svc)
.await
{
eprintln!("Error serving connection: {:?}", err);
}
});
}
}
+12 -31
View File
@@ -1,48 +1,42 @@
use std::io;
use std::net::SocketAddr;
use std::rc::Rc;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::{TcpListener, TcpStream};
use tokio::task;
use mlua::{chunk, Function, Lua, RegistryKey, String as LuaString, UserData, UserDataMethods};
use mlua::{chunk, BString, Function, Lua, UserData, UserDataMethods};
struct LuaTcpStream(TcpStream);
impl UserData for LuaTcpStream {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_method("peer_addr", |_, this, ()| {
Ok(this.0.peer_addr()?.to_string())
});
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_method("peer_addr", |_, this, ()| Ok(this.0.peer_addr()?.to_string()));
methods.add_async_method_mut("read", |lua, this, size| async move {
methods.add_async_method_mut("read", |lua, mut this, size| async move {
let mut buf = vec![0; size];
let n = this.0.read(&mut buf).await?;
buf.truncate(n);
lua.create_string(&buf)
});
methods.add_async_method_mut("write", |_, this, data: LuaString| async move {
let n = this.0.write(&data.as_bytes()).await?;
methods.add_async_method_mut("write", |_, mut this, data: BString| async move {
let n = this.0.write(&data).await?;
Ok(n)
});
methods.add_async_method_mut("close", |_, this, ()| async move {
methods.add_async_method_mut("close", |_, mut this, ()| async move {
this.0.shutdown().await?;
Ok(())
});
}
}
async fn run_server(lua: Lua, handler: RegistryKey) -> io::Result<()> {
async fn run_server(handler: Function) -> io::Result<()> {
let addr: SocketAddr = ([127, 0, 0, 1], 3000).into();
let listener = TcpListener::bind(addr).await.expect("cannot bind addr");
println!("Listening on {}", addr);
let lua = Rc::new(lua);
let handler = Rc::new(handler);
loop {
let (stream, _) = match listener.accept().await {
Ok(res) => res,
@@ -50,15 +44,10 @@ async fn run_server(lua: Lua, handler: RegistryKey) -> io::Result<()> {
Err(err) => return Err(err),
};
let lua = lua.clone();
let handler = handler.clone();
task::spawn_local(async move {
let handler: Function = lua
.registry_value(&handler)
.expect("cannot get Lua handler");
tokio::task::spawn(async move {
let stream = LuaTcpStream(stream);
if let Err(err) = handler.call_async::<_, ()>(stream).await {
if let Err(err) = handler.call_async::<()>(stream).await {
eprintln!("{}", err);
}
});
@@ -70,7 +59,7 @@ async fn main() {
let lua = Lua::new();
// Create Lua handler function
let handler_fn = lua
let handler = lua
.load(chunk! {
function(stream)
local peer_addr = stream:peer_addr()
@@ -92,15 +81,7 @@ async fn main() {
.eval::<Function>()
.expect("cannot create Lua handler");
// Store it in the Registry
let handler = lua
.create_registry_value(handler_fn)
.expect("cannot store Lua handler");
task::LocalSet::new()
.run_until(run_server(lua, handler))
.await
.expect("cannot run server")
run_server(handler).await.expect("cannot run server")
}
fn is_transient_error(e: &io::Error) -> bool {
+17 -22
View File
@@ -1,9 +1,7 @@
use std::f32;
use std::iter::FromIterator;
use mlua::{
chunk, FromLua, Function, Lua, MetaMethod, Result, UserData, UserDataMethods, Value, Variadic,
};
use mlua::{chunk, FromLua, Function, Lua, MetaMethod, Result, UserData, UserDataMethods, Value, Variadic};
fn main() -> Result<()> {
// You can create a new Lua state with `Lua::new()`. This loads the default Lua std library
@@ -19,12 +17,12 @@ fn main() -> Result<()> {
globals.set("string_var", "hello")?;
globals.set("int_var", 42)?;
assert_eq!(globals.get::<_, String>("string_var")?, "hello");
assert_eq!(globals.get::<_, i64>("int_var")?, 42);
assert_eq!(globals.get::<String>("string_var")?, "hello");
assert_eq!(globals.get::<i64>("int_var")?, 42);
// 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
// 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.
lua.load(
@@ -34,7 +32,7 @@ fn main() -> Result<()> {
)
.set_name("example code")
.exec()?;
assert_eq!(globals.get::<_, String>("global")?, "foobar");
assert_eq!(globals.get::<String>("global")?, "foobar");
assert_eq!(lua.load("1 + 1").eval::<i32>()?, 2);
assert_eq!(lua.load("false == false").eval::<bool>()?, true);
@@ -87,20 +85,20 @@ fn main() -> Result<()> {
// You can load Lua functions
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:
print.call::<_, ()>(("hello", "again", "from", "rust"))?;
print.call::<()>(("hello", "again", "from", "rust"))?;
// But, you can also pass variadic arguments with the `Variadic` type.
print.call::<_, ()>(Variadic::from_iter(
print.call::<()>(Variadic::from_iter(
["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
// of the arguments can be anything that is convertible from the parameters given by Lua, in
// this case, the function expects two string sequences.
@@ -154,8 +152,8 @@ fn main() -> Result<()> {
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> {
impl FromLua for Vec2 {
fn from_lua(value: Value, _: &Lua) -> Result<Self> {
match value {
Value::UserData(ud) => Ok(*ud.borrow::<Self>()?),
_ => unreachable!(),
@@ -164,7 +162,7 @@ fn main() -> Result<()> {
}
impl UserData for Vec2 {
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
fn add_methods<M: UserDataMethods<Self>>(methods: &mut M) {
methods.add_method("magnitude", |_, vec, ()| {
let mag_squared = vec.0 * vec.0 + vec.1 * vec.1;
Ok(mag_squared.sqrt())
@@ -179,19 +177,15 @@ fn main() -> Result<()> {
let vec2_constructor = lua.create_function(|_, (x, y): (f32, f32)| Ok(Vec2(x, y)))?;
globals.set("vec2", vec2_constructor)?;
assert!(
(lua.load("(vec2(1, 2) + vec2(2, 2)):magnitude()")
.eval::<f32>()?
- 5.0)
.abs()
< f32::EPSILON
);
assert!((lua.load("(vec2(1, 2) + vec2(2, 2)):magnitude()").eval::<f32>()? - 5.0).abs() < f32::EPSILON);
// Normally, Rust types passed to `Lua` must be `'static`, because there is no way to be
// sure of their lifetime inside the Lua state. There is, however, a limited way to lift this
// requirement. You can call `Lua::scope` to create userdata and callbacks types that only live
// for as long as the call to scope, but do not have to be `'static` (and `Send`).
// TODO: Re-enable this
/*
{
let mut rust_val = 0;
@@ -213,6 +207,7 @@ fn main() -> Result<()> {
assert_eq!(rust_val, 42);
}
*/
// We were able to run our 'sketchy' function inside the scope just fine. However, if we
// try to run our 'sketchy' function outside of the scope, the function we created will have
+7 -2
View File
@@ -28,9 +28,14 @@ fn main() -> Result<()> {
let globals = lua.globals();
// Create Car struct from a Lua table
let car: Car = lua.from_value(lua.load(r#"
let car: Car = lua.from_value(
lua.load(
r#"
{active = true, model = "Volkswagen Golf", transmission = "Automatic", engine = {v = 1499, kw = 90}}
"#).eval()?)?;
"#,
)
.eval()?,
)?;
// Set it as (serializable) userdata
globals.set("null", lua.null())?;
+2 -2
View File
@@ -7,7 +7,7 @@ struct Rectangle {
}
impl UserData for Rectangle {
fn add_fields<'lua, F: mlua::UserDataFields<'lua, Self>>(fields: &mut F) {
fn add_fields<F: mlua::UserDataFields<Self>>(fields: &mut F) {
fields.add_field_method_get("length", |_, this| Ok(this.length));
fields.add_field_method_set("length", |_, this, val| {
this.length = val;
@@ -20,7 +20,7 @@ impl UserData for Rectangle {
});
}
fn add_methods<'lua, M: mlua::UserDataMethods<'lua, Self>>(methods: &mut M) {
fn add_methods<M: mlua::UserDataMethods<Self>>(methods: &mut M) {
methods.add_method("area", |_, this, ()| Ok(this.length * this.width));
methods.add_method("diagonal", |_, this, ()| {
Ok((this.length.pow(2) as f64 + this.width.pow(2) as f64).sqrt())
+10 -6
View File
@@ -1,10 +1,10 @@
[package]
name = "mlua-sys"
version = "0.5.0"
version = "0.8.2"
authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
rust-version = "1.71"
edition = "2021"
repository = "https://github.com/khvzak/mlua"
repository = "https://github.com/mlua-rs/mlua"
documentation = "https://docs.rs/mlua-sys"
readme = "README.md"
categories = ["external-ffi-bindings"]
@@ -12,7 +12,7 @@ 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
Low level (FFI) bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Luau
"""
[package.metadata.docs.rs]
@@ -30,6 +30,7 @@ luau = ["luau0-src"]
luau-codegen = ["luau"]
luau-vector4 = ["luau"]
vendored = ["lua-src", "luajit-src"]
external = []
module = []
[dependencies]
@@ -38,6 +39,9 @@ module = []
cc = "1.0"
cfg-if = "1.0"
pkg-config = "0.3.17"
lua-src = { version = ">= 546.0.2, < 546.1.0", optional = true }
luajit-src = { version = ">= 210.5.0, < 210.6.0", optional = true }
luau0-src = { version = "0.7.11", optional = true }
lua-src = { version = ">= 548.1.0, < 548.2.0", optional = true }
luajit-src = { version = ">= 210.6.0, < 210.7.0", optional = true }
luau0-src = { version = "0.15.4", optional = true }
[lints.rust]
unexpected_cfgs = { level = "allow", check-cfg = ['cfg(raw_dylib)'] }
+2 -2
View File
@@ -1,8 +1,8 @@
# mlua-sys
Low level (FFI) bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Roblox [Luau].
Low level (FFI) bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and [Luau].
Intended to be consumed by the [mlua] crate.
[Luau]: https://github.com/Roblox/luau
[Luau]: https://github.com/luau-lang/luau
[mlua]: https://crates.io/crates/mlua
+19 -9
View File
@@ -32,15 +32,19 @@ pub fn probe_lua() {
// Find using `pkg-config`
#[cfg(feature = "lua54")]
let (incl_bound, excl_bound, alt_probe, ver) = ("5.4", "5.5", Some("lua5.4"), "5.4");
let (incl_bound, excl_bound, alt_probe, ver) =
("5.4", "5.5", ["lua5.4", "lua-5.4", "lua54"], "5.4");
#[cfg(feature = "lua53")]
let (incl_bound, excl_bound, alt_probe, ver) = ("5.3", "5.4", Some("lua5.3"), "5.3");
let (incl_bound, excl_bound, alt_probe, ver) =
("5.3", "5.4", ["lua5.3", "lua-5.3", "lua53"], "5.3");
#[cfg(feature = "lua52")]
let (incl_bound, excl_bound, alt_probe, ver) = ("5.2", "5.3", Some("lua5.2"), "5.2");
let (incl_bound, excl_bound, alt_probe, ver) =
("5.2", "5.3", ["lua5.2", "lua-5.2", "lua52"], "5.2");
#[cfg(feature = "lua51")]
let (incl_bound, excl_bound, alt_probe, ver) = ("5.1", "5.2", Some("lua5.1"), "5.1");
let (incl_bound, excl_bound, alt_probe, ver) =
("5.1", "5.2", ["lua5.1", "lua-5.1", "lua51"], "5.1");
#[cfg(feature = "luajit")]
let (incl_bound, excl_bound, alt_probe, ver) = ("2.0.4", "2.2", None, "JIT");
let (incl_bound, excl_bound, alt_probe, ver) = ("2.0.4", "2.2", [], "JIT");
#[rustfmt::skip]
let mut lua = pkg_config::Config::new()
@@ -48,10 +52,16 @@ pub fn probe_lua() {
.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());
if lua.is_err() {
for pkg in alt_probe {
lua = pkg_config::Config::new()
.cargo_metadata(true)
.probe(pkg);
if lua.is_ok() {
break;
}
}
}
lua.unwrap_or_else(|err| panic!("cannot find Lua{ver} using `pkg-config`: {err}"));
+1
View File
@@ -21,6 +21,7 @@ pub fn probe_lua() {
#[cfg(feature = "luau")]
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();
+21 -12
View File
@@ -1,3 +1,5 @@
use std::env;
cfg_if::cfg_if! {
if #[cfg(any(feature = "luau", feature = "vendored"))] {
#[path = "find_vendored.rs"]
@@ -12,22 +14,29 @@ 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");
#[cfg(any(
all(feature = "vendored", any(feature = "external", feature = "module")),
all(feature = "external", any(feature = "vendored", feature = "module")),
all(feature = "module", any(feature = "vendored", feature = "external"))
))]
compile_error!("`vendored`, `external` and `module` features are mutually exclusive");
println!("cargo:rerun-if-changed=build");
#[cfg(windows)]
if cfg!(feature = "module") {
if !std::env::var("LUA_LIB_NAME").unwrap_or_default().is_empty() {
// Don't use raw-dylib linking
find::probe_lua();
return;
// Check if compilation and linking is handled by external crate
if cfg!(not(feature = "external")) {
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");
}
println!("cargo:rustc-cfg=raw_dylib");
#[cfg(not(feature = "module"))]
find::probe_lua();
}
#[cfg(not(feature = "module"))]
find::probe_lua();
}
+13 -8
View File
@@ -1,7 +1,8 @@
//! Low level bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Roblox Luau.
//! Low level bindings to Lua 5.4/5.3/5.2/5.1 (including LuaJIT) and Luau.
#![allow(non_camel_case_types, non_snake_case, dead_code)]
#![allow(non_camel_case_types, non_snake_case)]
#![allow(clippy::missing_safety_doc)]
#![allow(unsafe_op_in_unsafe_fn)]
#![doc(test(attr(deny(warnings))))]
#![cfg_attr(docsrs, feature(doc_cfg))]
@@ -39,20 +40,22 @@ pub const LUA_MAX_UPVALUES: c_int = 200;
#[doc(hidden)]
pub const LUA_TRACEBACK_STACK: c_int = 11;
// Copied from https://github.com/rust-lang/rust/blob/master/library/std/src/sys/pal/common/alloc.rs
// The minimum alignment guaranteed by the architecture. This value is used to
// add fast paths for low alignment values.
// Copied from https://github.com/rust-lang/rust/blob/master/library/std/src/sys/common/alloc.rs
#[cfg(any(
target_arch = "x86",
target_arch = "arm",
target_arch = "m68k",
target_arch = "csky",
target_arch = "mips",
target_arch = "mips32r6",
target_arch = "powerpc",
target_arch = "powerpc64",
target_arch = "sparc",
target_arch = "asmjs",
target_arch = "wasm32",
target_arch = "hexagon",
all(target_arch = "riscv32", not(target_os = "espidf")),
all(target_arch = "riscv32", not(any(target_os = "espidf", target_os = "zkvm"))),
all(target_arch = "xtensa", not(target_os = "espidf")),
))]
#[doc(hidden)]
@@ -60,18 +63,20 @@ pub const SYS_MIN_ALIGN: usize = 8;
#[cfg(any(
target_arch = "x86_64",
target_arch = "aarch64",
target_arch = "arm64ec",
target_arch = "loongarch64",
target_arch = "mips64",
target_arch = "mips64r6",
target_arch = "s390x",
target_arch = "sparc64",
target_arch = "riscv64",
target_arch = "wasm64",
target_arch = "loongarch64",
))]
#[doc(hidden)]
pub const SYS_MIN_ALIGN: usize = 16;
// The allocator on the esp-idf platform guarentees 4 byte alignment.
// The allocator on the esp-idf and zkvm platforms guarantee 4 byte alignment.
#[cfg(any(
all(target_arch = "riscv32", target_os = "espidf"),
all(target_arch = "riscv32", any(target_os = "espidf", target_os = "zkvm")),
all(target_arch = "xtensa", target_os = "espidf"),
))]
#[doc(hidden)]
+40 -43
View File
@@ -2,10 +2,8 @@
//!
//! Based on github.com/keplerproject/lua-compat-5.3
use std::convert::TryInto;
use std::mem;
use std::os::raw::{c_char, c_int, c_void};
use std::ptr;
use std::{mem, ptr};
use super::lauxlib::*;
use super::lua::*;
@@ -92,7 +90,7 @@ unsafe fn compat53_findfield(L: *mut lua_State, objidx: c_int, level: c_int) ->
} else if compat53_findfield(L, objidx, level - 1) != 0 {
// try recursively
lua_remove(L, -2); // remove table (but keep name)
lua_pushliteral(L, ".");
lua_pushliteral(L, c".");
lua_insert(L, -2); // place '.' between the two names
lua_concat(L, 3);
return 1;
@@ -123,13 +121,13 @@ unsafe fn compat53_pushfuncname(L: *mut lua_State, ar: *mut lua_Debug) {
lua_pushfstring(L, cstr!("function '%s'"), (*ar).name);
} else if *(*ar).what == b'm' as c_char {
// main?
lua_pushliteral(L, "main chunk");
lua_pushliteral(L, c"main chunk");
} else if *(*ar).what == b'C' as c_char {
if compat53_pushglobalfuncname(L, ar) != 0 {
lua_pushfstring(L, cstr!("function '%s'"), lua_tostring(L, -1));
lua_remove(L, -2); // remove name
} else {
lua_pushliteral(L, "?");
lua_pushliteral(L, c"?");
}
} else {
lua_pushfstring(
@@ -178,7 +176,7 @@ pub unsafe fn lua_rotate(L: *mut lua_State, mut idx: c_int, mut n: c_int) {
#[inline(always)]
pub unsafe fn lua_copy(L: *mut lua_State, fromidx: c_int, toidx: c_int) {
let abs_to = lua_absindex(L, toidx);
luaL_checkstack(L, 1, cstr!("not enough stack slots"));
luaL_checkstack(L, 1, cstr!("not enough stack slots available"));
lua_pushvalue(L, fromidx);
lua_replace(L, abs_to);
}
@@ -316,7 +314,7 @@ pub unsafe fn lua_rawseti(L: *mut lua_State, idx: c_int, n: lua_Integer) {
#[inline(always)]
pub unsafe fn lua_rawsetp(L: *mut lua_State, idx: c_int, p: *const c_void) {
let abs_i = lua_absindex(L, idx);
luaL_checkstack(L, 1, cstr!("not enough stack slots"));
luaL_checkstack(L, 1, cstr!("not enough stack slots available"));
lua_pushlightuserdata(L, p as *mut c_void);
lua_insert(L, -2);
lua_rawset(L, abs_i);
@@ -329,12 +327,7 @@ pub unsafe fn lua_setuservalue(L: *mut lua_State, idx: c_int) {
}
#[inline(always)]
pub unsafe fn lua_dump(
L: *mut lua_State,
writer: lua_Writer,
data: *mut c_void,
_strip: c_int,
) -> c_int {
pub unsafe fn lua_dump(L: *mut lua_State, writer: lua_Writer, data: *mut c_void, _strip: c_int) -> c_int {
lua_dump_(L, writer, data)
}
@@ -366,12 +359,7 @@ pub unsafe fn lua_pushglobaltable(L: *mut lua_State) {
}
#[inline(always)]
pub unsafe fn lua_resume(
L: *mut lua_State,
_from: *mut lua_State,
narg: c_int,
nres: *mut c_int,
) -> c_int {
pub unsafe fn lua_resume(L: *mut lua_State, _from: *mut lua_State, narg: c_int, nres: *mut c_int) -> c_int {
let ret = lua_resume_(L, narg);
if (ret == LUA_OK || ret == LUA_YIELD) && !(nres.is_null()) {
*nres = lua_gettop(L);
@@ -389,7 +377,7 @@ pub unsafe fn luaL_checkstack(L: *mut lua_State, sz: c_int, msg: *const c_char)
if !msg.is_null() {
luaL_error(L, cstr!("stack overflow (%s)"), msg);
} else {
lua_pushliteral(L, "stack overflow");
lua_pushliteral(L, c"stack overflow");
lua_error(L);
}
}
@@ -415,6 +403,25 @@ pub unsafe fn luaL_newmetatable(L: *mut lua_State, tname: *const c_char) -> c_in
}
}
pub unsafe fn luaL_loadbufferenv(
L: *mut lua_State,
data: *const c_char,
size: usize,
name: *const c_char,
mode: *const c_char,
mut env: c_int,
) -> c_int {
if env != 0 {
env = lua_absindex(L, env);
}
let status = luaL_loadbufferx(L, data, size, name, mode);
if status == LUA_OK && env != 0 {
lua_pushvalue(L, env);
lua_setfenv(L, -2);
}
status
}
#[inline(always)]
pub unsafe fn luaL_loadbufferx(
L: *mut lua_State,
@@ -437,7 +444,7 @@ pub unsafe fn luaL_loadbufferx(
#[inline(always)]
pub unsafe fn luaL_len(L: *mut lua_State, idx: c_int) -> lua_Integer {
let mut isnum = 0;
luaL_checkstack(L, 1, cstr!("not enough stack slots"));
luaL_checkstack(L, 1, cstr!("not enough stack slots available"));
lua_len(L, idx);
let res = lua_tointegerx(L, -1, &mut isnum);
lua_pop(L, 1);
@@ -447,12 +454,7 @@ pub unsafe fn luaL_len(L: *mut lua_State, idx: c_int) -> lua_Integer {
res
}
pub unsafe fn luaL_traceback(
L: *mut lua_State,
L1: *mut lua_State,
msg: *const c_char,
mut level: c_int,
) {
pub unsafe fn luaL_traceback(L: *mut lua_State, L1: *mut lua_State, msg: *const c_char, mut level: c_int) {
let mut ar: lua_Debug = mem::zeroed();
let top = lua_gettop(L);
let numlevels = compat53_countlevels(L1);
@@ -465,12 +467,12 @@ pub unsafe fn luaL_traceback(
if !msg.is_null() {
lua_pushfstring(L, cstr!("%s\n"), msg);
}
lua_pushliteral(L, "stack traceback:");
lua_pushliteral(L, c"stack traceback:");
while lua_getstack(L1, level, &mut ar) != 0 {
level += 1;
if level == mark {
// too many levels?
lua_pushliteral(L, "\n\t..."); // add a '...'
lua_pushliteral(L, c"\n\t..."); // add a '...'
level = numlevels - COMPAT53_LEVELS2; // and skip to last ones
} else {
lua_getinfo(L1, cstr!("Slnt"), &mut ar);
@@ -478,7 +480,7 @@ pub unsafe fn luaL_traceback(
if ar.currentline > 0 {
lua_pushfstring(L, cstr!("%d:"), ar.currentline);
}
lua_pushliteral(L, " in ");
lua_pushliteral(L, c" in ");
compat53_pushfuncname(L, &mut ar);
lua_concat(L, lua_gettop(L) - top);
}
@@ -491,16 +493,16 @@ pub unsafe fn luaL_tolstring(L: *mut lua_State, mut idx: c_int, len: *mut usize)
if luaL_callmeta(L, idx, cstr!("__tostring")) == 0 {
match lua_type(L, idx) {
LUA_TNIL => {
lua_pushliteral(L, "nil");
lua_pushliteral(L, c"nil");
}
LUA_TSTRING | LUA_TNUMBER => {
lua_pushvalue(L, idx);
}
LUA_TBOOLEAN => {
if lua_toboolean(L, idx) == 0 {
lua_pushliteral(L, "false");
lua_pushliteral(L, c"false");
} else {
lua_pushliteral(L, "true");
lua_pushliteral(L, c"true");
}
}
t => {
@@ -524,14 +526,14 @@ pub unsafe fn luaL_tolstring(L: *mut lua_State, mut idx: c_int, len: *mut usize)
#[inline(always)]
pub unsafe fn luaL_setmetatable(L: *mut lua_State, tname: *const c_char) {
luaL_checkstack(L, 1, cstr!("not enough stack slots"));
luaL_checkstack(L, 1, cstr!("not enough stack slots available"));
luaL_getmetatable(L, tname);
lua_setmetatable(L, -2);
}
pub unsafe fn luaL_getsubtable(L: *mut lua_State, idx: c_int, fname: *const c_char) -> c_int {
let abs_i = lua_absindex(L, idx);
luaL_checkstack(L, 3, cstr!("not enough stack slots"));
luaL_checkstack(L, 3, cstr!("not enough stack slots available"));
lua_pushstring_(L, fname);
if lua_gettable(L, abs_i) == LUA_TTABLE {
return 1;
@@ -544,14 +546,9 @@ pub unsafe fn luaL_getsubtable(L: *mut lua_State, idx: c_int, fname: *const c_ch
0
}
pub unsafe fn luaL_requiref(
L: *mut lua_State,
modname: *const c_char,
openf: lua_CFunction,
glb: c_int,
) {
pub unsafe fn luaL_requiref(L: *mut lua_State, modname: *const c_char, openf: lua_CFunction, glb: c_int) {
luaL_checkstack(L, 3, cstr!("not enough stack slots available"));
luaL_getsubtable(L, LUA_REGISTRYINDEX, cstr!("_LOADED"));
luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE);
if lua_getfield(L, -1, modname) == LUA_TNIL {
lua_pop(L, 1);
lua_pushcfunction(L, openf);
+6 -8
View File
@@ -8,6 +8,9 @@ use super::lua::{self, lua_CFunction, lua_Integer, lua_Number, lua_State};
// Extra error code for 'luaL_load'
pub const LUA_ERRFILE: c_int = lua::LUA_ERRERR + 1;
// Key, in the registry, for table of loaded modules
pub const LUA_LOADED_TABLE: *const c_char = cstr!("_LOADED");
#[repr(C)]
pub struct luaL_Reg {
pub name: *const c_char,
@@ -15,7 +18,7 @@ pub struct luaL_Reg {
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn luaL_register(L: *mut lua_State, libname: *const c_char, l: *const luaL_Reg);
#[link_name = "luaL_getmetafield"]
pub fn luaL_getmetafield_(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int;
@@ -58,17 +61,12 @@ pub const LUA_NOREF: c_int = -2;
pub const LUA_REFNIL: c_int = -1;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn luaL_ref(L: *mut lua_State, t: c_int) -> c_int;
pub fn luaL_unref(L: *mut lua_State, t: c_int, r#ref: c_int);
pub fn luaL_loadfile(L: *mut lua_State, filename: *const c_char) -> c_int;
pub fn luaL_loadbuffer(
L: *mut lua_State,
buff: *const c_char,
sz: usize,
name: *const c_char,
) -> c_int;
pub fn luaL_loadbuffer(L: *mut lua_State, buff: *const c_char, sz: usize, name: *const c_char) -> c_int;
pub fn luaL_loadstring(L: *mut lua_State, s: *const c_char) -> c_int;
pub fn luaL_newstate() -> *mut lua_State;
+18 -16
View File
@@ -1,5 +1,6 @@
//! Contains definitions from `lua.h`.
use std::ffi::CStr;
use std::marker::{PhantomData, PhantomPinned};
use std::os::raw::{c_char, c_double, c_int, c_void};
use std::ptr;
@@ -83,13 +84,13 @@ pub type lua_Reader =
pub type lua_Writer =
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 (no unwinding)
#[rustfmt::skip]
pub type lua_Alloc =
unsafe extern "C-unwind" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void;
unsafe extern "C" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
//
// State manipulation
//
@@ -219,7 +220,7 @@ pub const LUA_GCSETPAUSE: c_int = 6;
pub const LUA_GCSETSTEPMUL: c_int = 7;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn lua_gc(L: *mut lua_State, what: c_int, data: c_int) -> c_int;
}
@@ -227,7 +228,7 @@ extern "C-unwind" {
// Miscellaneous functions
//
#[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe 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;
@@ -312,10 +313,8 @@ pub unsafe fn lua_isnoneornil(L: *mut lua_State, n: c_int) -> c_int {
}
#[inline(always)]
pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static str) {
use std::ffi::CString;
let c_str = CString::new(s).unwrap();
lua_pushlstring_(L, c_str.as_ptr(), c_str.as_bytes().len())
pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static CStr) {
lua_pushstring_(L, s.as_ptr());
}
#[inline(always)]
@@ -328,6 +327,14 @@ pub unsafe fn lua_getglobal_(L: *mut lua_State, var: *const c_char) {
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)]
pub unsafe fn lua_tostring(L: *mut lua_State, i: c_int) -> *const c_char {
lua_tolstring(L, i, ptr::null_mut())
@@ -363,7 +370,7 @@ pub const LUA_MASKCOUNT: c_int = 1 << (LUA_HOOKCOUNT as usize);
pub type lua_Hook = unsafe extern "C-unwind" fn(L: *mut lua_State, ar: *mut lua_Debug);
#[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn lua_getstack(L: *mut lua_State, level: c_int, ar: *mut lua_Debug) -> c_int;
pub fn lua_getinfo(L: *mut lua_State, what: *const c_char, ar: *mut lua_Debug) -> c_int;
pub fn lua_getlocal(L: *mut lua_State, ar: *const lua_Debug, n: c_int) -> *const c_char;
@@ -371,12 +378,7 @@ extern "C-unwind" {
pub fn lua_getupvalue(L: *mut lua_State, funcindex: c_int, n: c_int) -> *const c_char;
pub fn lua_setupvalue(L: *mut lua_State, funcindex: c_int, n: c_int) -> *const c_char;
pub fn lua_sethook(
L: *mut lua_State,
func: Option<lua_Hook>,
mask: c_int,
count: c_int,
) -> c_int;
pub fn lua_sethook(L: *mut lua_State, func: Option<lua_Hook>, mask: c_int, count: c_int) -> c_int;
pub fn lua_gethook(L: *mut lua_State) -> Option<lua_Hook>;
pub fn lua_gethookmask(L: *mut lua_State) -> c_int;
pub fn lua_gethookcount(L: *mut lua_State) -> c_int;
+13 -13
View File
@@ -1,27 +1,27 @@
//! Contains definitions from `lualib.h`.
use std::os::raw::c_int;
use std::os::raw::{c_char, c_int};
use super::lua::lua_State;
pub const LUA_COLIBNAME: &str = "coroutine";
pub const LUA_TABLIBNAME: &str = "table";
pub const LUA_IOLIBNAME: &str = "io";
pub const LUA_OSLIBNAME: &str = "os";
pub const LUA_STRLIBNAME: &str = "string";
pub const LUA_MATHLIBNAME: &str = "math";
pub const LUA_DBLIBNAME: &str = "debug";
pub const LUA_LOADLIBNAME: &str = "package";
pub const LUA_COLIBNAME: *const c_char = cstr!("coroutine");
pub const LUA_TABLIBNAME: *const c_char = cstr!("table");
pub const LUA_IOLIBNAME: *const c_char = cstr!("io");
pub const LUA_OSLIBNAME: *const c_char = cstr!("os");
pub const LUA_STRLIBNAME: *const c_char = cstr!("string");
pub const LUA_MATHLIBNAME: *const c_char = cstr!("math");
pub const LUA_DBLIBNAME: *const c_char = cstr!("debug");
pub const LUA_LOADLIBNAME: *const c_char = cstr!("package");
#[cfg(feature = "luajit")]
pub const LUA_BITLIBNAME: &str = "bit";
pub const LUA_BITLIBNAME: *const c_char = cstr!("bit");
#[cfg(feature = "luajit")]
pub const LUA_JITLIBNAME: &str = "jit";
pub const LUA_JITLIBNAME: *const c_char = cstr!("jit");
#[cfg(feature = "luajit")]
pub const LUA_FFILIBNAME: &str = "ffi";
pub const LUA_FFILIBNAME: *const c_char = cstr!("ffi");
#[cfg_attr(all(windows, raw_dylib), link(name = "lua51", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn luaopen_base(L: *mut lua_State) -> c_int;
pub fn luaopen_table(L: *mut lua_State) -> c_int;
pub fn luaopen_io(L: *mut lua_State) -> c_int;
+26 -23
View File
@@ -2,7 +2,6 @@
//!
//! 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::ptr;
@@ -158,22 +157,12 @@ pub unsafe fn lua_rawseti(L: *mut lua_State, idx: c_int, n: lua_Integer) {
}
#[inline(always)]
pub unsafe fn lua_dump(
L: *mut lua_State,
writer: lua_Writer,
data: *mut c_void,
_strip: c_int,
) -> c_int {
pub unsafe fn lua_dump(L: *mut lua_State, writer: lua_Writer, data: *mut c_void, _strip: c_int) -> c_int {
lua_dump_(L, writer, data)
}
#[inline(always)]
pub unsafe fn lua_resume(
L: *mut lua_State,
from: *mut lua_State,
narg: c_int,
nres: *mut c_int,
) -> c_int {
pub unsafe fn lua_resume(L: *mut lua_State, from: *mut lua_State, narg: c_int, nres: *mut c_int) -> c_int {
let ret = lua_resume_(L, from, narg);
if (ret == LUA_OK || ret == LUA_YIELD) && !(nres.is_null()) {
*nres = lua_gettop(L);
@@ -210,16 +199,16 @@ pub unsafe fn luaL_tolstring(L: *mut lua_State, mut idx: c_int, len: *mut usize)
if luaL_callmeta(L, idx, cstr!("__tostring")) == 0 {
match lua_type(L, idx) {
LUA_TNIL => {
lua_pushliteral(L, "nil");
lua_pushliteral(L, c"nil");
}
LUA_TSTRING | LUA_TNUMBER => {
lua_pushvalue(L, idx);
}
LUA_TBOOLEAN => {
if lua_toboolean(L, idx) == 0 {
lua_pushliteral(L, "false");
lua_pushliteral(L, c"false");
} else {
lua_pushliteral(L, "true");
lua_pushliteral(L, c"true");
}
}
t => {
@@ -241,14 +230,9 @@ pub unsafe fn luaL_tolstring(L: *mut lua_State, mut idx: c_int, len: *mut usize)
lua_tolstring(L, -1, len)
}
pub unsafe fn luaL_requiref(
L: *mut lua_State,
modname: *const c_char,
openf: lua_CFunction,
glb: c_int,
) {
pub unsafe fn luaL_requiref(L: *mut lua_State, modname: *const c_char, openf: lua_CFunction, glb: c_int) {
luaL_checkstack(L, 3, cstr!("not enough stack slots available"));
luaL_getsubtable(L, LUA_REGISTRYINDEX, cstr!("_LOADED"));
luaL_getsubtable(L, LUA_REGISTRYINDEX, LUA_LOADED_TABLE);
if lua_getfield(L, -1, modname) == LUA_TNIL {
lua_pop(L, 1);
lua_pushcfunction(L, openf);
@@ -263,3 +247,22 @@ pub unsafe fn luaL_requiref(
}
lua_replace(L, -2);
}
pub unsafe fn luaL_loadbufferenv(
L: *mut lua_State,
data: *const c_char,
size: usize,
name: *const c_char,
mode: *const c_char,
mut env: c_int,
) -> c_int {
if env != 0 {
env = lua_absindex(L, env);
}
let status = luaL_loadbufferx(L, data, size, name, mode);
if status == LUA_OK && env != 0 {
lua_pushvalue(L, env);
lua_setupvalue(L, -2, 1);
}
status
}
+14 -23
View File
@@ -8,6 +8,12 @@ use super::lua::{self, lua_CFunction, lua_Integer, lua_Number, lua_State, lua_Un
// Extra error code for 'luaL_load'
pub const LUA_ERRFILE: c_int = lua::LUA_ERRERR + 1;
// Key, in the registry, for table of loaded modules
pub const LUA_LOADED_TABLE: *const c_char = cstr!("_LOADED");
// Key, in the registry, for table of preloaded loaders
pub const LUA_PRELOAD_TABLE: *const c_char = cstr!("_PRELOAD");
#[repr(C)]
pub struct luaL_Reg {
pub name: *const c_char,
@@ -15,7 +21,7 @@ pub struct luaL_Reg {
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn luaL_checkversion_(L: *mut lua_State, ver: lua_Number);
#[link_name = "luaL_getmetafield"]
@@ -25,12 +31,8 @@ extern "C-unwind" {
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_checklstring(L: *mut lua_State, arg: c_int, l: *mut usize) -> *const c_char;
pub fn luaL_optlstring(
L: *mut lua_State,
arg: c_int,
def: *const c_char,
l: *mut usize,
) -> *const c_char;
pub fn luaL_optlstring(L: *mut lua_State, arg: c_int, def: *const c_char, l: *mut usize)
-> *const c_char;
pub fn luaL_checknumber(L: *mut lua_State, arg: c_int) -> lua_Number;
pub fn luaL_optnumber(L: *mut lua_State, arg: c_int, def: lua_Number) -> lua_Number;
pub fn luaL_checkinteger(L: *mut lua_State, arg: c_int) -> lua_Integer;
@@ -67,12 +69,11 @@ pub const LUA_NOREF: c_int = -2;
pub const LUA_REFNIL: c_int = -1;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn luaL_ref(L: *mut lua_State, t: c_int) -> c_int;
pub fn luaL_unref(L: *mut lua_State, t: c_int, r#ref: c_int);
pub fn luaL_loadfilex(L: *mut lua_State, filename: *const c_char, mode: *const c_char)
-> c_int;
pub fn luaL_loadfilex(L: *mut lua_State, filename: *const c_char, mode: *const c_char) -> c_int;
}
#[inline(always)]
@@ -81,7 +82,7 @@ pub unsafe fn luaL_loadfile(L: *mut lua_State, f: *const c_char) -> c_int {
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn luaL_loadbufferx(
L: *mut lua_State,
buff: *const c_char,
@@ -109,12 +110,7 @@ extern "C-unwind" {
pub fn luaL_traceback(L: *mut lua_State, L1: *mut lua_State, msg: *const c_char, level: c_int);
#[link_name = "luaL_requiref"]
pub fn luaL_requiref_(
L: *mut lua_State,
modname: *const c_char,
openf: lua_CFunction,
glb: c_int,
);
pub fn luaL_requiref_(L: *mut lua_State, modname: *const c_char, openf: lua_CFunction, glb: c_int);
}
//
@@ -173,12 +169,7 @@ pub unsafe fn luaL_getmetatable(L: *mut lua_State, n: *const c_char) {
// luaL_opt would be implemented here but it is undocumented, so it's omitted
#[inline(always)]
pub unsafe fn luaL_loadbuffer(
L: *mut lua_State,
s: *const c_char,
sz: usize,
n: *const c_char,
) -> c_int {
pub unsafe fn luaL_loadbuffer(L: *mut lua_State, s: *const c_char, sz: usize, n: *const c_char) -> c_int {
luaL_loadbufferx(L, s, sz, n, ptr::null())
}
+23 -32
View File
@@ -1,5 +1,6 @@
//! Contains definitions from `lua.h`.
use std::ffi::CStr;
use std::marker::{PhantomData, PhantomPinned};
use std::os::raw::{c_char, c_double, c_int, c_uchar, c_uint, c_void};
use std::ptr;
@@ -88,13 +89,13 @@ pub type lua_Reader =
pub type lua_Writer =
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 (no unwinding)
#[rustfmt::skip]
pub type lua_Alloc =
unsafe extern "C-unwind" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void;
unsafe extern "C" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
//
// State manipulation
//
@@ -160,14 +161,14 @@ pub const LUA_OPLT: c_int = 1;
pub const LUA_OPLE: c_int = 2;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn lua_arith(L: *mut lua_State, op: c_int);
pub fn lua_rawequal(L: *mut lua_State, idx1: c_int, idx2: c_int) -> c_int;
pub fn lua_compare(L: *mut lua_State, idx1: c_int, idx2: c_int, op: c_int) -> c_int;
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
//
// Push functions (C -> stack)
//
@@ -222,13 +223,7 @@ extern "C-unwind" {
//
// 'load' and 'call' functions (load and run Lua code)
//
pub fn lua_callk(
L: *mut lua_State,
nargs: c_int,
nresults: c_int,
ctx: c_int,
k: Option<lua_CFunction>,
);
pub fn lua_callk(L: *mut lua_State, nargs: c_int, nresults: c_int, ctx: c_int, k: Option<lua_CFunction>);
pub fn lua_pcallk(
L: *mut lua_State,
nargs: c_int,
@@ -262,16 +257,11 @@ pub unsafe fn lua_pcall(L: *mut lua_State, n: c_int, r: c_int, f: c_int) -> c_in
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
//
// Coroutine functions
//
pub fn lua_yieldk(
L: *mut lua_State,
nresults: c_int,
ctx: c_int,
k: Option<lua_CFunction>,
) -> c_int;
pub fn lua_yieldk(L: *mut lua_State, nresults: c_int, ctx: c_int, k: Option<lua_CFunction>) -> c_int;
#[link_name = "lua_resume"]
pub fn lua_resume_(L: *mut lua_State, from: *mut lua_State, narg: c_int) -> c_int;
pub fn lua_status(L: *mut lua_State) -> c_int;
@@ -299,12 +289,12 @@ pub const LUA_GCGEN: c_int = 10;
pub const LUA_GCINC: c_int = 11;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn lua_gc(L: *mut lua_State, what: c_int, data: c_int) -> c_int;
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
//
// Miscellaneous functions
//
@@ -406,10 +396,8 @@ pub unsafe fn lua_isnoneornil(L: *mut lua_State, n: c_int) -> c_int {
}
#[inline(always)]
pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static str) -> *const c_char {
use std::ffi::CString;
let c_str = CString::new(s).unwrap();
lua_pushlstring_(L, c_str.as_ptr(), c_str.as_bytes().len())
pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static CStr) {
lua_pushstring(L, s.as_ptr());
}
#[inline(always)]
@@ -417,6 +405,14 @@ pub unsafe fn lua_pushglobaltable(L: *mut lua_State) {
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)]
pub unsafe fn lua_tostring(L: *mut lua_State, i: c_int) -> *const c_char {
lua_tolstring(L, i, ptr::null_mut())
@@ -452,7 +448,7 @@ pub const LUA_MASKCOUNT: c_int = 1 << (LUA_HOOKCOUNT as usize);
pub type lua_Hook = unsafe extern "C-unwind" fn(L: *mut lua_State, ar: *mut lua_Debug);
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn lua_getstack(L: *mut lua_State, level: c_int, ar: *mut lua_Debug) -> c_int;
pub fn lua_getinfo(L: *mut lua_State, what: *const c_char, ar: *mut lua_Debug) -> c_int;
pub fn lua_getlocal(L: *mut lua_State, ar: *const lua_Debug, n: c_int) -> *const c_char;
@@ -463,12 +459,7 @@ extern "C-unwind" {
pub fn lua_upvalueid(L: *mut lua_State, fidx: c_int, n: c_int) -> *mut c_void;
pub fn lua_upvaluejoin(L: *mut lua_State, fidx1: c_int, n1: c_int, fidx2: c_int, n2: c_int);
pub fn lua_sethook(
L: *mut lua_State,
func: Option<lua_Hook>,
mask: c_int,
count: c_int,
) -> c_int;
pub fn lua_sethook(L: *mut lua_State, func: Option<lua_Hook>, mask: c_int, count: c_int) -> c_int;
pub fn lua_gethook(L: *mut lua_State) -> Option<lua_Hook>;
pub fn lua_gethookmask(L: *mut lua_State) -> c_int;
pub fn lua_gethookcount(L: *mut lua_State) -> c_int;
+11 -11
View File
@@ -1,21 +1,21 @@
//! Contains definitions from `lualib.h`.
use std::os::raw::c_int;
use std::os::raw::{c_char, c_int};
use super::lua::lua_State;
pub const LUA_COLIBNAME: &str = "coroutine";
pub const LUA_TABLIBNAME: &str = "table";
pub const LUA_IOLIBNAME: &str = "io";
pub const LUA_OSLIBNAME: &str = "os";
pub const LUA_STRLIBNAME: &str = "string";
pub const LUA_BITLIBNAME: &str = "bit32";
pub const LUA_MATHLIBNAME: &str = "math";
pub const LUA_DBLIBNAME: &str = "debug";
pub const LUA_LOADLIBNAME: &str = "package";
pub const LUA_COLIBNAME: *const c_char = cstr!("coroutine");
pub const LUA_TABLIBNAME: *const c_char = cstr!("table");
pub const LUA_IOLIBNAME: *const c_char = cstr!("io");
pub const LUA_OSLIBNAME: *const c_char = cstr!("os");
pub const LUA_STRLIBNAME: *const c_char = cstr!("string");
pub const LUA_BITLIBNAME: *const c_char = cstr!("bit32");
pub const LUA_MATHLIBNAME: *const c_char = cstr!("math");
pub const LUA_DBLIBNAME: *const c_char = cstr!("debug");
pub const LUA_LOADLIBNAME: *const c_char = cstr!("package");
#[cfg_attr(all(windows, raw_dylib), link(name = "lua52", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn luaopen_base(L: *mut lua_State) -> c_int;
pub fn luaopen_coroutine(L: *mut lua_State) -> c_int;
pub fn luaopen_table(L: *mut lua_State) -> c_int;
+22 -7
View File
@@ -1,19 +1,34 @@
//! MLua compatibility layer for Lua 5.3
use std::os::raw::c_int;
use std::os::raw::{c_char, c_int};
use super::lauxlib::*;
use super::lua::*;
#[inline(always)]
pub unsafe fn lua_resume(
L: *mut lua_State,
from: *mut lua_State,
narg: c_int,
nres: *mut c_int,
) -> c_int {
pub unsafe fn lua_resume(L: *mut lua_State, from: *mut lua_State, narg: c_int, nres: *mut c_int) -> c_int {
let ret = lua_resume_(L, from, narg);
if (ret == LUA_OK || ret == LUA_YIELD) && !(nres.is_null()) {
*nres = lua_gettop(L);
}
ret
}
pub unsafe fn luaL_loadbufferenv(
L: *mut lua_State,
data: *const c_char,
size: usize,
name: *const c_char,
mode: *const c_char,
mut env: c_int,
) -> c_int {
if env != 0 {
env = lua_absindex(L, env);
}
let status = luaL_loadbufferx(L, data, size, name, mode);
if status == LUA_OK && env != 0 {
lua_pushvalue(L, env);
lua_setupvalue(L, -2, 1);
}
status
}
+10 -25
View File
@@ -9,10 +9,10 @@ use super::lua::{self, lua_CFunction, lua_Integer, lua_Number, lua_State};
pub const LUA_ERRFILE: c_int = lua::LUA_ERRERR + 1;
// Key, in the registry, for table of loaded modules
pub const LUA_LOADED_TABLE: &str = "_LOADED";
pub const LUA_LOADED_TABLE: *const c_char = cstr!("_LOADED");
// Key, in the registry, for table of preloaded loaders
pub const LUA_PRELOAD_TABLE: &str = "_PRELOAD";
pub const LUA_PRELOAD_TABLE: *const c_char = cstr!("_PRELOAD");
#[repr(C)]
pub struct luaL_Reg {
@@ -21,7 +21,7 @@ pub struct luaL_Reg {
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn luaL_checkversion_(L: *mut lua_State, ver: lua_Number, sz: usize);
pub fn luaL_getmetafield(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int;
@@ -30,12 +30,8 @@ extern "C-unwind" {
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_checklstring(L: *mut lua_State, arg: c_int, l: *mut usize) -> *const c_char;
pub fn luaL_optlstring(
L: *mut lua_State,
arg: c_int,
def: *const c_char,
l: *mut usize,
) -> *const c_char;
pub fn luaL_optlstring(L: *mut lua_State, arg: c_int, def: *const c_char, l: *mut usize)
-> *const c_char;
pub fn luaL_checknumber(L: *mut lua_State, arg: c_int) -> lua_Number;
pub fn luaL_optnumber(L: *mut lua_State, arg: c_int, def: lua_Number) -> lua_Number;
pub fn luaL_checkinteger(L: *mut lua_State, arg: c_int) -> lua_Integer;
@@ -69,12 +65,11 @@ pub const LUA_NOREF: c_int = -2;
pub const LUA_REFNIL: c_int = -1;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn luaL_ref(L: *mut lua_State, t: c_int) -> c_int;
pub fn luaL_unref(L: *mut lua_State, t: c_int, r#ref: c_int);
pub fn luaL_loadfilex(L: *mut lua_State, filename: *const c_char, mode: *const c_char)
-> c_int;
pub fn luaL_loadfilex(L: *mut lua_State, filename: *const c_char, mode: *const c_char) -> c_int;
}
#[inline(always)]
@@ -83,7 +78,7 @@ pub unsafe fn luaL_loadfile(L: *mut lua_State, f: *const c_char) -> c_int {
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn luaL_loadbufferx(
L: *mut lua_State,
buff: *const c_char,
@@ -110,12 +105,7 @@ extern "C-unwind" {
pub fn luaL_traceback(L: *mut lua_State, L1: *mut lua_State, msg: *const c_char, level: c_int);
pub fn luaL_requiref(
L: *mut lua_State,
modname: *const c_char,
openf: lua_CFunction,
glb: c_int,
);
pub fn luaL_requiref(L: *mut lua_State, modname: *const c_char, openf: lua_CFunction, glb: c_int);
}
//
@@ -179,12 +169,7 @@ pub unsafe fn luaL_tolstring(L: *mut lua_State, idx: c_int, len: *mut usize) ->
// luaL_opt would be implemented here but it is undocumented, so it's omitted
#[inline(always)]
pub unsafe fn luaL_loadbuffer(
L: *mut lua_State,
s: *const c_char,
sz: usize,
n: *const c_char,
) -> c_int {
pub unsafe fn luaL_loadbuffer(L: *mut lua_State, s: *const c_char, sz: usize, n: *const c_char) -> c_int {
luaL_loadbufferx(L, s, sz, n, ptr::null())
}
+22 -21
View File
@@ -1,9 +1,9 @@
//! Contains definitions from `lua.h`.
use std::ffi::CStr;
use std::marker::{PhantomData, PhantomPinned};
use std::mem;
use std::os::raw::{c_char, c_double, c_int, c_uchar, c_void};
use std::ptr;
use std::{mem, ptr};
// Mark for precompiled code (`<esc>Lua`)
pub const LUA_SIGNATURE: &[u8] = b"\x1bLua";
@@ -96,13 +96,13 @@ pub type lua_Reader =
pub type lua_Writer =
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 (no unwinding)
#[rustfmt::skip]
pub type lua_Alloc =
unsafe extern "C-unwind" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void;
unsafe extern "C" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
//
// State manipulation
//
@@ -172,14 +172,14 @@ pub const LUA_OPLT: c_int = 1;
pub const LUA_OPLE: c_int = 2;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn lua_arith(L: *mut lua_State, op: c_int);
pub fn lua_rawequal(L: *mut lua_State, idx1: c_int, idx2: c_int) -> c_int;
pub fn lua_compare(L: *mut lua_State, idx1: c_int, idx2: c_int, op: c_int) -> c_int;
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
//
// Push functions (C -> stack)
//
@@ -251,12 +251,7 @@ extern "C-unwind" {
mode: *const c_char,
) -> c_int;
pub fn lua_dump(
L: *mut lua_State,
writer: lua_Writer,
data: *mut c_void,
strip: c_int,
) -> c_int;
pub fn lua_dump(L: *mut lua_State, writer: lua_Writer, data: *mut c_void, strip: c_int) -> c_int;
}
#[inline(always)]
@@ -270,7 +265,7 @@ pub unsafe fn lua_pcall(L: *mut lua_State, n: c_int, r: c_int, f: c_int) -> c_in
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
//
// Coroutine functions
//
@@ -305,12 +300,12 @@ pub const LUA_GCSETSTEPMUL: c_int = 7;
pub const LUA_GCISRUNNING: c_int = 9;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn lua_gc(L: *mut lua_State, what: c_int, data: c_int) -> c_int;
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
//
// Miscellaneous functions
//
@@ -413,10 +408,8 @@ pub unsafe fn lua_isnoneornil(L: *mut lua_State, n: c_int) -> c_int {
}
#[inline(always)]
pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static str) -> *const c_char {
use std::ffi::CString;
let c_str = CString::new(s).unwrap();
lua_pushlstring(L, c_str.as_ptr(), c_str.as_bytes().len())
pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static CStr) {
lua_pushstring(L, s.as_ptr());
}
#[inline(always)]
@@ -424,6 +417,14 @@ pub unsafe fn lua_pushglobaltable(L: *mut lua_State) -> c_int {
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)]
pub unsafe fn lua_tostring(L: *mut lua_State, i: c_int) -> *const c_char {
lua_tolstring(L, i, ptr::null_mut())
@@ -476,7 +477,7 @@ pub const LUA_MASKCOUNT: c_int = 1 << (LUA_HOOKCOUNT as usize);
pub type lua_Hook = unsafe extern "C-unwind" fn(L: *mut lua_State, ar: *mut lua_Debug);
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn lua_getstack(L: *mut lua_State, level: c_int, ar: *mut lua_Debug) -> c_int;
pub fn lua_getinfo(L: *mut lua_State, what: *const c_char, ar: *mut lua_Debug) -> c_int;
pub fn lua_getlocal(L: *mut lua_State, ar: *const lua_Debug, n: c_int) -> *const c_char;
+12 -12
View File
@@ -1,22 +1,22 @@
//! Contains definitions from `lualib.h`.
use std::os::raw::c_int;
use std::os::raw::{c_char, c_int};
use super::lua::lua_State;
pub const LUA_COLIBNAME: &str = "coroutine";
pub const LUA_TABLIBNAME: &str = "table";
pub const LUA_IOLIBNAME: &str = "io";
pub const LUA_OSLIBNAME: &str = "os";
pub const LUA_STRLIBNAME: &str = "string";
pub const LUA_UTF8LIBNAME: &str = "utf8";
pub const LUA_BITLIBNAME: &str = "bit32";
pub const LUA_MATHLIBNAME: &str = "math";
pub const LUA_DBLIBNAME: &str = "debug";
pub const LUA_LOADLIBNAME: &str = "package";
pub const LUA_COLIBNAME: *const c_char = cstr!("coroutine");
pub const LUA_TABLIBNAME: *const c_char = cstr!("table");
pub const LUA_IOLIBNAME: *const c_char = cstr!("io");
pub const LUA_OSLIBNAME: *const c_char = cstr!("os");
pub const LUA_STRLIBNAME: *const c_char = cstr!("string");
pub const LUA_UTF8LIBNAME: *const c_char = cstr!("utf8");
pub const LUA_BITLIBNAME: *const c_char = cstr!("bit32");
pub const LUA_MATHLIBNAME: *const c_char = cstr!("math");
pub const LUA_DBLIBNAME: *const c_char = cstr!("debug");
pub const LUA_LOADLIBNAME: *const c_char = cstr!("package");
#[cfg_attr(all(windows, raw_dylib), link(name = "lua53", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn luaopen_base(L: *mut lua_State) -> c_int;
pub fn luaopen_coroutine(L: *mut lua_State) -> c_int;
pub fn luaopen_table(L: *mut lua_State) -> c_int;
+29 -25
View File
@@ -9,10 +9,10 @@ use super::lua::{self, lua_CFunction, lua_Integer, lua_Number, lua_State};
pub const LUA_ERRFILE: c_int = lua::LUA_ERRERR + 1;
// Key, in the registry, for table of loaded modules
pub const LUA_LOADED_TABLE: &str = "_LOADED";
pub const LUA_LOADED_TABLE: *const c_char = cstr!("_LOADED");
// Key, in the registry, for table of preloaded loaders
pub const LUA_PRELOAD_TABLE: &str = "_PRELOAD";
pub const LUA_PRELOAD_TABLE: *const c_char = cstr!("_PRELOAD");
#[repr(C)]
pub struct luaL_Reg {
@@ -21,7 +21,7 @@ pub struct luaL_Reg {
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn luaL_checkversion_(L: *mut lua_State, ver: lua_Number, sz: usize);
pub fn luaL_getmetafield(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int;
@@ -29,12 +29,8 @@ extern "C-unwind" {
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_checklstring(L: *mut lua_State, arg: c_int, l: *mut usize) -> *const c_char;
pub fn luaL_optlstring(
L: *mut lua_State,
arg: c_int,
def: *const c_char,
l: *mut usize,
) -> *const c_char;
pub fn luaL_optlstring(L: *mut lua_State, arg: c_int, def: *const c_char, l: *mut usize)
-> *const c_char;
pub fn luaL_checknumber(L: *mut lua_State, arg: c_int) -> lua_Number;
pub fn luaL_optnumber(L: *mut lua_State, arg: c_int, def: lua_Number) -> lua_Number;
pub fn luaL_checkinteger(L: *mut lua_State, arg: c_int) -> lua_Integer;
@@ -68,12 +64,11 @@ pub const LUA_NOREF: c_int = -2;
pub const LUA_REFNIL: c_int = -1;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn luaL_ref(L: *mut lua_State, t: c_int) -> c_int;
pub fn luaL_unref(L: *mut lua_State, t: c_int, r#ref: c_int);
pub fn luaL_loadfilex(L: *mut lua_State, filename: *const c_char, mode: *const c_char)
-> c_int;
pub fn luaL_loadfilex(L: *mut lua_State, filename: *const c_char, mode: *const c_char) -> c_int;
}
#[inline(always)]
@@ -82,7 +77,7 @@ pub unsafe fn luaL_loadfile(L: *mut lua_State, f: *const c_char) -> c_int {
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn luaL_loadbufferx(
L: *mut lua_State,
buff: *const c_char,
@@ -111,12 +106,7 @@ extern "C-unwind" {
pub fn luaL_traceback(L: *mut lua_State, L1: *mut lua_State, msg: *const c_char, level: c_int);
pub fn luaL_requiref(
L: *mut lua_State,
modname: *const c_char,
openf: lua_CFunction,
glb: c_int,
);
pub fn luaL_requiref(L: *mut lua_State, modname: *const c_char, openf: lua_CFunction, glb: c_int);
}
//
@@ -175,15 +165,29 @@ pub unsafe fn luaL_getmetatable(L: *mut lua_State, n: *const c_char) {
// luaL_opt would be implemented here but it is undocumented, so it's omitted
#[inline(always)]
pub unsafe fn luaL_loadbuffer(
L: *mut lua_State,
s: *const c_char,
sz: usize,
n: *const c_char,
) -> c_int {
pub unsafe fn luaL_loadbuffer(L: *mut lua_State, s: *const c_char, sz: usize, n: *const c_char) -> c_int {
luaL_loadbufferx(L, s, sz, n, ptr::null())
}
pub unsafe fn luaL_loadbufferenv(
L: *mut lua_State,
data: *const c_char,
size: usize,
name: *const c_char,
mode: *const c_char,
mut env: c_int,
) -> c_int {
if env != 0 {
env = lua::lua_absindex(L, env);
}
let status = luaL_loadbufferx(L, data, size, name, mode);
if status == lua::LUA_OK && env != 0 {
lua::lua_pushvalue(L, env);
lua::lua_setupvalue(L, -2, 1);
}
status
}
//
// TODO: Generic Buffer Manipulation
//
+25 -30
View File
@@ -1,9 +1,9 @@
//! Contains definitions from `lua.h`.
use std::ffi::CStr;
use std::marker::{PhantomData, PhantomPinned};
use std::mem;
use std::os::raw::{c_char, c_double, c_int, c_uchar, c_ushort, c_void};
use std::ptr;
use std::{mem, ptr};
// Mark for precompiled code (`<esc>Lua`)
pub const LUA_SIGNATURE: &[u8] = b"\x1bLua";
@@ -95,17 +95,16 @@ pub type lua_Reader =
pub type lua_Writer =
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 (no unwinding)
#[rustfmt::skip]
pub type lua_Alloc =
unsafe extern "C-unwind" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void;
unsafe extern "C" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void;
/// Type for warning functions
pub type lua_WarnFunction =
unsafe extern "C-unwind" fn(ud: *mut c_void, msg: *const c_char, tocont: c_int);
pub type lua_WarnFunction = unsafe extern "C-unwind" fn(ud: *mut c_void, msg: *const c_char, tocont: c_int);
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
//
// State manipulation
//
@@ -187,14 +186,14 @@ pub const LUA_OPLT: c_int = 1;
pub const LUA_OPLE: c_int = 2;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn lua_arith(L: *mut lua_State, op: c_int);
pub fn lua_rawequal(L: *mut lua_State, idx1: c_int, idx2: c_int) -> c_int;
pub fn lua_compare(L: *mut lua_State, idx1: c_int, idx2: c_int, op: c_int) -> c_int;
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
//
// Push functions (C -> stack)
//
@@ -266,12 +265,7 @@ extern "C-unwind" {
mode: *const c_char,
) -> c_int;
pub fn lua_dump(
L: *mut lua_State,
writer: lua_Writer,
data: *mut c_void,
strip: c_int,
) -> c_int;
pub fn lua_dump(L: *mut lua_State, writer: lua_Writer, data: *mut c_void, strip: c_int) -> c_int;
}
#[inline(always)]
@@ -285,7 +279,7 @@ pub unsafe fn lua_pcall(L: *mut lua_State, n: c_int, r: c_int, f: c_int) -> c_in
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
//
// Coroutine functions
//
@@ -295,12 +289,7 @@ extern "C-unwind" {
ctx: lua_KContext,
k: Option<lua_KFunction>,
) -> c_int;
pub fn lua_resume(
L: *mut lua_State,
from: *mut lua_State,
narg: c_int,
nres: *mut c_int,
) -> c_int;
pub fn lua_resume(L: *mut lua_State, from: *mut lua_State, narg: c_int, nres: *mut c_int) -> c_int;
pub fn lua_status(L: *mut lua_State) -> c_int;
pub fn lua_isyieldable(L: *mut lua_State) -> c_int;
}
@@ -314,7 +303,7 @@ pub unsafe fn lua_yield(L: *mut lua_State, n: c_int) -> c_int {
// Warning-related functions
//
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn lua_setwarnf(L: *mut lua_State, f: Option<lua_WarnFunction>, ud: *mut c_void);
pub fn lua_warning(L: *mut lua_State, msg: *const c_char, tocont: c_int);
}
@@ -335,12 +324,12 @@ pub const LUA_GCGEN: c_int = 10;
pub const LUA_GCINC: c_int = 11;
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn lua_gc(L: *mut lua_State, what: c_int, ...) -> c_int;
}
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
//
// Miscellaneous functions
//
@@ -446,10 +435,8 @@ pub unsafe fn lua_isnoneornil(L: *mut lua_State, n: c_int) -> c_int {
}
#[inline(always)]
pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static str) -> *const c_char {
use std::ffi::CString;
let c_str = CString::new(s).unwrap();
lua_pushlstring(L, c_str.as_ptr(), c_str.as_bytes().len())
pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static CStr) {
lua_pushstring(L, s.as_ptr());
}
#[inline(always)]
@@ -457,6 +444,14 @@ pub unsafe fn lua_pushglobaltable(L: *mut lua_State) -> c_int {
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)]
pub unsafe fn lua_tostring(L: *mut lua_State, i: c_int) -> *const c_char {
lua_tolstring(L, i, ptr::null_mut())
@@ -524,7 +519,7 @@ pub const LUA_MASKCOUNT: c_int = 1 << (LUA_HOOKCOUNT as usize);
pub type lua_Hook = unsafe extern "C-unwind" fn(L: *mut lua_State, ar: *mut lua_Debug);
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn lua_getstack(L: *mut lua_State, level: c_int, ar: *mut lua_Debug) -> c_int;
pub fn lua_getinfo(L: *mut lua_State, what: *const c_char, ar: *mut lua_Debug) -> c_int;
pub fn lua_getlocal(L: *mut lua_State, ar: *const lua_Debug, n: c_int) -> *const c_char;
+11 -11
View File
@@ -1,21 +1,21 @@
//! Contains definitions from `lualib.h`.
use std::os::raw::c_int;
use std::os::raw::{c_char, c_int};
use super::lua::lua_State;
pub const LUA_COLIBNAME: &str = "coroutine";
pub const LUA_TABLIBNAME: &str = "table";
pub const LUA_IOLIBNAME: &str = "io";
pub const LUA_OSLIBNAME: &str = "os";
pub const LUA_STRLIBNAME: &str = "string";
pub const LUA_UTF8LIBNAME: &str = "utf8";
pub const LUA_MATHLIBNAME: &str = "math";
pub const LUA_DBLIBNAME: &str = "debug";
pub const LUA_LOADLIBNAME: &str = "package";
pub const LUA_COLIBNAME: *const c_char = cstr!("coroutine");
pub const LUA_TABLIBNAME: *const c_char = cstr!("table");
pub const LUA_IOLIBNAME: *const c_char = cstr!("io");
pub const LUA_OSLIBNAME: *const c_char = cstr!("os");
pub const LUA_STRLIBNAME: *const c_char = cstr!("string");
pub const LUA_UTF8LIBNAME: *const c_char = cstr!("utf8");
pub const LUA_MATHLIBNAME: *const c_char = cstr!("math");
pub const LUA_DBLIBNAME: *const c_char = cstr!("debug");
pub const LUA_LOADLIBNAME: *const c_char = cstr!("package");
#[cfg_attr(all(windows, raw_dylib), link(name = "lua54", kind = "raw-dylib"))]
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn luaopen_base(L: *mut lua_State) -> c_int;
pub fn luaopen_coroutine(L: *mut lua_State) -> c_int;
pub fn luaopen_table(L: *mut lua_State) -> c_int;
+105 -63
View File
@@ -1,16 +1,17 @@
//! MLua compatibility layer for Roblox Luau.
//! MLua compatibility layer for Luau.
//!
//! Based on github.com/keplerproject/lua-compat-5.3
use std::ffi::CStr;
use std::mem;
use std::os::raw::{c_char, c_int, c_void};
use std::ptr;
use std::{mem, ptr};
use super::lauxlib::*;
use super::lua::*;
use super::luacode::*;
pub const LUA_RESUMEERROR: c_int = -1;
unsafe fn compat53_reverse(L: *mut lua_State, mut a: c_int, mut b: c_int) {
while a < b {
lua_pushvalue(L, a);
@@ -42,7 +43,7 @@ unsafe fn compat53_findfield(L: *mut lua_State, objidx: c_int, level: c_int) ->
} else if compat53_findfield(L, objidx, level - 1) != 0 {
// try recursively
lua_remove(L, -2); // remove table (but keep name)
lua_pushliteral(L, ".");
lua_pushliteral(L, c".");
lua_insert(L, -2); // place '.' between the two names
lua_concat(L, 3);
return 1;
@@ -53,11 +54,7 @@ unsafe fn compat53_findfield(L: *mut lua_State, objidx: c_int, level: c_int) ->
0 // not found
}
unsafe fn compat53_pushglobalfuncname(
L: *mut lua_State,
level: c_int,
ar: *mut lua_Debug,
) -> c_int {
unsafe fn compat53_pushglobalfuncname(L: *mut lua_State, level: c_int, ar: *mut lua_Debug) -> c_int {
let top = lua_gettop(L);
// push function
lua_getinfo(L, level, cstr!("f"), ar);
@@ -80,7 +77,7 @@ unsafe fn compat53_pushfuncname(L: *mut lua_State, level: c_int, ar: *mut lua_De
lua_pushfstring(L, cstr!("function '%s'"), lua_tostring(L, -1));
lua_remove(L, -2); // remove name
} else {
lua_pushliteral(L, "?");
lua_pushliteral(L, c"?");
}
}
@@ -113,7 +110,7 @@ pub unsafe fn lua_rotate(L: *mut lua_State, mut idx: c_int, mut n: c_int) {
#[inline(always)]
pub unsafe fn lua_copy(L: *mut lua_State, fromidx: c_int, toidx: c_int) {
let abs_to = lua_absindex(L, toidx);
luaL_checkstack(L, 1, cstr!("not enough stack slots"));
luaL_checkstack(L, 1, cstr!("not enough stack slots available"));
lua_pushvalue(L, fromidx);
lua_replace(L, abs_to);
}
@@ -130,6 +127,11 @@ pub unsafe fn lua_isinteger(L: *mut lua_State, idx: c_int) -> c_int {
0
}
#[inline(always)]
pub unsafe fn lua_pushinteger(L: *mut lua_State, i: lua_Integer) {
lua_pushnumber(L, i as lua_Number);
}
#[inline(always)]
pub unsafe fn lua_tointeger(L: *mut lua_State, i: c_int) -> lua_Integer {
lua_tointegerx(L, i, ptr::null_mut())
@@ -181,6 +183,7 @@ pub unsafe fn lua_geti(L: *mut lua_State, mut idx: c_int, n: lua_Integer) -> c_i
#[inline(always)]
pub unsafe fn lua_rawgeti(L: *mut lua_State, idx: c_int, n: lua_Integer) -> c_int {
let n = n.try_into().expect("cannot convert index from lua_Integer");
lua_rawgeti_(L, idx, n)
}
@@ -195,7 +198,7 @@ pub unsafe fn lua_rawgetp(L: *mut lua_State, idx: c_int, p: *const c_void) -> c_
pub unsafe fn lua_getuservalue(L: *mut lua_State, mut idx: c_int) -> c_int {
luaL_checkstack(L, 2, cstr!("not enough stack slots available"));
idx = lua_absindex(L, idx);
lua_pushliteral(L, "__mlua_uservalues");
lua_pushliteral(L, c"__mlua_uservalues");
if lua_rawget(L, LUA_REGISTRYINDEX) != LUA_TTABLE {
return LUA_TNIL;
}
@@ -216,13 +219,14 @@ pub unsafe fn lua_seti(L: *mut lua_State, mut idx: c_int, n: lua_Integer) {
#[inline(always)]
pub unsafe fn lua_rawseti(L: *mut lua_State, idx: c_int, n: lua_Integer) {
let n = n.try_into().expect("cannot convert index from lua_Integer");
lua_rawseti_(L, idx, n)
}
#[inline(always)]
pub unsafe fn lua_rawsetp(L: *mut lua_State, idx: c_int, p: *const c_void) {
let abs_i = lua_absindex(L, idx);
luaL_checkstack(L, 1, cstr!("not enough stack slots"));
luaL_checkstack(L, 1, cstr!("not enough stack slots available"));
lua_pushlightuserdata(L, p as *mut c_void);
lua_insert(L, -2);
lua_rawset(L, abs_i);
@@ -232,13 +236,13 @@ pub unsafe fn lua_rawsetp(L: *mut lua_State, idx: c_int, p: *const c_void) {
pub unsafe fn lua_setuservalue(L: *mut lua_State, mut idx: c_int) {
luaL_checkstack(L, 4, cstr!("not enough stack slots available"));
idx = lua_absindex(L, idx);
lua_pushliteral(L, "__mlua_uservalues");
lua_pushliteral(L, c"__mlua_uservalues");
lua_pushvalue(L, -1);
if lua_rawget(L, LUA_REGISTRYINDEX) != LUA_TTABLE {
lua_pop(L, 1);
lua_createtable(L, 0, 2); // main table
lua_createtable(L, 0, 1); // metatable
lua_pushliteral(L, "k");
lua_pushliteral(L, c"k");
lua_setfield(L, -2, cstr!("__mode"));
lua_setmetatable(L, -2);
lua_pushvalue(L, -2);
@@ -281,12 +285,7 @@ pub unsafe fn lua_pushglobaltable(L: *mut lua_State) {
}
#[inline(always)]
pub unsafe fn lua_resume(
L: *mut lua_State,
from: *mut lua_State,
narg: c_int,
nres: *mut c_int,
) -> c_int {
pub unsafe fn lua_resume(L: *mut lua_State, from: *mut lua_State, narg: c_int, nres: *mut c_int) -> c_int {
let ret = lua_resume_(L, from, narg);
if (ret == LUA_OK || ret == LUA_YIELD) && !(nres.is_null()) {
*nres = lua_gettop(L);
@@ -294,6 +293,19 @@ pub unsafe fn lua_resume(
ret
}
#[inline(always)]
pub unsafe fn lua_resumex(L: *mut lua_State, from: *mut lua_State, narg: c_int, nres: *mut c_int) -> c_int {
let ret = if narg == LUA_RESUMEERROR {
lua_resumeerror(L, from)
} else {
lua_resume_(L, from, narg)
};
if (ret == LUA_OK || ret == LUA_YIELD) && !(nres.is_null()) {
*nres = lua_gettop(L);
}
ret
}
//
// lauxlib ported functions
//
@@ -304,12 +316,30 @@ pub unsafe fn luaL_checkstack(L: *mut lua_State, sz: c_int, msg: *const c_char)
if !msg.is_null() {
luaL_error(L, cstr!("stack overflow (%s)"), msg);
} else {
lua_pushliteral(L, "stack overflow");
lua_pushliteral(L, c"stack overflow");
lua_error(L);
}
}
}
#[inline(always)]
pub unsafe fn luaL_checkinteger(L: *mut lua_State, narg: c_int) -> lua_Integer {
let mut isnum = 0;
let int = lua_tointegerx(L, narg, &mut isnum);
if isnum == 0 {
luaL_typeerror(L, narg, lua_typename(L, LUA_TNUMBER));
}
int
}
pub unsafe fn luaL_optinteger(L: *mut lua_State, narg: c_int, def: lua_Integer) -> lua_Integer {
if lua_isnoneornil(L, narg) != 0 {
def
} else {
luaL_checkinteger(L, narg)
}
}
#[inline(always)]
pub unsafe fn luaL_getmetafield(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int {
if luaL_getmetafield_(L, obj, e) != 0 {
@@ -330,50 +360,72 @@ pub unsafe fn luaL_newmetatable(L: *mut lua_State, tname: *const c_char) -> c_in
}
}
pub unsafe fn luaL_loadbufferx(
pub unsafe fn luaL_loadbufferenv(
L: *mut lua_State,
data: *const c_char,
mut size: usize,
name: *const c_char,
mode: *const c_char,
mut env: c_int,
) -> c_int {
extern "C" {
unsafe extern "C" {
fn free(p: *mut c_void);
}
unsafe extern "C" fn data_dtor(_: *mut lua_State, data: *mut c_void) {
free(*(data as *mut *mut c_char) as *mut c_void);
}
let chunk_is_text = size == 0 || (*data as u8) >= b'\t';
if !mode.is_null() {
let modeb = CStr::from_ptr(mode).to_bytes();
if !chunk_is_text && !modeb.contains(&b'b') {
lua_pushfstring(
L,
cstr!("attempt to load a binary chunk (mode is '%s')"),
mode,
);
lua_pushfstring(L, cstr!("attempt to load a binary chunk (mode is '%s')"), mode);
return LUA_ERRSYNTAX;
} else if chunk_is_text && !modeb.contains(&b't') {
lua_pushfstring(
L,
cstr!("attempt to load a text chunk (mode is '%s')"),
mode,
);
lua_pushfstring(L, cstr!("attempt to load a text chunk (mode is '%s')"), mode);
return LUA_ERRSYNTAX;
}
}
if chunk_is_text {
let data = luau_compile_(data, size, ptr::null_mut(), &mut size);
let ok = luau_load(L, name, data, size, 0) == 0;
free(data as *mut c_void);
if !ok {
return LUA_ERRSYNTAX;
let status = if chunk_is_text {
if env < 0 {
env -= 1;
}
let data_ud = lua_newuserdatadtor(L, mem::size_of::<*mut c_char>(), data_dtor) as *mut *mut c_char;
let data = luau_compile_(data, size, ptr::null_mut(), &mut size);
ptr::write(data_ud, data);
// By deferring the `free(data)` to the userdata destructor, we ensure that
// even if `luau_load` throws an error, the `data` is still released.
let status = luau_load(L, name, data, size, env);
lua_replace(L, -2); // replace data with the result
status
} else {
luau_load(L, name, data, size, env)
};
if status != 0 {
if lua_isstring(L, -1) != 0 && CStr::from_ptr(lua_tostring(L, -1)) == c"not enough memory" {
// A case for Luau >= 0.679
return LUA_ERRMEM;
}
} else if luau_load(L, name, data, size, 0) != 0 {
return LUA_ERRSYNTAX;
}
LUA_OK
}
#[inline(always)]
pub unsafe fn luaL_loadbufferx(
L: *mut lua_State,
data: *const c_char,
size: usize,
name: *const c_char,
mode: *const c_char,
) -> c_int {
luaL_loadbufferenv(L, data, size, name, mode, 0)
}
#[inline(always)]
pub unsafe fn luaL_loadbuffer(
L: *mut lua_State,
@@ -381,13 +433,13 @@ pub unsafe fn luaL_loadbuffer(
size: usize,
name: *const c_char,
) -> c_int {
luaL_loadbufferx(L, data, size, name, ptr::null())
luaL_loadbufferenv(L, data, size, name, ptr::null(), 0)
}
#[inline(always)]
pub unsafe fn luaL_len(L: *mut lua_State, idx: c_int) -> lua_Integer {
let mut isnum = 0;
luaL_checkstack(L, 1, cstr!("not enough stack slots"));
luaL_checkstack(L, 1, cstr!("not enough stack slots available"));
lua_len(L, idx);
let res = lua_tointegerx(L, -1, &mut isnum);
lua_pop(L, 1);
@@ -397,12 +449,7 @@ pub unsafe fn luaL_len(L: *mut lua_State, idx: c_int) -> lua_Integer {
res
}
pub unsafe fn luaL_traceback(
L: *mut lua_State,
L1: *mut lua_State,
msg: *const c_char,
mut level: c_int,
) {
pub unsafe fn luaL_traceback(L: *mut lua_State, L1: *mut lua_State, msg: *const c_char, mut level: c_int) {
let mut ar: lua_Debug = mem::zeroed();
let top = lua_gettop(L);
let numlevels = lua_stackdepth(L);
@@ -415,11 +462,11 @@ pub unsafe fn luaL_traceback(
if !msg.is_null() {
lua_pushfstring(L, cstr!("%s\n"), msg);
}
lua_pushliteral(L, "stack traceback:");
lua_pushliteral(L, c"stack traceback:");
while lua_getinfo(L1, level, cstr!(""), &mut ar) != 0 {
if level + 1 == mark {
// too many levels?
lua_pushliteral(L, "\n\t..."); // add a '...'
lua_pushliteral(L, c"\n\t..."); // add a '...'
level = numlevels - COMPAT53_LEVELS2; // and skip to last ones
} else {
lua_getinfo(L1, level, cstr!("sln"), &mut ar);
@@ -427,7 +474,7 @@ pub unsafe fn luaL_traceback(
if ar.currentline > 0 {
lua_pushfstring(L, cstr!("%d:"), ar.currentline);
}
lua_pushliteral(L, " in ");
lua_pushliteral(L, c" in ");
compat53_pushfuncname(L, level, &mut ar);
lua_concat(L, lua_gettop(L) - top);
}
@@ -441,16 +488,16 @@ pub unsafe fn luaL_tolstring(L: *mut lua_State, mut idx: c_int, len: *mut usize)
if luaL_callmeta(L, idx, cstr!("__tostring")) == 0 {
match lua_type(L, idx) {
LUA_TNIL => {
lua_pushliteral(L, "nil");
lua_pushliteral(L, c"nil");
}
LUA_TSTRING | LUA_TNUMBER => {
lua_pushvalue(L, idx);
}
LUA_TBOOLEAN => {
if lua_toboolean(L, idx) == 0 {
lua_pushliteral(L, "false");
lua_pushliteral(L, c"false");
} else {
lua_pushliteral(L, "true");
lua_pushliteral(L, c"true");
}
}
t => {
@@ -474,14 +521,14 @@ pub unsafe fn luaL_tolstring(L: *mut lua_State, mut idx: c_int, len: *mut usize)
#[inline(always)]
pub unsafe fn luaL_setmetatable(L: *mut lua_State, tname: *const c_char) {
luaL_checkstack(L, 1, cstr!("not enough stack slots"));
luaL_checkstack(L, 1, cstr!("not enough stack slots available"));
luaL_getmetatable(L, tname);
lua_setmetatable(L, -2);
}
pub unsafe fn luaL_getsubtable(L: *mut lua_State, idx: c_int, fname: *const c_char) -> c_int {
let abs_i = lua_absindex(L, idx);
luaL_checkstack(L, 3, cstr!("not enough stack slots"));
luaL_checkstack(L, 3, cstr!("not enough stack slots available"));
lua_pushstring_(L, fname);
if lua_gettable(L, abs_i) == LUA_TTABLE {
return 1;
@@ -494,12 +541,7 @@ pub unsafe fn luaL_getsubtable(L: *mut lua_State, idx: c_int, fname: *const c_ch
0
}
pub unsafe fn luaL_requiref(
L: *mut lua_State,
modname: *const c_char,
openf: lua_CFunction,
glb: c_int,
) {
pub unsafe fn luaL_requiref(L: *mut lua_State, modname: *const c_char, openf: lua_CFunction, glb: c_int) {
luaL_checkstack(L, 3, cstr!("not enough stack slots available"));
luaL_getsubtable(L, LUA_REGISTRYINDEX, cstr!("_LOADED"));
if lua_getfield(L, -1, modname) == LUA_TNIL {
+22 -12
View File
@@ -3,9 +3,7 @@
use std::os::raw::{c_char, c_float, c_int, c_void};
use std::ptr;
use super::lua::{
self, lua_CFunction, lua_Integer, lua_Number, lua_State, lua_Unsigned, LUA_REGISTRYINDEX,
};
use super::lua::{self, lua_CFunction, lua_Number, lua_State, lua_Unsigned, LUA_REGISTRYINDEX};
#[repr(C)]
pub struct luaL_Reg {
@@ -13,7 +11,7 @@ pub struct luaL_Reg {
pub func: lua_CFunction,
}
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn luaL_register(L: *mut lua_State, libname: *const c_char, l: *const luaL_Reg);
#[link_name = "luaL_getmetafield"]
pub fn luaL_getmetafield_(L: *mut lua_State, obj: c_int, e: *const c_char) -> c_int;
@@ -35,8 +33,10 @@ extern "C-unwind" {
pub fn luaL_checkboolean(L: *mut lua_State, narg: c_int) -> c_int;
pub fn luaL_optboolean(L: *mut lua_State, narg: c_int, def: c_int) -> c_int;
pub fn luaL_checkinteger(L: *mut lua_State, narg: c_int) -> lua_Integer;
pub fn luaL_optinteger(L: *mut lua_State, narg: c_int, def: lua_Integer) -> lua_Integer;
#[link_name = "luaL_checkinteger"]
pub fn luaL_checkinteger_(L: *mut lua_State, narg: c_int) -> c_int;
#[link_name = "luaL_optinteger"]
pub fn luaL_optinteger_(L: *mut lua_State, narg: c_int, def: c_int) -> c_int;
pub fn luaL_checkunsigned(L: *mut lua_State, narg: c_int) -> lua_Unsigned;
pub fn luaL_optunsigned(L: *mut lua_State, narg: c_int, def: lua_Unsigned) -> lua_Unsigned;
@@ -71,10 +71,17 @@ extern "C-unwind" {
pub fn luaL_newstate() -> *mut lua_State;
// TODO: luaL_findtable
pub fn luaL_findtable(
L: *mut lua_State,
idx: c_int,
fname: *const c_char,
szhint: c_int,
) -> *const c_char;
pub fn luaL_typename(L: *mut lua_State, idx: c_int) -> *const c_char;
pub fn luaL_callyieldable(L: *mut lua_State, nargs: c_int, nresults: c_int) -> c_int;
// sandbox libraries and globals
#[link_name = "luaL_sandbox"]
pub fn luaL_sandbox_(L: *mut lua_State);
@@ -143,10 +150,13 @@ pub unsafe fn luaL_sandbox(L: *mut lua_State, enabled: c_int) {
}
// set all builtin metatables to read-only
lua_pushliteral(L, "");
lua_getmetatable(L, -1);
lua_setreadonly(L, -1, enabled);
lua_pop(L, 2);
lua_pushliteral(L, c"");
if lua_getmetatable(L, -1) != 0 {
lua_setreadonly(L, -1, enabled);
lua_pop(L, 2);
} else {
lua_pop(L, 1);
}
// set globals to readonly and activate safeenv since the env is immutable
lua_setreadonly(L, LUA_GLOBALSINDEX, enabled);
@@ -172,7 +182,7 @@ pub struct luaL_Strbuf {
// For compatibility
pub type luaL_Buffer = luaL_Strbuf;
extern "C-unwind" {
unsafe 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;
+71 -59
View File
@@ -1,5 +1,6 @@
//! Contains definitions from `lua.h`.
use std::ffi::CStr;
use std::marker::{PhantomData, PhantomPinned};
use std::os::raw::{c_char, c_double, c_float, c_int, c_uint, c_void};
use std::{mem, ptr};
@@ -8,7 +9,13 @@ use std::{mem, ptr};
pub const LUA_MULTRET: c_int = -1;
// Max number of Lua stack slots
const LUAI_MAXCSTACK: c_int = 100000;
const LUAI_MAXCSTACK: c_int = 1000000;
// Number of valid Lua userdata tags
pub const LUA_UTAG_LIMIT: c_int = 128;
// Number of valid Lua lightuserdata tags
pub const LUA_LUTAG_LIMIT: c_int = 128;
//
// Pseudo-indices
@@ -63,8 +70,11 @@ pub const LUA_MINSTACK: c_int = 20;
/// A Lua number, usually equivalent to `f64`.
pub type lua_Number = c_double;
/// A Lua integer, equivalent to `i32`.
pub type lua_Integer = c_int;
/// A Lua integer, usually equivalent to `i64`
#[cfg(target_pointer_width = "32")]
pub type lua_Integer = i32;
#[cfg(target_pointer_width = "64")]
pub type lua_Integer = i64;
/// A Lua unsigned integer, equivalent to `u32`.
pub type lua_Unsigned = c_uint;
@@ -73,24 +83,19 @@ pub type lua_Unsigned = c_uint;
pub type lua_CFunction = unsafe extern "C-unwind" fn(L: *mut lua_State) -> c_int;
pub type lua_Continuation = unsafe extern "C-unwind" fn(L: *mut lua_State, status: c_int) -> c_int;
/// Type for userdata destructor functions.
pub type lua_Udestructor = unsafe extern "C-unwind" fn(*mut c_void);
pub type lua_Destructor = unsafe extern "C-unwind" fn(L: *mut lua_State, *mut c_void);
/// Type for userdata destructor functions (no unwinding).
pub type lua_Destructor = unsafe extern "C" fn(L: *mut lua_State, *mut c_void);
/// Type for memory-allocation functions.
pub type lua_Alloc = unsafe extern "C-unwind" fn(
ud: *mut c_void,
ptr: *mut c_void,
osize: usize,
nsize: usize,
) -> *mut c_void;
/// Type for memory-allocation functions (no unwinding).
pub type lua_Alloc =
unsafe extern "C" fn(ud: *mut c_void, ptr: *mut c_void, osize: usize, nsize: usize) -> *mut c_void;
/// Returns Luau release version (eg. `0.xxx`).
pub const fn luau_version() -> Option<&'static str> {
option_env!("LUAU_VERSION")
}
extern "C-unwind" {
unsafe extern "C-unwind" {
//
// State manipulation
//
@@ -134,7 +139,7 @@ extern "C-unwind" {
pub fn lua_tonumberx(L: *mut lua_State, idx: c_int, isnum: *mut c_int) -> lua_Number;
#[link_name = "lua_tointegerx"]
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) -> c_int;
pub fn lua_tounsignedx(L: *mut lua_State, idx: c_int, isnum: *mut c_int) -> lua_Unsigned;
pub fn lua_tovector(L: *mut lua_State, idx: c_int) -> *const c_float;
pub fn lua_toboolean(L: *mut lua_State, idx: c_int) -> c_int;
@@ -144,9 +149,11 @@ extern "C-unwind" {
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_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_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_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_tobuffer(L: *mut lua_State, idx: c_int, len: *mut usize) -> *mut c_void;
pub fn lua_topointer(L: *mut lua_State, idx: c_int) -> *const c_void;
@@ -156,7 +163,8 @@ extern "C-unwind" {
//
pub fn lua_pushnil(L: *mut lua_State);
pub fn lua_pushnumber(L: *mut lua_State, n: lua_Number);
pub fn lua_pushinteger(L: *mut lua_State, n: lua_Integer);
#[link_name = "lua_pushinteger"]
pub fn lua_pushinteger_(L: *mut lua_State, n: c_int);
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);
@@ -179,9 +187,10 @@ extern "C-unwind" {
pub fn lua_pushboolean(L: *mut lua_State, b: 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_newuserdatadtor(L: *mut lua_State, sz: usize, dtor: lua_Udestructor) -> *mut c_void;
pub fn lua_newuserdatataggedwithmetatable(L: *mut lua_State, sz: usize, tag: c_int) -> *mut c_void;
pub fn lua_newuserdatadtor(L: *mut lua_State, sz: usize, dtor: lua_Destructor) -> *mut c_void;
pub fn lua_newbuffer(L: *mut lua_State, sz: usize) -> *mut c_void;
@@ -255,14 +264,14 @@ pub const LUA_GCSETGOAL: c_int = 7;
pub const LUA_GCSETSTEPMUL: c_int = 8;
pub const LUA_GCSETSTEPSIZE: c_int = 9;
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn lua_gc(L: *mut lua_State, what: c_int, data: c_int) -> c_int;
}
//
// Memory statistics
//
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn lua_setmemcat(L: *mut lua_State, category: c_int);
pub fn lua_totalbytes(L: *mut lua_State, category: c_int) -> usize;
}
@@ -270,7 +279,7 @@ extern "C-unwind" {
//
// Miscellaneous functions
//
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn lua_error(L: *mut lua_State) -> !;
pub fn lua_next(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_rawiter(L: *mut lua_State, idx: c_int, iter: c_int) -> c_int;
@@ -280,6 +289,10 @@ extern "C-unwind" {
pub fn lua_setuserdatatag(L: *mut lua_State, idx: c_int, tag: c_int);
pub fn lua_setuserdatadtor(L: *mut lua_State, tag: c_int, dtor: Option<lua_Destructor>);
pub fn lua_getuserdatadtor(L: *mut lua_State, tag: c_int) -> Option<lua_Destructor>;
pub fn lua_setuserdatametatable(L: *mut lua_State, tag: c_int);
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_cleartable(L: *mut lua_State, idx: c_int);
pub fn lua_getallocf(L: *mut lua_State, ud: *mut *mut c_void) -> lua_Alloc;
@@ -291,7 +304,7 @@ extern "C-unwind" {
pub const LUA_NOREF: c_int = -1;
pub const LUA_REFNIL: c_int = 0;
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn lua_ref(L: *mut lua_State, idx: c_int) -> c_int;
pub fn lua_unref(L: *mut lua_State, r#ref: c_int);
}
@@ -301,13 +314,13 @@ extern "C-unwind" {
//
#[inline(always)]
pub unsafe fn lua_tonumber(L: *mut lua_State, i: c_int) -> lua_Number {
lua_tonumberx(L, i, ptr::null_mut())
pub unsafe fn lua_tonumber(L: *mut lua_State, idx: c_int) -> lua_Number {
lua_tonumberx(L, idx, ptr::null_mut())
}
#[inline(always)]
pub unsafe fn lua_tointeger_(L: *mut lua_State, i: c_int) -> lua_Integer {
lua_tointegerx_(L, i, ptr::null_mut())
pub unsafe fn lua_tointeger_(L: *mut lua_State, idx: c_int) -> c_int {
lua_tointegerx_(L, idx, ptr::null_mut())
}
#[inline(always)]
@@ -331,12 +344,14 @@ pub unsafe fn lua_newuserdata(L: *mut lua_State, sz: usize) -> *mut c_void {
}
#[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) {
pub unsafe fn lua_newuserdata_t<T>(L: *mut lua_State, data: T) -> *mut T {
unsafe extern "C" fn destructor<T>(_: *mut lua_State, ud: *mut c_void) {
ptr::drop_in_place(ud as *mut T);
}
lua_newuserdatadtor(L, mem::size_of::<T>(), destructor::<T>) as *mut T
let ud_ptr = lua_newuserdatadtor(L, const { mem::size_of::<T>() }, destructor::<T>) as *mut T;
ptr::write(ud_ptr, data);
ud_ptr
}
// TODO: lua_strlen
@@ -392,33 +407,35 @@ pub unsafe fn lua_isnoneornil(L: *mut lua_State, n: c_int) -> c_int {
}
#[inline(always)]
pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static str) {
use std::ffi::CString;
let c_str = CString::new(s).unwrap();
lua_pushlstring_(L, c_str.as_ptr(), c_str.as_bytes().len())
pub unsafe fn lua_pushliteral(L: *mut lua_State, s: &'static CStr) {
lua_pushstring_(L, s.as_ptr());
}
#[inline(always)]
pub unsafe fn lua_pushcfunction(L: *mut lua_State, f: lua_CFunction) {
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) {
lua_pushcclosurek(L, f, debugname, 0, None)
}
#[inline(always)]
pub unsafe fn lua_pushcclosure(L: *mut lua_State, f: lua_CFunction, nup: c_int) {
lua_pushcclosurek(L, f, ptr::null(), nup, None)
}
pub unsafe fn lua_pushcclosured(
L: *mut lua_State,
f: lua_CFunction,
debugname: *const c_char,
nup: c_int,
) {
#[inline(always)]
pub unsafe fn lua_pushcclosured(L: *mut lua_State, f: lua_CFunction, debugname: *const c_char, nup: c_int) {
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)]
pub unsafe fn lua_setglobal(L: *mut lua_State, var: *const c_char) {
lua_setfield(L, LUA_GLOBALSINDEX, var)
@@ -453,14 +470,9 @@ pub type lua_Coverage = unsafe extern "C-unwind" fn(
size: usize,
);
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn lua_stackdepth(L: *mut lua_State) -> c_int;
pub fn lua_getinfo(
L: *mut lua_State,
level: c_int,
what: *const c_char,
ar: *mut lua_Debug,
) -> c_int;
pub fn lua_getinfo(L: *mut lua_State, level: c_int, what: *const c_char, ar: *mut lua_Debug) -> c_int;
pub fn lua_getargument(L: *mut lua_State, level: c_int, n: c_int) -> c_int;
pub fn lua_getlocal(L: *mut lua_State, level: c_int, n: c_int) -> *const c_char;
pub fn lua_setlocal(L: *mut lua_State, level: c_int, n: c_int) -> *const c_char;
@@ -468,19 +480,9 @@ extern "C-unwind" {
pub fn lua_setupvalue(L: *mut lua_State, funcindex: c_int, n: c_int) -> *const c_char;
pub fn lua_singlestep(L: *mut lua_State, enabled: c_int);
pub fn lua_breakpoint(
L: *mut lua_State,
funcindex: c_int,
line: c_int,
enabled: c_int,
) -> c_int;
pub fn lua_breakpoint(L: *mut lua_State, funcindex: c_int, line: c_int, enabled: c_int) -> c_int;
pub fn lua_getcoverage(
L: *mut lua_State,
funcindex: c_int,
context: *mut c_void,
callback: lua_Coverage,
);
pub fn lua_getcoverage(L: *mut lua_State, funcindex: c_int, context: *mut c_void, callback: lua_Coverage);
pub fn lua_debugtrace(L: *mut lua_State) -> *const c_char;
}
@@ -506,6 +508,7 @@ pub struct lua_Debug {
//
#[repr(C)]
#[non_exhaustive]
pub struct lua_Callbacks {
/// arbitrary userdata pointer that is never overwritten by Luau
pub userdata: *mut c_void,
@@ -528,8 +531,17 @@ pub struct lua_Callbacks {
pub debuginterrupt: Option<unsafe extern "C-unwind" fn(L: *mut lua_State, ar: *mut lua_Debug)>,
/// gets called when protected call results in an error
pub debugprotectederror: Option<unsafe extern "C-unwind" fn(L: *mut lua_State)>,
/// gets called when memory is allocated
pub onallocate: Option<unsafe extern "C-unwind" fn(L: *mut lua_State, osize: usize, nsize: usize)>,
}
extern "C" {
unsafe extern "C" {
pub fn lua_callbacks(L: *mut lua_State) -> *mut lua_Callbacks;
}
// Functions from customization lib
unsafe extern "C" {
pub fn luau_setfflag(name: *const c_char, value: c_int) -> c_int;
pub fn lua_getmetatablepointer(L: *mut lua_State, idx: c_int) -> *const c_void;
}
+74 -3
View File
@@ -1,20 +1,90 @@
//! Contains definitions from `luacode.h`.
use std::marker::{PhantomData, PhantomPinned};
use std::os::raw::{c_char, c_int, c_void};
use std::slice;
use std::{ptr, slice};
#[repr(C)]
#[non_exhaustive]
pub struct lua_CompileOptions {
pub optimizationLevel: c_int,
pub debugLevel: c_int,
pub typeInfoLevel: c_int,
pub coverageLevel: c_int,
pub vectorLib: *const c_char,
pub vectorCtor: *const c_char,
pub vectorType: *const c_char,
pub mutableGlobals: *const *const c_char,
pub userdataTypes: *const *const c_char,
pub librariesWithKnownMembers: *const *const c_char,
pub libraryMemberTypeCallback: Option<lua_LibraryMemberTypeCallback>,
pub libraryMemberConstantCallback: Option<lua_LibraryMemberConstantCallback>,
pub disabledBuiltins: *const *const c_char,
}
extern "C-unwind" {
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(),
librariesWithKnownMembers: ptr::null(),
libraryMemberTypeCallback: None,
libraryMemberConstantCallback: None,
disabledBuiltins: ptr::null(),
}
}
}
#[repr(C)]
pub struct lua_CompileConstant {
_data: [u8; 0],
_marker: PhantomData<(*mut u8, PhantomPinned)>,
}
/// Type table tags
#[doc(hidden)]
#[repr(i32)]
#[non_exhaustive]
pub enum luau_BytecodeType {
Nil = 0,
Boolean,
Number,
String,
Table,
Function,
Thread,
UserData,
Vector,
Buffer,
Any = 15,
}
pub type lua_LibraryMemberTypeCallback =
unsafe extern "C-unwind" fn(library: *const c_char, member: *const c_char) -> c_int;
pub type lua_LibraryMemberConstantCallback = unsafe extern "C-unwind" fn(
library: *const c_char,
member: *const c_char,
constant: *mut lua_CompileConstant,
);
unsafe extern "C" {
pub fn luau_set_compile_constant_nil(cons: *mut lua_CompileConstant);
pub fn luau_set_compile_constant_boolean(cons: *mut lua_CompileConstant, b: c_int);
pub fn luau_set_compile_constant_number(cons: *mut lua_CompileConstant, n: f64);
pub fn luau_set_compile_constant_vector(cons: *mut lua_CompileConstant, x: f32, y: f32, z: f32, w: f32);
pub fn luau_set_compile_constant_string(cons: *mut lua_CompileConstant, s: *const c_char, l: usize);
}
unsafe extern "C-unwind" {
#[link_name = "luau_compile"]
pub fn luau_compile_(
source: *const c_char,
@@ -24,7 +94,7 @@ extern "C-unwind" {
) -> *mut c_char;
}
extern "C" {
unsafe extern "C" {
fn free(p: *mut c_void);
}
@@ -36,6 +106,7 @@ pub unsafe fn luau_compile(source: &[u8], mut options: lua_CompileOptions) -> Ve
&mut options,
&mut outsize,
);
assert!(!data_ptr.is_null(), "luau_compile failed");
let data = slice::from_raw_parts(data_ptr as *mut u8, outsize).to_vec();
free(data_ptr as *mut c_void);
data
+1 -1
View File
@@ -4,7 +4,7 @@ use std::os::raw::c_int;
use super::lua::lua_State;
extern "C-unwind" {
unsafe 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);
+13 -11
View File
@@ -1,20 +1,21 @@
//! Contains definitions from `lualib.h`.
use std::os::raw::c_int;
use std::os::raw::{c_char, c_int};
use super::lua::lua_State;
pub const LUA_COLIBNAME: &str = "coroutine";
pub const LUA_TABLIBNAME: &str = "table";
pub const LUA_OSLIBNAME: &str = "os";
pub const LUA_STRLIBNAME: &str = "string";
pub const LUA_BITLIBNAME: &str = "bit32";
pub const LUA_BUFFERLIBNAME: &str = "buffer";
pub const LUA_UTF8LIBNAME: &str = "utf8";
pub const LUA_MATHLIBNAME: &str = "math";
pub const LUA_DBLIBNAME: &str = "debug";
pub const LUA_COLIBNAME: *const c_char = cstr!("coroutine");
pub const LUA_TABLIBNAME: *const c_char = cstr!("table");
pub const LUA_OSLIBNAME: *const c_char = cstr!("os");
pub const LUA_STRLIBNAME: *const c_char = cstr!("string");
pub const LUA_BITLIBNAME: *const c_char = cstr!("bit32");
pub const LUA_BUFFERLIBNAME: *const c_char = cstr!("buffer");
pub const LUA_UTF8LIBNAME: *const c_char = cstr!("utf8");
pub const LUA_MATHLIBNAME: *const c_char = cstr!("math");
pub const LUA_DBLIBNAME: *const c_char = cstr!("debug");
pub const LUA_VECLIBNAME: *const c_char = cstr!("vector");
extern "C-unwind" {
unsafe extern "C-unwind" {
pub fn luaopen_base(L: *mut lua_State) -> c_int;
pub fn luaopen_coroutine(L: *mut lua_State) -> c_int;
pub fn luaopen_table(L: *mut lua_State) -> c_int;
@@ -25,6 +26,7 @@ extern "C-unwind" {
pub fn luaopen_utf8(L: *mut lua_State) -> c_int;
pub fn luaopen_math(L: *mut lua_State) -> c_int;
pub fn luaopen_debug(L: *mut lua_State) -> c_int;
pub fn luaopen_vector(L: *mut lua_State) -> c_int;
// open all builtin libraries
pub fn luaL_openlibs(L: *mut lua_State);
+177
View File
@@ -0,0 +1,177 @@
//! Contains definitions from `Require.h`.
use std::os::raw::{c_char, c_int, c_void};
use super::lua::lua_State;
pub const LUA_REGISTERED_MODULES_TABLE: *const c_char = cstr!("_REGISTEREDMODULES");
#[repr(C)]
pub enum luarequire_NavigateResult {
Success,
Ambiguous,
NotFound,
}
// Functions returning WriteSuccess are expected to set their size_out argument
// to the number of bytes written to the buffer. If WriteBufferTooSmall is
// returned, size_out should be set to the required buffer size.
#[repr(C)]
pub enum luarequire_WriteResult {
Success,
BufferTooSmall,
Failure,
}
#[repr(C)]
pub struct luarequire_Configuration {
// Returns whether requires are permitted from the given chunkname.
pub is_require_allowed: unsafe extern "C-unwind" fn(
L: *mut lua_State,
ctx: *mut c_void,
requirer_chunkname: *const c_char,
) -> bool,
// Resets the internal state to point at the requirer module.
pub reset: unsafe extern "C-unwind" fn(
L: *mut lua_State,
ctx: *mut c_void,
requirer_chunkname: *const c_char,
) -> luarequire_NavigateResult,
// Resets the internal state to point at an aliased module, given its exact path from a configuration
// file. This function is only called when an alias's path cannot be resolved relative to its
// configuration file.
pub jump_to_alias: unsafe extern "C-unwind" fn(
L: *mut lua_State,
ctx: *mut c_void,
path: *const c_char,
) -> luarequire_NavigateResult,
// Navigates through the context by making mutations to the internal state.
pub to_parent:
unsafe extern "C-unwind" fn(L: *mut lua_State, ctx: *mut c_void) -> luarequire_NavigateResult,
pub to_child: unsafe extern "C-unwind" fn(
L: *mut lua_State,
ctx: *mut c_void,
name: *const c_char,
) -> luarequire_NavigateResult,
// Returns whether the context is currently pointing at a module.
pub is_module_present: unsafe extern "C-unwind" fn(L: *mut lua_State, ctx: *mut c_void) -> bool,
// Provides a chunkname for the current module. This will be accessible through the debug library. This
// function is only called if is_module_present returns true.
pub get_chunkname: unsafe extern "C-unwind" fn(
L: *mut lua_State,
ctx: *mut c_void,
buffer: *mut c_char,
buffer_size: usize,
size_out: *mut usize,
) -> luarequire_WriteResult,
// Provides a loadname that identifies the current module and is passed to load. This function
// is only called if is_module_present returns true.
pub get_loadname: unsafe extern "C-unwind" fn(
L: *mut lua_State,
ctx: *mut c_void,
buffer: *mut c_char,
buffer_size: usize,
size_out: *mut usize,
) -> luarequire_WriteResult,
// Provides a cache key representing the current module. This function is only called if
// is_module_present returns true.
pub get_cache_key: unsafe extern "C-unwind" fn(
L: *mut lua_State,
ctx: *mut c_void,
buffer: *mut c_char,
buffer_size: usize,
size_out: *mut usize,
) -> luarequire_WriteResult,
// Returns whether a configuration file is present in the current context.
// If not, require-by-string will call to_parent until either a configuration file is present or
// NAVIGATE_FAILURE is returned (at root).
pub is_config_present: unsafe extern "C-unwind" fn(L: *mut lua_State, ctx: *mut c_void) -> bool,
// Parses the configuration file in the current context for the given alias and returns its
// value or WRITE_FAILURE if not found. This function is only called if is_config_present
// returns true. If this function pointer is set, get_config must not be set. Opting in to this
// function pointer disables parsing configuration files internally and can be used for finer
// control over the configuration file parsing process.
pub get_alias: Option<
unsafe extern "C-unwind" fn(
L: *mut lua_State,
ctx: *mut c_void,
alias: *const c_char,
buffer: *mut c_char,
buffer_size: usize,
size_out: *mut usize,
) -> luarequire_WriteResult,
>,
// Provides the contents of the configuration file in the current context. This function is only called
// if is_config_present returns true. If this function pointer is set, get_alias must not be set. Opting
// in to this function pointer enables parsing configuration files internally.
pub get_config: Option<
unsafe extern "C-unwind" fn(
L: *mut lua_State,
ctx: *mut c_void,
buffer: *mut c_char,
buffer_size: usize,
size_out: *mut usize,
) -> luarequire_WriteResult,
>,
// Executes the module and places the result on the stack. Returns the number of results placed on the
// stack.
// Returning -1 directs the requiring thread to yield. In this case, this thread should be resumed with
// the module result pushed onto its stack.
pub load: unsafe extern "C-unwind" fn(
L: *mut lua_State,
ctx: *mut c_void,
path: *const c_char,
chunkname: *const c_char,
loadname: *const c_char,
) -> c_int,
}
// Populates function pointers in the given luarequire_Configuration.
pub type luarequire_Configuration_init = unsafe extern "C-unwind" fn(config: *mut luarequire_Configuration);
unsafe extern "C-unwind" {
// Initializes and pushes the require closure onto the stack without registration.
pub fn luarequire_pushrequire(
L: *mut lua_State,
config_init: luarequire_Configuration_init,
ctx: *mut c_void,
) -> c_int;
// Initializes the require library and registers it globally.
pub fn luaopen_require(L: *mut lua_State, config_init: luarequire_Configuration_init, ctx: *mut c_void);
// Initializes and pushes a "proxyrequire" closure onto the stack.
//
// The closure takes two parameters: the string path to resolve and the chunkname of an existing
// module.
pub fn luarequire_pushproxyrequire(
L: *mut lua_State,
config_init: luarequire_Configuration_init,
ctx: *mut c_void,
) -> c_int;
// Registers an aliased require path to a result.
//
// After registration, the given result will always be immediately returned when the given path is
// required.
// Expects the path and table to be passed as arguments on the stack.
pub fn luarequire_registermodule(L: *mut lua_State) -> c_int;
// Clears the entry associated with the given cache key from the require cache.
// Expects the cache key to be passed as an argument on the stack.
pub fn luarequire_clearcacheentry(L: *mut lua_State) -> c_int;
// Clears all entries from the require cache.
pub fn luarequire_clearcache(L: *mut lua_State) -> c_int;
}
+2
View File
@@ -6,6 +6,7 @@ pub use lua::*;
pub use luacode::*;
pub use luacodegen::*;
pub use lualib::*;
pub use luarequire::*;
pub mod compat;
pub mod lauxlib;
@@ -13,3 +14,4 @@ pub mod lua;
pub mod luacode;
pub mod luacodegen;
pub mod lualib;
pub mod luarequire;
+1 -2
View File
@@ -1,7 +1,6 @@
#[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
concat!($s, "\0") as *const str as *const [::std::os::raw::c_char] as *const ::std::os::raw::c_char
};
}
+5 -5
View File
@@ -1,10 +1,10 @@
[package]
name = "mlua_derive"
version = "0.9.0"
version = "0.11.0"
authors = ["Aleksandr Orlenko <zxteam@pm.me>"]
edition = "2021"
description = "Procedural macros for the mlua crate."
repository = "https://github.com/khvzak/mlua"
repository = "https://github.com/mlua-rs/mlua"
keywords = ["lua", "mlua"]
license = "MIT"
@@ -12,13 +12,13 @@ license = "MIT"
proc-macro = true
[features]
macros = ["proc-macro-error", "itertools", "regex", "once_cell"]
macros = ["proc-macro-error2", "itertools", "regex", "once_cell"]
[dependencies]
quote = "1.0"
proc-macro2 = { version = "1.0", features = ["span-locations"] }
proc-macro-error = { version = "1.0", optional = true }
proc-macro-error2 = { version = "2.0.1", optional = true }
syn = { version = "2.0", features = ["full"] }
itertools = { version = "0.11", optional = true }
itertools = { version = "0.14", optional = true }
regex = { version = "1.4", optional = true }
once_cell = { version = "1.0", optional = true }
+6 -7
View File
@@ -3,25 +3,24 @@ 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 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 },
};
let ident_str = ident.to_string();
quote! {
impl #generics ::mlua::FromLua<'_> for #ident #generics #where_clause {
impl #impl_generics ::mlua::FromLua for #ident #ty_generics #where_clause {
#[inline]
fn from_lua(value: ::mlua::Value<'_>, lua: &'_ ::mlua::Lua) -> ::mlua::Result<Self> {
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,
to: #ident_str.to_string(),
message: None,
}),
}
+24 -22
View File
@@ -7,7 +7,7 @@ use syn::{parse_macro_input, ItemFn, LitStr, Result};
#[cfg(feature = "macros")]
use {
crate::chunk::Chunk, proc_macro::TokenTree, proc_macro2::TokenStream as TokenStream2,
proc_macro_error::proc_macro_error,
proc_macro_error2::proc_macro_error,
};
#[derive(Default)]
@@ -51,18 +51,23 @@ pub fn lua_module(attr: TokenStream, item: TokenStream) -> TokenStream {
let func_name = &func.sig.ident;
let module_name = args.name.unwrap_or_else(|| func_name.clone());
let ext_entrypoint_name = Ident::new(&format!("luaopen_{module_name}"), Span::call_site());
let skip_memory_check = args.skip_memory_check;
let skip_memory_check = if args.skip_memory_check {
quote! { lua.skip_memory_check(true); }
} else {
quote! {}
};
let wrapped = quote! {
::mlua::require_module_feature!();
mlua::require_module_feature!();
#func
#[no_mangle]
unsafe extern "C-unwind" fn #ext_entrypoint_name(state: *mut ::mlua::lua_State) -> ::std::os::raw::c_int {
let lua = ::mlua::Lua::init_from_ptr(state);
lua.skip_memory_check(#skip_memory_check);
lua.entrypoint1(#func_name)
unsafe extern "C-unwind" fn #ext_entrypoint_name(state: *mut mlua::lua_State) -> ::std::os::raw::c_int {
mlua::Lua::entrypoint1(state, move |lua| {
#skip_memory_check
#func_name(lua)
})
}
};
@@ -91,19 +96,18 @@ pub fn chunk(input: TokenStream) -> TokenStream {
});
let wrapped_code = quote! {{
use ::mlua::{AsChunk, ChunkMode, Lua, Result, Table};
use mlua::{AsChunk, ChunkMode, Lua, Result, Table};
use ::std::borrow::Cow;
use ::std::cell::Cell;
use ::std::io::Result as IoResult;
use ::std::marker::PhantomData;
struct InnerChunk<'lua, F: FnOnce(&'lua Lua) -> Result<Table<'lua>>>(Cell<Option<F>>, PhantomData<&'lua ()>);
struct InnerChunk<F: FnOnce(&Lua) -> Result<Table>>(Cell<Option<F>>);
impl<'lua, F> AsChunk<'lua, 'static> for InnerChunk<'lua, F>
impl<F> AsChunk for InnerChunk<F>
where
F: FnOnce(&'lua Lua) -> Result<Table<'lua>>,
F: FnOnce(&Lua) -> Result<Table>,
{
fn environment(&self, lua: &'lua Lua) -> Result<Option<Table<'lua>>> {
fn environment(&self, lua: &Lua) -> Result<Option<Table>> {
if #caps_len > 0 {
if let Some(make_env) = self.0.take() {
return make_env(lua).map(Some);
@@ -116,28 +120,26 @@ pub fn chunk(input: TokenStream) -> TokenStream {
Some(ChunkMode::Text)
}
fn source(self) -> IoResult<Cow<'static, [u8]>> {
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>> {
Ok(Cow::Borrowed((#source).as_bytes()))
}
}
fn annotate<'a, F: FnOnce(&'a Lua) -> Result<Table<'a>>>(f: F) -> F { f }
let make_env = annotate(move |lua: &Lua| -> Result<Table> {
let make_env = move |lua: &Lua| -> Result<Table> {
let globals = lua.globals();
let env = lua.create_table()?;
let meta = lua.create_table()?;
meta.raw_set("__index", globals.clone())?;
meta.raw_set("__newindex", globals)?;
meta.raw_set("__index", &globals)?;
meta.raw_set("__newindex", &globals)?;
// Add captured variables
#(#caps)*
env.set_metatable(Some(meta));
env.set_metatable(Some(meta))?;
Ok(env)
});
};
InnerChunk(Cell::new(Some(make_env)), PhantomData)
InnerChunk(Cell::new(Some(make_env)))
}};
wrapped_code.into()
+5 -13
View File
@@ -1,9 +1,6 @@
use std::{
cmp::{Eq, PartialEq},
fmt::{self, Display, Formatter},
iter::IntoIterator,
vec::IntoIter,
};
use std::cmp::{Eq, PartialEq};
use std::fmt::{self, Display, Formatter};
use std::vec::IntoIter;
use itertools::Itertools;
use once_cell::sync::Lazy;
@@ -48,10 +45,7 @@ fn span_pos(span: &Span) -> (Pos, Pos) {
return fallback_span_pos(span);
}
(
Pos::new(start.line, start.column),
Pos::new(end.line, end.column),
)
(Pos::new(start.line, start.column), Pos::new(end.line, end.column))
}
fn parse_pos(span: &Span) -> Option<(usize, usize)> {
@@ -80,9 +74,7 @@ fn parse_pos(span: &Span) -> Option<(usize, usize)> {
fn fallback_span_pos(span: &Span) -> (Pos, Pos) {
let (start, end) = match parse_pos(span) {
Some(v) => v,
None => proc_macro_error::abort_call_site!(
"Cannot retrieve span information; please use nightly"
),
None => proc_macro_error2::abort_call_site!("Cannot retrieve span information; please use nightly"),
};
(Pos::new(1, start), Pos::new(1, end))
}
+4
View File
@@ -0,0 +1,4 @@
imports_granularity = "Module"
max_width = 110
comment_width = 100
wrap_comments = true
+92
View File
@@ -0,0 +1,92 @@
#[cfg(feature = "serde")]
use serde::ser::{Serialize, Serializer};
use crate::state::RawLua;
use crate::types::ValueRef;
/// A Luau buffer type.
///
/// See the buffer [documentation] for more information.
///
/// [documentation]: https://luau.org/library#buffer-library
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
#[derive(Clone, Debug, PartialEq)]
pub struct Buffer(pub(crate) ValueRef);
#[cfg_attr(not(feature = "luau"), allow(unused))]
impl Buffer {
/// Copies the buffer data into a new `Vec<u8>`.
pub fn to_vec(&self) -> Vec<u8> {
let lua = self.0.lua.lock();
self.as_slice(&lua).to_vec()
}
/// Returns the length of the buffer.
pub fn len(&self) -> usize {
let lua = self.0.lua.lock();
self.as_slice(&lua).len()
}
/// Returns `true` if the buffer is empty.
pub fn is_empty(&self) -> bool {
self.len() == 0
}
/// Reads given number of bytes from the buffer at the given offset.
///
/// Offset is 0-based.
#[track_caller]
pub fn read_bytes<const N: usize>(&self, offset: usize) -> [u8; N] {
let lua = self.0.lua.lock();
let data = self.as_slice(&lua);
let mut bytes = [0u8; N];
bytes.copy_from_slice(&data[offset..offset + N]);
bytes
}
/// Writes given bytes to the buffer at the given offset.
///
/// Offset is 0-based.
#[track_caller]
pub fn write_bytes(&self, offset: usize, bytes: &[u8]) {
let lua = self.0.lua.lock();
let data = unsafe {
let (buf, size) = self.as_raw_parts(&lua);
std::slice::from_raw_parts_mut(buf, size)
};
data[offset..offset + bytes.len()].copy_from_slice(bytes);
}
pub(crate) fn as_slice(&self, lua: &RawLua) -> &[u8] {
unsafe {
let (buf, size) = self.as_raw_parts(lua);
std::slice::from_raw_parts(buf, size)
}
}
#[cfg(feature = "luau")]
unsafe fn as_raw_parts(&self, lua: &RawLua) -> (*mut u8, usize) {
let mut size = 0usize;
let buf = ffi::lua_tobuffer(lua.ref_thread(), self.0.index, &mut size);
mlua_assert!(!buf.is_null(), "invalid Luau buffer");
(buf as *mut u8, size)
}
#[cfg(not(feature = "luau"))]
unsafe fn as_raw_parts(&self, lua: &RawLua) -> (*mut u8, usize) {
unreachable!()
}
}
#[cfg(feature = "serde")]
impl Serialize for Buffer {
fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
let lua = self.0.lua.lock();
serializer.serialize_bytes(self.as_slice(&lua))
}
}
#[cfg(feature = "luau")]
impl crate::types::LuaType for Buffer {
const TYPE_ID: std::os::raw::c_int = ffi::LUA_TBUFFER;
}
+419 -144
View File
@@ -2,21 +2,24 @@ use std::borrow::Cow;
use std::collections::HashMap;
use std::ffi::CString;
use std::io::Result as IoResult;
use std::panic::Location;
use std::path::{Path, PathBuf};
use std::string::String as StdString;
use crate::error::{Error, ErrorContext, Result};
use crate::error::{Error, Result};
use crate::function::Function;
use crate::lua::Lua;
use crate::state::{Lua, WeakLua};
use crate::table::Table;
use crate::value::{FromLuaMulti, IntoLua, IntoLuaMulti};
use crate::traits::{FromLuaMulti, IntoLua, IntoLuaMulti};
use crate::value::Value;
/// Trait for types [loadable by Lua] and convertible to a [`Chunk`]
///
/// [loadable by Lua]: https://www.lua.org/manual/5.4/manual.html#3.3.2
/// [`Chunk`]: crate::Chunk
pub trait AsChunk<'lua, 'a> {
pub trait AsChunk {
/// Returns optional chunk name
///
/// See [`Chunk::set_name`] for possible name prefixes.
fn name(&self) -> Option<StdString> {
None
}
@@ -24,7 +27,7 @@ pub trait AsChunk<'lua, 'a> {
/// Returns optional chunk [environment]
///
/// [environment]: https://www.lua.org/manual/5.4/manual.html#2.2
fn environment(&self, lua: &'lua Lua) -> Result<Option<Table<'lua>>> {
fn environment(&self, lua: &Lua) -> Result<Option<Table>> {
let _lua = lua; // suppress warning
Ok(None)
}
@@ -35,73 +38,106 @@ pub trait AsChunk<'lua, 'a> {
}
/// Returns chunk data (can be text or binary)
fn source(self) -> IoResult<Cow<'a, [u8]>>;
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>>
where
Self: 'a;
}
impl<'a> AsChunk<'_, 'a> for &'a str {
fn source(self) -> IoResult<Cow<'a, [u8]>> {
Ok(Cow::Borrowed(self.as_ref()))
}
}
impl AsChunk<'_, 'static> for StdString {
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]>> {
impl AsChunk for &str {
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>>
where
Self: 'a,
{
Ok(Cow::Borrowed(self.as_bytes()))
}
}
impl<'a> AsChunk<'_, 'a> for &'a [u8] {
fn source(self) -> IoResult<Cow<'a, [u8]>> {
impl AsChunk for StdString {
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>> {
Ok(Cow::Owned(self.clone().into_bytes()))
}
}
impl AsChunk for &StdString {
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>>
where
Self: 'a,
{
Ok(Cow::Borrowed(self.as_bytes()))
}
}
impl AsChunk for &[u8] {
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>>
where
Self: 'a,
{
Ok(Cow::Borrowed(self))
}
}
impl AsChunk<'_, 'static> for Vec<u8> {
fn source(self) -> IoResult<Cow<'static, [u8]>> {
Ok(Cow::Owned(self))
impl AsChunk for Vec<u8> {
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>> {
Ok(Cow::Owned(self.clone()))
}
}
impl<'a> AsChunk<'_, 'a> for &'a Vec<u8> {
fn source(self) -> IoResult<Cow<'a, [u8]>> {
Ok(Cow::Borrowed(self.as_ref()))
impl AsChunk for &Vec<u8> {
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>>
where
Self: 'a,
{
Ok(Cow::Borrowed(self))
}
}
impl AsChunk<'_, 'static> for &Path {
impl AsChunk for &Path {
fn name(&self) -> Option<StdString> {
Some(format!("@{}", self.display()))
}
fn source(self) -> IoResult<Cow<'static, [u8]>> {
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>> {
std::fs::read(self).map(Cow::Owned)
}
}
impl AsChunk<'_, 'static> for PathBuf {
impl AsChunk for PathBuf {
fn name(&self) -> Option<StdString> {
Some(format!("@{}", self.display()))
}
fn source(self) -> IoResult<Cow<'static, [u8]>> {
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>> {
std::fs::read(self).map(Cow::Owned)
}
}
impl<C: AsChunk + ?Sized> AsChunk for Box<C> {
fn name(&self) -> Option<StdString> {
(**self).name()
}
fn environment(&self, lua: &Lua) -> Result<Option<Table>> {
(**self).environment(lua)
}
fn mode(&self) -> Option<ChunkMode> {
(**self).mode()
}
fn source<'a>(&self) -> IoResult<Cow<'a, [u8]>>
where
Self: 'a,
{
(**self).source()
}
}
/// Returned from [`Lua::load`] and is used to finalize loading and executing Lua main chunks.
///
/// [`Lua::load`]: crate::Lua::load
#[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(crate) lua: &'lua Lua,
pub struct Chunk<'a> {
pub(crate) lua: WeakLua,
pub(crate) name: StdString,
pub(crate) env: Result<Option<Table<'lua>>>,
pub(crate) env: Result<Option<Table>>,
pub(crate) mode: Option<ChunkMode>,
pub(crate) source: IoResult<Cow<'a, [u8]>>,
#[cfg(feature = "luau")]
@@ -115,6 +151,49 @@ pub enum ChunkMode {
Binary,
}
/// Represents a constant value that can be used by Luau compiler.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
#[derive(Clone, Debug)]
pub enum CompileConstant {
Nil,
Boolean(bool),
Number(crate::Number),
Vector(crate::Vector),
String(StdString),
}
#[cfg(any(feature = "luau", doc))]
impl From<bool> for CompileConstant {
fn from(b: bool) -> Self {
CompileConstant::Boolean(b)
}
}
#[cfg(any(feature = "luau", doc))]
impl From<crate::Number> for CompileConstant {
fn from(n: crate::Number) -> Self {
CompileConstant::Number(n)
}
}
#[cfg(any(feature = "luau", doc))]
impl From<crate::Vector> for CompileConstant {
fn from(v: crate::Vector) -> Self {
CompileConstant::Vector(v)
}
}
#[cfg(any(feature = "luau", doc))]
impl From<&str> for CompileConstant {
fn from(s: &str) -> Self {
CompileConstant::String(s.to_owned())
}
}
#[cfg(any(feature = "luau", doc))]
type LibraryMemberConstantMap = HashMap<(StdString, StdString), CompileConstant>;
/// Luau compiler
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
@@ -122,17 +201,22 @@ pub enum ChunkMode {
pub struct Compiler {
optimization_level: u8,
debug_level: u8,
type_info_level: u8,
coverage_level: u8,
vector_lib: Option<String>,
vector_ctor: Option<String>,
vector_type: Option<String>,
mutable_globals: Vec<String>,
vector_lib: Option<StdString>,
vector_ctor: Option<StdString>,
vector_type: Option<StdString>,
mutable_globals: Vec<StdString>,
userdata_types: Vec<StdString>,
libraries_with_known_members: Vec<StdString>,
library_constants: Option<LibraryMemberConstantMap>,
disabled_builtins: Vec<StdString>,
}
#[cfg(any(feature = "luau", doc))]
impl Default for Compiler {
fn default() -> Self {
Self::new()
const { Self::new() }
}
}
@@ -144,11 +228,16 @@ impl Compiler {
Compiler {
optimization_level: 1,
debug_level: 1,
type_info_level: 0,
coverage_level: 0,
vector_lib: None,
vector_ctor: None,
vector_type: None,
mutable_globals: Vec::new(),
userdata_types: Vec::new(),
libraries_with_known_members: Vec::new(),
library_constants: None,
disabled_builtins: Vec::new(),
}
}
@@ -176,6 +265,17 @@ impl Compiler {
self
}
/// Sets Luau type information level used to guide native code generation decisions.
///
/// Possible values:
/// * 0 - generate for native modules (default)
/// * 1 - generate for all modules
#[must_use]
pub const fn set_type_info_level(mut self, level: u8) -> Self {
self.type_info_level = level;
self
}
/// Sets Luau compiler code coverage level.
///
/// Possible values:
@@ -188,39 +288,113 @@ impl Compiler {
self
}
/// Sets alternative global builtin to construct vectors, in addition to default builtin
/// `vector.create`.
///
/// To set the library and method name, use the `lib.ctor` format.
#[doc(hidden)]
#[must_use]
pub fn set_vector_lib(mut self, lib: impl Into<String>) -> Self {
self.vector_lib = Some(lib.into());
pub fn set_vector_ctor(mut self, ctor: impl Into<StdString>) -> Self {
let ctor = ctor.into();
let lib_ctor = ctor.split_once('.');
self.vector_lib = lib_ctor.as_ref().map(|&(lib, _)| lib.to_owned());
self.vector_ctor = (lib_ctor.as_ref())
.map(|&(_, ctor)| ctor.to_owned())
.or(Some(ctor));
self
}
/// Sets alternative vector type name for type tables, in addition to default type `vector`.
#[doc(hidden)]
#[must_use]
pub fn set_vector_ctor(mut self, ctor: impl Into<String>) -> Self {
self.vector_ctor = Some(ctor.into());
self
}
#[doc(hidden)]
#[must_use]
pub fn set_vector_type(mut self, r#type: impl Into<String>) -> Self {
pub fn set_vector_type(mut self, r#type: impl Into<StdString>) -> Self {
self.vector_type = Some(r#type.into());
self
}
/// Adds a mutable global.
///
/// It disables the import optimization for fields accessed through it.
#[must_use]
pub fn add_mutable_global(mut self, global: impl Into<StdString>) -> Self {
self.mutable_globals.push(global.into());
self
}
/// Sets a list of globals that are mutable.
///
/// It disables the import optimization for fields accessed through these.
#[must_use]
pub fn set_mutable_globals(mut self, globals: Vec<String>) -> Self {
self.mutable_globals = globals;
pub fn set_mutable_globals<S: Into<StdString>>(mut self, globals: impl IntoIterator<Item = S>) -> Self {
self.mutable_globals = globals.into_iter().map(|s| s.into()).collect();
self
}
/// Adds a userdata type to the list that will be included in the type information.
#[must_use]
pub fn add_userdata_type(mut self, r#type: impl Into<StdString>) -> Self {
self.userdata_types.push(r#type.into());
self
}
/// Sets a list of userdata types that will be included in the type information.
#[must_use]
pub fn set_userdata_types<S: Into<StdString>>(mut self, types: impl IntoIterator<Item = S>) -> Self {
self.userdata_types = types.into_iter().map(|s| s.into()).collect();
self
}
/// Adds a constant for a known library member.
///
/// The constants are used by the compiler to optimize the generated bytecode.
/// Optimization level must be at least 2 for this to have any effect.
///
/// The `name` is a string in the format `lib.member`, where `lib` is the library name
/// and `member` is the member (constant) name.
#[must_use]
pub fn add_library_constant(
mut self,
name: impl AsRef<str>,
r#const: impl Into<CompileConstant>,
) -> Self {
let Some((lib, member)) = name.as_ref().split_once('.') else {
return self;
};
let (lib, member) = (lib.to_owned(), member.to_owned());
if !self.libraries_with_known_members.contains(&lib) {
self.libraries_with_known_members.push(lib.clone());
}
self.library_constants
.get_or_insert_with(HashMap::new)
.insert((lib, member), r#const.into());
self
}
/// Adds a builtin that should be disabled.
#[must_use]
pub fn add_disabled_builtin(mut self, builtin: impl Into<StdString>) -> Self {
self.disabled_builtins.push(builtin.into());
self
}
/// Sets a list of builtins that should be disabled.
#[must_use]
pub fn set_disabled_builtins<S: Into<StdString>>(
mut self,
builtins: impl IntoIterator<Item = S>,
) -> Self {
self.disabled_builtins = builtins.into_iter().map(|s| s.into()).collect();
self
}
/// Compiles the `source` into bytecode.
pub fn compile(&self, source: impl AsRef<[u8]>) -> Vec<u8> {
use std::os::raw::c_int;
///
/// Returns [`Error::SyntaxError`] if the source code is invalid.
pub fn compile(&self, source: impl AsRef<[u8]>) -> Result<Vec<u8>> {
use std::cell::RefCell;
use std::ffi::CStr;
use std::os::raw::{c_char, c_int};
use std::ptr;
let vector_lib = self.vector_lib.clone();
@@ -233,48 +407,126 @@ impl Compiler {
let vector_type = vector_type.and_then(|t| CString::new(t).ok());
let vector_type = vector_type.as_ref();
let mutable_globals = self
.mutable_globals
.iter()
.map(|name| CString::new(name.clone()).ok())
.collect::<Option<Vec<_>>>()
.unwrap_or_default();
let mut mutable_globals = mutable_globals
.iter()
.map(|s| s.as_ptr())
.collect::<Vec<_>>();
let mut mutable_globals_ptr = ptr::null();
if !mutable_globals.is_empty() {
mutable_globals.push(ptr::null());
mutable_globals_ptr = mutable_globals.as_ptr();
macro_rules! vec2cstring_ptr {
($name:ident, $name_ptr:ident) => {
let $name = self
.$name
.iter()
.map(|name| CString::new(name.clone()).ok())
.collect::<Option<Vec<_>>>()
.unwrap_or_default();
let mut $name = $name.iter().map(|s| s.as_ptr()).collect::<Vec<_>>();
let mut $name_ptr = ptr::null();
if !$name.is_empty() {
$name.push(ptr::null());
$name_ptr = $name.as_ptr();
}
};
}
unsafe {
let options = ffi::lua_CompileOptions {
optimizationLevel: self.optimization_level as c_int,
debugLevel: self.debug_level as c_int,
coverageLevel: self.coverage_level as c_int,
vectorLib: vector_lib.map_or(ptr::null(), |s| s.as_ptr()),
vectorCtor: vector_ctor.map_or(ptr::null(), |s| s.as_ptr()),
vectorType: vector_type.map_or(ptr::null(), |s| s.as_ptr()),
mutableGlobals: mutable_globals_ptr,
};
ffi::luau_compile(source.as_ref(), options)
vec2cstring_ptr!(mutable_globals, mutable_globals_ptr);
vec2cstring_ptr!(userdata_types, userdata_types_ptr);
vec2cstring_ptr!(libraries_with_known_members, libraries_with_known_members_ptr);
vec2cstring_ptr!(disabled_builtins, disabled_builtins_ptr);
thread_local! {
static LIBRARY_MEMBER_CONSTANT_MAP: RefCell<LibraryMemberConstantMap> = Default::default();
}
#[cfg(feature = "luau")]
unsafe extern "C-unwind" fn library_member_constant_callback(
library: *const c_char,
member: *const c_char,
constant: *mut ffi::lua_CompileConstant,
) {
let library = CStr::from_ptr(library).to_string_lossy();
let member = CStr::from_ptr(member).to_string_lossy();
LIBRARY_MEMBER_CONSTANT_MAP.with_borrow(|map| {
if let Some(cons) = map.get(&(library.to_string(), member.to_string())) {
match cons {
CompileConstant::Nil => ffi::luau_set_compile_constant_nil(constant),
CompileConstant::Boolean(b) => {
ffi::luau_set_compile_constant_boolean(constant, *b as c_int)
}
CompileConstant::Number(n) => ffi::luau_set_compile_constant_number(constant, *n),
CompileConstant::Vector(v) => {
#[cfg(not(feature = "luau-vector4"))]
ffi::luau_set_compile_constant_vector(constant, v.x(), v.y(), v.z(), 0.0);
#[cfg(feature = "luau-vector4")]
ffi::luau_set_compile_constant_vector(constant, v.x(), v.y(), v.z(), v.w());
}
CompileConstant::String(s) => ffi::luau_set_compile_constant_string(
constant,
s.as_ptr() as *const c_char,
s.len(),
),
}
}
})
}
let bytecode = unsafe {
let mut options = ffi::lua_CompileOptions::default();
options.optimizationLevel = self.optimization_level as c_int;
options.debugLevel = self.debug_level as c_int;
options.typeInfoLevel = self.type_info_level as c_int;
options.coverageLevel = self.coverage_level as c_int;
options.vectorLib = vector_lib.map_or(ptr::null(), |s| s.as_ptr());
options.vectorCtor = vector_ctor.map_or(ptr::null(), |s| s.as_ptr());
options.vectorType = vector_type.map_or(ptr::null(), |s| s.as_ptr());
options.mutableGlobals = mutable_globals_ptr;
options.userdataTypes = userdata_types_ptr;
options.librariesWithKnownMembers = libraries_with_known_members_ptr;
if let Some(map) = self.library_constants.as_ref() {
if !self.libraries_with_known_members.is_empty() {
LIBRARY_MEMBER_CONSTANT_MAP.with_borrow_mut(|gmap| *gmap = map.clone());
options.libraryMemberConstantCallback = Some(library_member_constant_callback);
}
}
options.disabledBuiltins = disabled_builtins_ptr;
ffi::luau_compile(source.as_ref(), options)
};
if bytecode.first() == Some(&0) {
// The rest of the bytecode is the error message starting with `:`
// See https://github.com/luau-lang/luau/blob/0.640/Compiler/src/Compiler.cpp#L4336
let message = StdString::from_utf8_lossy(&bytecode[2..]).into_owned();
return Err(Error::SyntaxError {
incomplete_input: message.ends_with("<eof>"),
message,
});
}
Ok(bytecode)
}
}
impl<'lua, 'a> Chunk<'lua, 'a> {
impl Chunk<'_> {
/// Returns the name of this chunk.
pub fn name(&self) -> &str {
&self.name
}
/// Sets the name of this chunk, which results in more informative error traces.
pub fn set_name(mut self, name: impl Into<String>) -> Self {
///
/// Possible name prefixes:
/// - `@` - file path (when truncation is needed, the end of the file path is kept, as this is
/// more useful for identifying the file)
/// - `=` - custom chunk name (when truncation is needed, the beginning of the name is kept)
pub fn set_name(mut self, name: impl Into<StdString>) -> Self {
self.name = name.into();
self
}
/// Returns the environment of this chunk.
pub fn environment(&self) -> Option<&Table> {
self.env.as_ref().ok()?.as_ref()
}
/// Sets the environment of the loaded chunk to the given value.
///
/// 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.
/// 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.
///
/// Calling this method changes the `_ENV` upvalue to the value provided, and variables inside
/// the chunk will refer to the given environment rather than the global one.
@@ -282,14 +534,16 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
/// 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
/// useful.
pub fn set_environment<V: IntoLua<'lua>>(mut self, env: V) -> Self {
self.env = env
.into_lua(self.lua)
.and_then(|val| self.lua.unpack(val))
.context("bad environment value");
pub fn set_environment(mut self, env: Table) -> Self {
self.env = Ok(Some(env));
self
}
/// Returns the mode (auto-detected by default) of this chunk.
pub fn mode(&self) -> ChunkMode {
self.detect_mode()
}
/// Sets whether the chunk is text or binary (autodetected by default).
///
/// Be aware, Lua does not check the consistency of the code inside binary chunks.
@@ -302,8 +556,6 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
/// Sets or overwrites a Luau compiler used for this chunk.
///
/// See [`Compiler`] for details and possible options.
///
/// Requires `feature = "luau"`
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn set_compiler(mut self, compiler: Compiler) -> Self {
@@ -315,17 +567,14 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
///
/// This is equivalent to calling the chunk function with no arguments and no return values.
pub fn exec(self) -> Result<()> {
self.call(())?;
Ok(())
self.call(())
}
/// Asynchronously execute this chunk of code.
///
/// See [`exec`] for more details.
///
/// Requires `feature = "async"`
///
/// [`exec`]: #method.exec
/// [`exec`]: Chunk::exec
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn exec_async(self) -> Result<()> {
@@ -337,7 +586,7 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
/// If the chunk can be parsed as an expression, this loads and executes the chunk and returns
/// the value that it evaluates to. Otherwise, the chunk is interpreted as a block as normal,
/// and this is equivalent to calling `exec`.
pub fn eval<R: FromLuaMulti<'lua>>(self) -> Result<R> {
pub fn eval<R: FromLuaMulti>(self) -> Result<R> {
// Bytecode is always interpreted as a statement.
// For source code, first try interpreting the lua as an expression by adding
// "return", then as a statement. This is the same thing the
@@ -355,14 +604,12 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
///
/// See [`eval`] for more details.
///
/// Requires `feature = "async"`
///
/// [`eval`]: #method.eval
/// [`eval`]: Chunk::eval
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn eval_async<R>(self) -> Result<R>
where
R: FromLuaMulti<'lua> + 'lua,
R: FromLuaMulti,
{
if self.detect_mode() == ChunkMode::Binary {
self.call_async(()).await
@@ -376,7 +623,7 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
/// Load the chunk function and call it with the given arguments.
///
/// This is equivalent to `into_function` and calling the resulting function.
pub fn call<A: IntoLuaMulti<'lua>, R: FromLuaMulti<'lua>>(self, args: A) -> Result<R> {
pub fn call<R: FromLuaMulti>(self, args: impl IntoLuaMulti) -> Result<R> {
self.into_function()?.call(args)
}
@@ -384,24 +631,21 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
///
/// See [`call`] for more details.
///
/// Requires `feature = "async"`
///
/// [`call`]: #method.call
/// [`call`]: Chunk::call
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub async fn call_async<A, R>(self, args: A) -> Result<R>
pub async fn call_async<R>(self, args: impl IntoLuaMulti) -> Result<R>
where
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'lua,
R: FromLuaMulti,
{
self.into_function()?.call_async(args).await
}
/// Load this chunk into a regular `Function`.
/// Load this chunk into a regular [`Function`].
///
/// This simply compiles the chunk without actually executing it.
#[cfg_attr(not(feature = "luau"), allow(unused_mut))]
pub fn into_function(mut self) -> Result<Function<'lua>> {
pub fn into_function(mut self) -> Result<Function> {
#[cfg(feature = "luau")]
if self.compiler.is_some() {
// We don't need to compile source if no compiler set
@@ -410,26 +654,23 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
let name = Self::convert_name(self.name)?;
self.lua
.load_chunk(Some(&name), self.env?, self.mode, self.source?.as_ref())
.lock()
.load_chunk(Some(&name), self.env?.as_ref(), self.mode, self.source?.as_ref())
}
/// Compiles the chunk and changes mode to binary.
///
/// It does nothing if the chunk is already binary.
/// It does nothing if the chunk is already binary or invalid.
fn compile(&mut self) {
if let Ok(ref source) = self.source {
if self.detect_mode() == ChunkMode::Text {
#[cfg(feature = "luau")]
{
let data = self
.compiler
.get_or_insert_with(Default::default)
.compile(source);
if let Ok(data) = self.compiler.get_or_insert_with(Default::default).compile(source) {
self.source = Ok(Cow::Owned(data));
self.mode = Some(ChunkMode::Binary);
}
#[cfg(not(feature = "luau"))]
if let Ok(func) = self.lua.load_chunk(None, None, None, source.as_ref()) {
if let Ok(func) = self.lua.lock().load_chunk(None, None, None, source.as_ref()) {
let data = func.dump(false);
self.source = Ok(Cow::Owned(data));
self.mode = Some(ChunkMode::Binary);
@@ -448,7 +689,8 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
let mut text_source = None;
if let Ok(ref source) = self.source {
if self.detect_mode() == ChunkMode::Text {
if let Some(cache) = self.lua.app_data_ref::<ChunksCache>() {
let lua = self.lua.lock();
if let Some(cache) = lua.priv_app_data_ref::<ChunksCache>() {
if let Some(data) = cache.0.get(source.as_ref()) {
self.source = Ok(Cow::Owned(data.clone()));
self.mode = Some(ChunkMode::Binary);
@@ -464,13 +706,14 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
self.compile();
if let Ok(ref binary_source) = self.source {
if self.detect_mode() == ChunkMode::Binary {
if let Some(mut cache) = self.lua.app_data_mut::<ChunksCache>() {
cache.0.insert(text_source, binary_source.as_ref().to_vec());
let lua = self.lua.lock();
if let Some(mut cache) = lua.priv_app_data_mut::<ChunksCache>() {
cache.0.insert(text_source, binary_source.to_vec());
} else {
let mut cache = ChunksCache(HashMap::new());
cache.0.insert(text_source, binary_source.as_ref().to_vec());
let _ = self.lua.try_set_app_data(cache);
}
cache.0.insert(text_source, binary_source.to_vec());
lua.set_priv_app_data(cache);
};
}
}
}
@@ -478,7 +721,7 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
self
}
fn to_expression(&self) -> Result<Function<'lua>> {
fn to_expression(&self) -> Result<Function> {
// We assume that mode is Text
let source = self.source.as_ref();
let source = source.map_err(Error::runtime)?;
@@ -489,32 +732,36 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
.compiler
.as_ref()
.map(|c| c.compile(&source))
.transpose()?
.unwrap_or(source);
let name = Self::convert_name(self.name.clone())?;
self.lua
.load_chunk(Some(&name), self.env.clone()?, None, &source)
let env = match &self.env {
Ok(Some(env)) => Some(env),
Ok(None) => None,
Err(err) => return Err(err.clone()),
};
self.lua.lock().load_chunk(Some(&name), env, None, &source)
}
fn detect_mode(&self) -> ChunkMode {
match (self.mode, &self.source) {
(Some(mode), _) => mode,
(None, Ok(source)) => {
#[cfg(not(feature = "luau"))]
if source.starts_with(ffi::LUA_SIGNATURE) {
return ChunkMode::Binary;
}
#[cfg(feature = "luau")]
if *source.first().unwrap_or(&u8::MAX) < b'\n' {
return ChunkMode::Binary;
}
ChunkMode::Text
}
(None, Err(_)) => ChunkMode::Text, // any value is fine
if let Some(mode) = self.mode {
return mode;
}
if let Ok(source) = &self.source {
#[cfg(not(feature = "luau"))]
if source.starts_with(ffi::LUA_SIGNATURE) {
return ChunkMode::Binary;
}
#[cfg(feature = "luau")]
if *source.first().unwrap_or(&u8::MAX) < b'\n' {
return ChunkMode::Binary;
}
}
ChunkMode::Text
}
fn convert_name(name: String) -> Result<CString> {
fn convert_name(name: StdString) -> Result<CString> {
CString::new(name).map_err(|err| Error::runtime(format!("invalid name: {err}")))
}
@@ -525,3 +772,31 @@ impl<'lua, 'a> Chunk<'lua, 'a> {
buf
}
}
struct WrappedChunk<T: AsChunk> {
chunk: T,
caller: &'static Location<'static>,
}
impl Chunk<'_> {
/// Wraps a chunk of Lua code, returning an opaque type that implements [`IntoLua`] trait.
///
/// The resulted `IntoLua` implementation will convert the chunk into a Lua function without
/// executing it.
#[doc(hidden)]
#[track_caller]
pub fn wrap(chunk: impl AsChunk) -> impl IntoLua {
WrappedChunk {
chunk,
caller: Location::caller(),
}
}
}
impl<T: AsChunk> IntoLua for WrappedChunk<T> {
fn into_lua(self, lua: &Lua) -> Result<Value> {
lua.load_with_location(self.chunk, self.caller)
.into_function()
.map(Value::Function)
}
}
+715 -313
View File
File diff suppressed because it is too large Load Diff
+107 -103
View File
@@ -1,51 +1,39 @@
use std::borrow::Cow;
use std::cell::UnsafeCell;
#[cfg(not(feature = "luau"))]
use std::ops::{BitOr, BitOrAssign};
use std::os::raw::c_int;
use ffi::lua_Debug;
use ffi::{lua_Debug, lua_State};
use crate::lua::Lua;
use crate::util::{linenumber_to_usize, ptr_to_lossy_str, ptr_to_str};
use crate::function::Function;
use crate::state::RawLua;
use crate::util::{assert_stack, linenumber_to_usize, ptr_to_lossy_str, ptr_to_str, StackGuard};
/// Contains information about currently executing Lua code.
///
/// The `Debug` structure is provided as a parameter to the hook function set with
/// [`Lua::set_hook`]. You may call the methods on this structure to retrieve information about the
/// Lua code executing at the time that the hook function was called. Further information can be
/// found in the Lua [documentation][lua_doc].
/// You may call the methods on this structure to retrieve information about the Lua code executing
/// at the specific level. Further information can be found in the Lua [documentation].
///
/// [lua_doc]: https://www.lua.org/manual/5.4/manual.html#lua_Debug
/// [`Lua::set_hook`]: crate::Lua::set_hook
pub struct Debug<'lua> {
lua: &'lua Lua,
ar: ActivationRecord,
#[cfg(feature = "luau")]
/// [documentation]: https://www.lua.org/manual/5.4/manual.html#lua_Debug
pub struct Debug<'a> {
state: *mut lua_State,
lua: &'a RawLua,
#[cfg_attr(not(feature = "luau"), allow(unused))]
level: c_int,
ar: *mut lua_Debug,
}
impl<'lua> Debug<'lua> {
#[cfg(not(feature = "luau"))]
pub(crate) fn new(lua: &'lua Lua, ar: *mut lua_Debug) -> Self {
impl<'a> Debug<'a> {
pub(crate) fn new(lua: &'a RawLua, level: c_int, ar: *mut lua_Debug) -> Self {
Debug {
state: lua.state(),
lua,
ar: ActivationRecord::Borrowed(ar),
}
}
pub(crate) fn new_owned(lua: &'lua Lua, _level: c_int, ar: lua_Debug) -> Self {
Debug {
lua,
ar: ActivationRecord::Owned(UnsafeCell::new(ar)),
#[cfg(feature = "luau")]
level: _level,
ar,
level,
}
}
/// Returns the specific event that triggered the hook.
///
/// For [Lua 5.1] `DebugEvent::TailCall` is used for return events to indicate a return
/// For [Lua 5.1] [`DebugEvent::TailCall`] is used for return events to indicate a return
/// from a function that did a tail call.
///
/// [Lua 5.1]: https://www.lua.org/manual/5.1/manual.html#pdf-LUA_HOOKTAILRET
@@ -53,7 +41,7 @@ impl<'lua> Debug<'lua> {
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub fn event(&self) -> DebugEvent {
unsafe {
match (*self.ar.get()).event {
match (*self.ar).event {
ffi::LUA_HOOKCALL => DebugEvent::Call,
ffi::LUA_HOOKRET => DebugEvent::Ret,
ffi::LUA_HOOKTAILCALL => DebugEvent::TailCall,
@@ -64,24 +52,48 @@ impl<'lua> Debug<'lua> {
}
}
/// Corresponds to the `n` what mask.
pub fn names(&self) -> DebugNames {
/// Returns the function that is running at the given level.
///
/// Corresponds to the `f` "what" mask.
pub fn function(&self) -> Function {
unsafe {
let _sg = StackGuard::new(self.state);
assert_stack(self.state, 1);
#[cfg(not(feature = "luau"))]
mlua_assert!(
ffi::lua_getinfo(self.state, cstr!("f"), self.ar) != 0,
"lua_getinfo failed with `f`"
);
#[cfg(feature = "luau")]
mlua_assert!(
ffi::lua_getinfo(self.state, self.level, cstr!("f"), self.ar) != 0,
"lua_getinfo failed with `f`"
);
ffi::lua_xmove(self.state, self.lua.ref_thread(), 1);
Function(self.lua.pop_ref_thread())
}
}
/// Corresponds to the `n` "what" mask.
pub fn names(&self) -> DebugNames<'_> {
unsafe {
#[cfg(not(feature = "luau"))]
mlua_assert!(
ffi::lua_getinfo(self.lua.state(), cstr!("n"), self.ar.get()) != 0,
ffi::lua_getinfo(self.state, cstr!("n"), self.ar) != 0,
"lua_getinfo failed with `n`"
);
#[cfg(feature = "luau")]
mlua_assert!(
ffi::lua_getinfo(self.lua.state(), self.level, cstr!("n"), self.ar.get()) != 0,
ffi::lua_getinfo(self.state, self.level, cstr!("n"), self.ar) != 0,
"lua_getinfo failed with `n`"
);
DebugNames {
name: ptr_to_lossy_str((*self.ar.get()).name),
name: ptr_to_lossy_str((*self.ar).name),
#[cfg(not(feature = "luau"))]
name_what: match ptr_to_str((*self.ar.get()).namewhat) {
name_what: match ptr_to_str((*self.ar).namewhat) {
Some("") => None,
val => val,
},
@@ -91,118 +103,110 @@ impl<'lua> Debug<'lua> {
}
}
/// Corresponds to the `S` what mask.
pub fn source(&self) -> DebugSource {
/// Corresponds to the `S` "what" mask.
pub fn source(&self) -> DebugSource<'_> {
unsafe {
#[cfg(not(feature = "luau"))]
mlua_assert!(
ffi::lua_getinfo(self.lua.state(), cstr!("S"), self.ar.get()) != 0,
ffi::lua_getinfo(self.state, cstr!("S"), self.ar) != 0,
"lua_getinfo failed with `S`"
);
#[cfg(feature = "luau")]
mlua_assert!(
ffi::lua_getinfo(self.lua.state(), self.level, cstr!("s"), self.ar.get()) != 0,
ffi::lua_getinfo(self.state, self.level, cstr!("s"), self.ar) != 0,
"lua_getinfo failed with `s`"
);
DebugSource {
source: ptr_to_lossy_str((*self.ar.get()).source),
source: ptr_to_lossy_str((*self.ar).source),
#[cfg(not(feature = "luau"))]
short_src: ptr_to_lossy_str((*self.ar.get()).short_src.as_ptr()),
short_src: ptr_to_lossy_str((*self.ar).short_src.as_ptr()),
#[cfg(feature = "luau")]
short_src: ptr_to_lossy_str((*self.ar.get()).short_src),
line_defined: linenumber_to_usize((*self.ar.get()).linedefined),
short_src: ptr_to_lossy_str((*self.ar).short_src),
line_defined: linenumber_to_usize((*self.ar).linedefined),
#[cfg(not(feature = "luau"))]
last_line_defined: linenumber_to_usize((*self.ar.get()).lastlinedefined),
last_line_defined: linenumber_to_usize((*self.ar).lastlinedefined),
#[cfg(feature = "luau")]
last_line_defined: None,
what: ptr_to_str((*self.ar.get()).what).unwrap_or("main"),
what: ptr_to_str((*self.ar).what).unwrap_or("main"),
}
}
}
/// Corresponds to the `l` what mask. Returns the current line.
#[doc(hidden)]
#[deprecated(note = "Use `current_line` instead")]
pub fn curr_line(&self) -> i32 {
self.current_line().map(|n| n as i32).unwrap_or(-1)
}
/// Corresponds to the `l` "what" mask. Returns the current line.
pub fn current_line(&self) -> Option<usize> {
unsafe {
#[cfg(not(feature = "luau"))]
mlua_assert!(
ffi::lua_getinfo(self.lua.state(), cstr!("l"), self.ar.get()) != 0,
ffi::lua_getinfo(self.state, cstr!("l"), self.ar) != 0,
"lua_getinfo failed with `l`"
);
#[cfg(feature = "luau")]
mlua_assert!(
ffi::lua_getinfo(self.lua.state(), self.level, cstr!("l"), self.ar.get()) != 0,
ffi::lua_getinfo(self.state, self.level, cstr!("l"), self.ar) != 0,
"lua_getinfo failed with `l`"
);
(*self.ar.get()).currentline
linenumber_to_usize((*self.ar).currentline)
}
}
/// Corresponds to the `t` what mask. Returns true if the hook is in a function tail call, false
/// otherwise.
#[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
/// Corresponds to the `t` "what" mask. Returns true if the hook is in a function tail call,
/// false otherwise.
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua54", feature = "lua53", feature = "lua52")))
)]
pub fn is_tail_call(&self) -> bool {
unsafe {
mlua_assert!(
ffi::lua_getinfo(self.lua.state(), cstr!("t"), self.ar.get()) != 0,
ffi::lua_getinfo(self.state, cstr!("t"), self.ar) != 0,
"lua_getinfo failed with `t`"
);
(*self.ar.get()).currentline != 0
(*self.ar).istailcall != 0
}
}
/// Corresponds to the `u` what mask.
/// Corresponds to the `u` "what" mask.
pub fn stack(&self) -> DebugStack {
unsafe {
#[cfg(not(feature = "luau"))]
mlua_assert!(
ffi::lua_getinfo(self.lua.state(), cstr!("u"), self.ar.get()) != 0,
ffi::lua_getinfo(self.state, cstr!("u"), self.ar) != 0,
"lua_getinfo failed with `u`"
);
#[cfg(feature = "luau")]
mlua_assert!(
ffi::lua_getinfo(self.lua.state(), self.level, cstr!("a"), self.ar.get()) != 0,
"lua_getinfo failed with `a`"
ffi::lua_getinfo(self.state, self.level, cstr!("au"), self.ar) != 0,
"lua_getinfo failed with `au`"
);
#[cfg(not(feature = "luau"))]
let stack = DebugStack {
num_ups: (*self.ar.get()).nups as _,
num_ups: (*self.ar).nups as _,
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
num_params: (*self.ar.get()).nparams as _,
num_params: (*self.ar).nparams as _,
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52"))]
is_vararg: (*self.ar.get()).isvararg != 0,
is_vararg: (*self.ar).isvararg != 0,
};
#[cfg(feature = "luau")]
let stack = DebugStack {
num_ups: (*self.ar.get()).nupvals as i32,
num_params: (*self.ar.get()).nparams as i32,
is_vararg: (*self.ar.get()).isvararg != 0,
num_ups: (*self.ar).nupvals,
num_params: (*self.ar).nparams,
is_vararg: (*self.ar).isvararg != 0,
};
stack
}
}
}
enum ActivationRecord {
#[cfg(not(feature = "luau"))]
Borrowed(*mut lua_Debug),
Owned(UnsafeCell<lua_Debug>),
}
impl ActivationRecord {
#[inline]
fn get(&self) -> *mut lua_Debug {
match self {
#[cfg(not(feature = "luau"))]
ActivationRecord::Borrowed(x) => *x,
ActivationRecord::Owned(x) => x.get(),
}
}
}
/// Represents a specific event that triggered the hook.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DebugEvent {
@@ -234,28 +238,28 @@ pub struct DebugSource<'a> {
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.
/// 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)]
pub struct DebugStack {
pub num_ups: i32,
/// Requires `feature = "lua54/lua53/lua52/luau"`
#[cfg(any(
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "luau"
))]
pub num_params: i32,
/// Requires `feature = "lua54/lua53/lua52/luau"`
#[cfg(any(
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "luau"
))]
/// Number of upvalues.
pub num_ups: u8,
/// Number of parameters.
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau")))
)]
pub num_params: u8,
/// Whether the function is a vararg function.
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau")))
)]
pub is_vararg: bool,
}
@@ -361,7 +365,7 @@ impl HookTriggers {
}
#[cfg(not(feature = "luau"))]
impl BitOr for HookTriggers {
impl std::ops::BitOr for HookTriggers {
type Output = Self;
fn bitor(mut self, rhs: Self) -> Self::Output {
@@ -376,7 +380,7 @@ impl BitOr for HookTriggers {
}
#[cfg(not(feature = "luau"))]
impl BitOrAssign for HookTriggers {
impl std::ops::BitOrAssign for HookTriggers {
fn bitor_assign(&mut self, rhs: Self) {
*self = *self | rhs;
}
+146 -64
View File
@@ -9,6 +9,12 @@ use std::sync::Arc;
use crate::private::Sealed;
#[cfg(feature = "error-send")]
type DynStdError = dyn StdError + Send + Sync;
#[cfg(not(feature = "error-send"))]
type DynStdError = dyn StdError;
/// Error type returned by `mlua` methods.
#[derive(Debug, Clone)]
#[non_exhaustive]
@@ -42,11 +48,11 @@ pub enum Error {
GarbageCollectorError(StdString),
/// Potentially unsafe action in safe mode.
SafetyError(StdString),
/// Setting memory limit is not available.
/// Memory control is not available.
///
/// This error can only happen when Lua state was not created by us and does not have the
/// custom allocator attached.
MemoryLimitNotAvailable,
MemoryControlNotAvailable,
/// 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.
@@ -61,10 +67,12 @@ pub enum Error {
///
/// Due to the way `mlua` works, it should not be directly possible to run out of stack space
/// during normal use. The only way that this error can be triggered is if a `Function` is
/// 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.
StackError,
/// Too many arguments to `Function::bind`.
/// Too many arguments to [`Function::bind`].
///
/// [`Function::bind`]: crate::Function::bind
BindError,
/// Bad argument received from Lua (usually when calling a function).
///
@@ -83,7 +91,7 @@ pub enum Error {
/// A Rust value could not be converted to a Lua value.
ToLuaConversionError {
/// Name of the Rust type that could not be converted.
from: &'static str,
from: String,
/// Name of the Lua type that could not be created.
to: &'static str,
/// A message indicating why the conversion failed in more detail.
@@ -94,21 +102,21 @@ pub enum Error {
/// Name of the Lua type that could not be converted.
from: &'static str,
/// Name of the Rust type that could not be created.
to: &'static str,
to: String,
/// A string containing more detailed error information.
message: Option<StdString>,
},
/// [`Thread::resume`] was called on an inactive coroutine.
/// [`Thread::resume`] was called on an unresumable coroutine.
///
/// A coroutine is inactive if its main function has returned or if an error has occurred inside
/// the coroutine.
/// A coroutine is unresumable if its main function has returned or if an error has occurred
/// inside the coroutine. Already running coroutines are also marked as unresumable.
///
/// [`Thread::status`] can be used to check if the coroutine can be resumed without causing this
/// error.
///
/// [`Thread::resume`]: crate::Thread::resume
/// [`Thread::status`]: crate::Thread::status
CoroutineInactive,
CoroutineUnresumable,
/// An [`AnyUserData`] is not the expected type in a borrow.
///
/// This error can only happen when manually using [`AnyUserData`], or when implementing
@@ -175,12 +183,12 @@ pub enum Error {
/// and returned again.
PreviouslyResumedPanic,
/// Serialization error.
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
SerializeError(StdString),
/// Deserialization error.
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
DeserializeError(StdString),
/// A custom error.
///
@@ -189,7 +197,7 @@ pub enum Error {
/// Returning `Err(ExternalError(...))` from a Rust callback will raise the error as a Lua
/// 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.
ExternalError(Arc<dyn StdError + Send + Sync>),
ExternalError(Arc<DynStdError>),
/// An error with additional context.
WithContext {
/// A string containing additional context.
@@ -205,21 +213,21 @@ pub type Result<T> = StdResult<T, Error>;
#[cfg(not(tarpaulin_include))]
impl fmt::Display for Error {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match *self {
Error::SyntaxError { ref message, .. } => write!(fmt, "syntax error: {message}"),
Error::RuntimeError(ref msg) => write!(fmt, "runtime error: {msg}"),
Error::MemoryError(ref msg) => {
match self {
Error::SyntaxError { message, .. } => write!(fmt, "syntax error: {message}"),
Error::RuntimeError(msg) => write!(fmt, "runtime error: {msg}"),
Error::MemoryError(msg) => {
write!(fmt, "memory error: {msg}")
}
#[cfg(any(feature = "lua53", feature = "lua52"))]
Error::GarbageCollectorError(ref msg) => {
Error::GarbageCollectorError(msg) => {
write!(fmt, "garbage collector error: {msg}")
}
Error::SafetyError(ref msg) => {
Error::SafetyError(msg) => {
write!(fmt, "safety error: {msg}")
},
Error::MemoryLimitNotAvailable => {
write!(fmt, "setting memory limit is not available")
Error::MemoryControlNotAvailable => {
write!(fmt, "memory control is not available")
}
Error::RecursiveMutCallback => write!(fmt, "mutable callback called recursively"),
Error::CallbackDestructed => write!(
@@ -234,7 +242,7 @@ impl fmt::Display for Error {
fmt,
"too many arguments to Function::bind"
),
Error::BadArgument { ref to, pos, ref name, ref cause } => {
Error::BadArgument { to, pos, name, cause } => {
if let Some(name) = name {
write!(fmt, "bad argument `{name}`")?;
} else {
@@ -245,40 +253,40 @@ impl fmt::Display for Error {
}
write!(fmt, ": {cause}")
},
Error::ToLuaConversionError { from, to, ref message } => {
Error::ToLuaConversionError { from, to, message } => {
write!(fmt, "error converting {from} to Lua {to}")?;
match *message {
match message {
None => Ok(()),
Some(ref message) => write!(fmt, " ({message})"),
Some(message) => write!(fmt, " ({message})"),
}
}
Error::FromLuaConversionError { from, to, ref message } => {
Error::FromLuaConversionError { from, to, message } => {
write!(fmt, "error converting Lua {from} to {to}")?;
match *message {
match message {
None => Ok(()),
Some(ref message) => write!(fmt, " ({message})"),
Some(message) => write!(fmt, " ({message})"),
}
}
Error::CoroutineInactive => write!(fmt, "cannot resume inactive coroutine"),
Error::CoroutineUnresumable => write!(fmt, "coroutine is non-resumable"),
Error::UserDataTypeMismatch => write!(fmt, "userdata is not expected type"),
Error::UserDataDestructed => write!(fmt, "userdata has been destructed"),
Error::UserDataBorrowError => write!(fmt, "error borrowing userdata"),
Error::UserDataBorrowMutError => write!(fmt, "error mutably borrowing userdata"),
Error::MetaMethodRestricted(ref method) => write!(fmt, "metamethod {method} is restricted"),
Error::MetaMethodTypeError { ref method, type_name, ref message } => {
Error::MetaMethodRestricted(method) => write!(fmt, "metamethod {method} is restricted"),
Error::MetaMethodTypeError { method, type_name, message } => {
write!(fmt, "metamethod {method} has unsupported type {type_name}")?;
match *message {
match message {
None => Ok(()),
Some(ref message) => write!(fmt, " ({message})"),
Some(message) => write!(fmt, " ({message})"),
}
}
Error::MismatchedRegistryKey => {
write!(fmt, "RegistryKey used from different Lua state")
}
Error::CallbackError { ref cause, ref traceback } => {
Error::CallbackError { cause, traceback } => {
// Trace errors down to the root
let (mut cause, mut full_traceback) = (cause, None);
while let Error::CallbackError { cause: ref cause2, traceback: ref traceback2 } = **cause {
while let Error::CallbackError { cause: cause2, traceback: traceback2 } = &**cause {
cause = cause2;
full_traceback = Some(traceback2);
}
@@ -301,16 +309,16 @@ impl fmt::Display for Error {
Error::PreviouslyResumedPanic => {
write!(fmt, "previously resumed panic returned again")
}
#[cfg(feature = "serialize")]
Error::SerializeError(ref err) => {
#[cfg(feature = "serde")]
Error::SerializeError(err) => {
write!(fmt, "serialize error: {err}")
},
#[cfg(feature = "serialize")]
Error::DeserializeError(ref err) => {
#[cfg(feature = "serde")]
Error::DeserializeError(err) => {
write!(fmt, "deserialize error: {err}")
},
Error::ExternalError(ref err) => write!(fmt, "{err}"),
Error::WithContext { ref context, ref cause } => {
Error::ExternalError(err) => err.fmt(fmt),
Error::WithContext { context, cause } => {
writeln!(fmt, "{context}")?;
write!(fmt, "{cause}")
}
@@ -320,17 +328,15 @@ impl fmt::Display for Error {
impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match *self {
match self {
// An error type with a source error should either return that error via source or
// include that source's error message in its own Display output, but never both.
// https://blog.rust-lang.org/inside-rust/2021/07/01/What-the-error-handling-project-group-is-working-towards.html
// 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::ExternalError(ref err) => err.source(),
Error::WithContext { ref cause, .. } => match cause.as_ref() {
Error::ExternalError(err) => err.source(),
_ => None,
},
Error::ExternalError(err) => err.source(),
Error::WithContext { cause, .. } => Self::source(cause),
_ => None,
}
}
@@ -345,7 +351,7 @@ impl Error {
/// Wraps an external error object.
#[inline]
pub fn external<T: Into<Box<dyn StdError + Send + Sync>>>(err: T) -> Self {
pub fn external<T: Into<Box<DynStdError>>>(err: T) -> Self {
Error::ExternalError(err.into().into())
}
@@ -356,10 +362,25 @@ impl Error {
{
match self {
Error::ExternalError(err) => err.downcast_ref(),
Error::WithContext { cause, .. } => match cause.as_ref() {
Error::ExternalError(err) => err.downcast_ref(),
_ => None,
},
Error::WithContext { cause, .. } => Self::downcast_ref(cause),
_ => None,
}
}
/// An iterator over the chain of nested errors wrapped by this Error.
pub fn chain(&self) -> impl Iterator<Item = &(dyn StdError + 'static)> {
Chain {
root: self,
current: None,
}
}
/// Returns the parent of this error.
#[doc(hidden)]
pub fn parent(&self) -> Option<&Error> {
match self {
Error::CallbackError { cause, .. } => Some(cause.as_ref()),
Error::WithContext { cause, .. } => Some(cause.as_ref()),
_ => None,
}
}
@@ -373,15 +394,15 @@ impl Error {
}
}
pub(crate) fn from_lua_conversion<'a>(
pub(crate) fn from_lua_conversion(
from: &'static str,
to: &'static str,
message: impl Into<Option<&'a str>>,
to: impl ToString,
message: impl Into<Option<String>>,
) -> Self {
Error::FromLuaConversionError {
from,
to,
message: message.into().map(|s| s.into()),
to: to.to_string(),
message: message.into(),
}
}
}
@@ -391,7 +412,7 @@ pub trait ExternalError {
fn into_lua_err(self) -> Error;
}
impl<E: Into<Box<dyn StdError + Send + Sync>>> ExternalError for E {
impl<E: Into<Box<DynStdError>>> ExternalError for E {
fn into_lua_err(self) -> Error {
Error::external(self)
}
@@ -445,7 +466,7 @@ impl ErrorContext for Error {
}
}
impl<T> ErrorContext for StdResult<T, Error> {
impl<T> ErrorContext for Result<T> {
fn context<C: fmt::Display>(self, context: C) -> Self {
self.map_err(|err| err.context(context))
}
@@ -473,16 +494,77 @@ impl From<Utf8Error> for Error {
}
}
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
impl serde::ser::Error for Error {
fn custom<T: fmt::Display>(msg: T) -> Self {
Self::SerializeError(msg.to_string())
}
}
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
impl serde::de::Error for Error {
fn custom<T: fmt::Display>(msg: T) -> Self {
Self::DeserializeError(msg.to_string())
}
}
#[cfg(feature = "anyhow")]
impl From<anyhow::Error> for Error {
fn from(err: anyhow::Error) -> Self {
match err.downcast::<Self>() {
Ok(err) => err,
Err(err) => Error::external(err),
}
}
}
struct Chain<'a> {
root: &'a Error,
current: Option<&'a (dyn StdError + 'static)>,
}
impl<'a> Iterator for Chain<'a> {
type Item = &'a (dyn StdError + 'static);
fn next(&mut self) -> Option<Self::Item> {
loop {
let error: Option<&dyn StdError> = match self.current {
None => {
self.current = Some(self.root);
self.current
}
Some(current) => match current.downcast_ref::<Error>()? {
Error::BadArgument { cause, .. }
| Error::CallbackError { cause, .. }
| Error::WithContext { cause, .. } => {
self.current = Some(&**cause);
self.current
}
Error::ExternalError(err) => {
self.current = Some(&**err);
self.current
}
_ => None,
},
};
// Skip `ExternalError` as it only wraps the underlying error
// without meaningful context
if let Some(Error::ExternalError(_)) = error?.downcast_ref::<Error>() {
continue;
}
return self.current;
}
}
}
#[cfg(test)]
mod assertions {
use super::*;
#[cfg(not(feature = "error-send"))]
static_assertions::assert_not_impl_any!(Error: Send, Sync);
#[cfg(feature = "send")]
static_assertions::assert_impl_all!(Error: Send, Sync);
}
+229 -159
View File
@@ -1,49 +1,30 @@
use std::cell::RefCell;
use std::mem;
use std::os::raw::{c_int, c_void};
use std::ptr;
use std::slice;
use std::{mem, ptr, slice};
use crate::error::{Error, Result};
use crate::lua::Lua;
use crate::state::Lua;
use crate::table::Table;
use crate::types::{Callback, LuaRef, MaybeSend};
use crate::traits::{FromLuaMulti, IntoLua, IntoLuaMulti, LuaNativeFn, LuaNativeFnMut};
use crate::types::{Callback, LuaType, MaybeSend, ValueRef};
use crate::util::{
assert_stack, check_stack, linenumber_to_usize, pop_error, ptr_to_lossy_str, ptr_to_str,
StackGuard,
assert_stack, check_stack, linenumber_to_usize, pop_error, ptr_to_lossy_str, ptr_to_str, StackGuard,
};
use crate::value::{FromLuaMulti, IntoLua, IntoLuaMulti, Value};
use crate::value::Value;
#[cfg(feature = "async")]
use {
crate::thread::AsyncThread,
crate::traits::LuaNativeAsyncFn,
crate::types::AsyncCallback,
futures_util::future::{self, Future},
std::future::{self, Future},
std::pin::Pin,
std::task::{Context, Poll},
};
/// Handle to an internal Lua function.
#[derive(Clone, Debug)]
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())
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Function(pub(crate) ValueRef);
/// Contains information about a function.
///
@@ -58,7 +39,8 @@ pub struct FunctionInfo {
///
/// Always `None` for Luau.
pub name_what: Option<&'static str>,
/// A string `Lua` if the function is a Lua function, `C` if it is a C function, `main` if it is the main part of a chunk.
/// A string `Lua` if the function is a Lua function, `C` if it is a C function, `main` if it is
/// the main part of a chunk.
pub what: &'static str,
/// Source of the chunk that created the function.
pub source: Option<String>,
@@ -81,7 +63,7 @@ pub struct CoverageInfo {
pub hits: Vec<i32>,
}
impl<'lua> Function<'lua> {
impl Function {
/// Calls the function, passing `args` as function arguments.
///
/// The function's return values are converted to the generic type `R`.
@@ -98,7 +80,7 @@ impl<'lua> Function<'lua> {
///
/// let tostring: Function = globals.get("tostring")?;
///
/// assert_eq!(tostring.call::<_, String>(123)?, "123");
/// assert_eq!(tostring.call::<String>(123)?, "123");
///
/// # Ok(())
/// # }
@@ -117,13 +99,13 @@ impl<'lua> Function<'lua> {
/// end
/// "#).eval()?;
///
/// assert_eq!(sum.call::<_, u32>((3, 4))?, 3 + 4);
/// assert_eq!(sum.call::<u32>((3, 4))?, 3 + 4);
///
/// # Ok(())
/// # }
/// ```
pub fn call<A: IntoLuaMulti<'lua>, R: FromLuaMulti<'lua>>(&self, args: A) -> Result<R> {
let lua = self.0.lua;
pub fn call<R: FromLuaMulti>(&self, args: impl IntoLuaMulti) -> Result<R> {
let lua = self.0.lua.lock();
let state = lua.state();
unsafe {
let _sg = StackGuard::new(state);
@@ -134,7 +116,7 @@ impl<'lua> Function<'lua> {
let stack_start = ffi::lua_gettop(state);
// Push function and the arguments
lua.push_ref(&self.0);
let nargs = args.push_into_stack_multi(lua)?;
let nargs = args.push_into_stack_multi(&lua)?;
// Call the function
let ret = ffi::lua_pcall(state, nargs, ffi::LUA_MULTRET, stack_start);
if ret != ffi::LUA_OK {
@@ -142,16 +124,15 @@ impl<'lua> Function<'lua> {
}
// Get the results
let nresults = ffi::lua_gettop(state) - stack_start;
R::from_stack_multi(nresults, lua)
R::from_stack_multi(nresults, &lua)
}
}
/// Returns a future that, when polled, calls `self`, passing `args` as function arguments,
/// and drives the execution.
///
/// Internally it wraps the function to an [`AsyncThread`].
///
/// Requires `feature = "async"`
/// Internally it wraps the function to an [`AsyncThread`]. The returned type implements
/// `Future<Output = Result<R>>` and can be awaited.
///
/// # Examples
///
@@ -167,7 +148,7 @@ impl<'lua> Function<'lua> {
/// Ok(())
/// })?;
///
/// sleep.call_async(10).await?;
/// sleep.call_async::<()>(10).await?;
///
/// # Ok(())
/// # }
@@ -176,18 +157,18 @@ impl<'lua> Function<'lua> {
/// [`AsyncThread`]: crate::AsyncThread
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn call_async<A, R>(&self, args: A) -> impl Future<Output = Result<R>> + 'lua
pub fn call_async<R>(&self, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
where
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua> + 'lua,
R: FromLuaMulti,
{
let lua = self.0.lua;
let thread_res = lua.create_recycled_thread(self).map(|th| {
let mut th = th.into_async(args);
th.set_recyclable(true);
th
});
async move { thread_res?.await }
let lua = self.0.lua.lock();
AsyncCallFuture(unsafe {
lua.create_recycled_thread(self).and_then(|th| {
let mut th = th.into_async(args)?;
th.set_recyclable(true);
Ok(th)
})
})
}
/// Returns a function that, when called, calls `self`, passing `args` as the first set of
@@ -209,15 +190,15 @@ impl<'lua> Function<'lua> {
/// "#).eval()?;
///
/// let bound_a = sum.bind(1)?;
/// assert_eq!(bound_a.call::<_, u32>(2)?, 1 + 2);
/// assert_eq!(bound_a.call::<u32>(2)?, 1 + 2);
///
/// let bound_a_and_b = sum.bind(13)?.bind(57)?;
/// assert_eq!(bound_a_and_b.call::<_, u32>(())?, 13 + 57);
/// assert_eq!(bound_a_and_b.call::<u32>(())?, 13 + 57);
///
/// # Ok(())
/// # }
/// ```
pub fn bind<A: IntoLuaMulti<'lua>>(&self, args: A) -> Result<Function<'lua>> {
pub fn bind(&self, args: impl IntoLuaMulti) -> Result<Function> {
unsafe extern "C-unwind" fn args_wrapper_impl(state: *mut ffi::lua_State) -> c_int {
let nargs = ffi::lua_gettop(state);
let nbinds = ffi::lua_tointeger(state, ffi::lua_upvalueindex(1)) as c_int;
@@ -233,10 +214,10 @@ impl<'lua> Function<'lua> {
nargs + nbinds
}
let lua = self.0.lua;
let lua = self.0.lua.lock();
let state = lua.state();
let args = args.into_lua_multi(lua)?;
let args = args.into_lua_multi(lua.lua())?;
let nargs = args.len() as c_int;
if nargs == 0 {
@@ -252,7 +233,7 @@ impl<'lua> Function<'lua> {
check_stack(state, nargs + 3)?;
ffi::lua_pushinteger(state, nargs as ffi::lua_Integer);
for arg in args {
for arg in &args {
lua.push_value(arg)?;
}
protect_lua!(state, nargs + 1, 1, fn(state) {
@@ -262,6 +243,7 @@ impl<'lua> Function<'lua> {
Function(lua.pop_ref())
};
let lua = lua.lua();
lua.load(
r#"
local func, args_wrapper = ...
@@ -271,8 +253,8 @@ impl<'lua> Function<'lua> {
"#,
)
.try_cache()
.set_name("__mlua_bind")
.call((self.clone(), args_wrapper))
.set_name("=__mlua_bind")
.call((self, args_wrapper))
}
/// Returns the environment of the Lua function.
@@ -281,7 +263,7 @@ impl<'lua> Function<'lua> {
///
/// This function always returns `None` for Rust/C functions.
pub fn environment(&self) -> Option<Table> {
let lua = self.0.lua;
let lua = self.0.lua.lock();
let state = lua.state();
unsafe {
let _sg = StackGuard::new(state);
@@ -299,7 +281,7 @@ impl<'lua> Function<'lua> {
// 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,
s if std::ffi::CStr::from_ptr(s as _) == c"_ENV" => break,
_ => ffi::lua_pop(state, 1),
}
}
@@ -318,7 +300,7 @@ impl<'lua> Function<'lua> {
///
/// This function does nothing for Rust/C functions.
pub fn set_environment(&self, env: Table) -> Result<bool> {
let lua = self.0.lua;
let lua = self.0.lua.lock();
let state = lua.state();
unsafe {
let _sg = StackGuard::new(state);
@@ -338,10 +320,11 @@ impl<'lua> Function<'lua> {
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" => {
s if std::ffi::CStr::from_ptr(s as _) == c"_ENV" => {
ffi::lua_pop(state, 1);
// Create an anonymous function with the new environment
let f_with_env = lua
.lua()
.load("return _ENV")
.set_environment(env)
.try_cache()
@@ -364,7 +347,7 @@ impl<'lua> Function<'lua> {
///
/// [`lua_getinfo`]: https://www.lua.org/manual/5.4/manual.html#lua_getinfo
pub fn info(&self) -> FunctionInfo {
let lua = self.0.lua;
let lua = self.0.lua.lock();
let state = lua.state();
unsafe {
let _sg = StackGuard::new(state);
@@ -407,9 +390,9 @@ impl<'lua> Function<'lua> {
/// If `strip` is true, the binary representation may not include all debug information
/// about the function, to save space.
///
/// For Luau a [Compiler] can be used to compile Lua chunks to bytecode.
/// For Luau a [`Compiler`] can be used to compile Lua chunks to bytecode.
///
/// [Compiler]: crate::chunk::Compiler
/// [`Compiler`]: crate::chunk::Compiler
#[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub fn dump(&self, strip: bool) -> Vec<u8> {
@@ -425,7 +408,7 @@ impl<'lua> Function<'lua> {
0
}
let lua = self.0.lua;
let lua = self.0.lua.lock();
let state = lua.state();
let mut data: Vec<u8> = Vec::new();
unsafe {
@@ -443,17 +426,15 @@ impl<'lua> Function<'lua> {
/// Retrieves recorded coverage information about this Lua function including inner calls.
///
/// This function takes a callback as an argument and calls it providing [`CoverageInfo`] snapshot
/// per each executed inner function.
/// This function takes a callback as an argument and calls it providing [`CoverageInfo`]
/// snapshot per each executed inner function.
///
/// Recording of coverage information is controlled by [`Compiler::set_coverage_level`] option.
///
/// Requires `feature = "luau"`
///
/// [`Compiler::set_coverage_level`]: crate::chunk::Compiler::set_coverage_level
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn coverage<F>(&self, mut func: F)
pub fn coverage<F>(&self, func: F)
where
F: FnMut(CoverageInfo),
{
@@ -473,144 +454,230 @@ impl<'lua> Function<'lua> {
} else {
None
};
let rust_callback = &mut *(data as *mut F);
rust_callback(CoverageInfo {
function,
line_defined,
depth,
hits: slice::from_raw_parts(hits, size).to_vec(),
});
let rust_callback = &*(data as *const RefCell<F>);
if let Ok(mut rust_callback) = rust_callback.try_borrow_mut() {
// Call the Rust callback with CoverageInfo
rust_callback(CoverageInfo {
function,
line_defined,
depth,
hits: slice::from_raw_parts(hits, size).to_vec(),
});
}
}
let lua = self.0.lua;
let lua = self.0.lua.lock();
let state = lua.state();
unsafe {
let _sg = StackGuard::new(state);
assert_stack(state, 1);
lua.push_ref(&self.0);
let func_ptr = &mut func as *mut F as *mut c_void;
let func = RefCell::new(func);
let func_ptr = &func as *const RefCell<F> as *mut c_void;
ffi::lua_getcoverage(state, -1, func_ptr, callback::<F>);
}
}
/// 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> {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
// Additional shortcuts
#[cfg(feature = "unstable")]
impl OwnedFunction {
/// Calls the function, passing `args` as function arguments.
/// Converts this function to a generic C pointer.
///
/// This is a shortcut for [`Function::call()`].
/// 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 call<'lua, A, R>(&'lua self, args: A) -> Result<R>
where
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua>,
{
self.to_ref().call(args)
pub fn to_pointer(&self) -> *const c_void {
self.0.to_pointer()
}
/// Returns a future that, when polled, calls `self`, passing `args` as function arguments,
/// and drives the execution.
/// Creates a deep clone of the Lua function.
///
/// 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
/// Copies the function prototype and all its upvalues to the
/// newly created function.
/// This function returns shallow clone (same handle) for Rust/C functions.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn deep_clone(&self) -> Result<Self> {
let lua = self.0.lua.lock();
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(self.clone());
}
if lua.unlikely_memory_error() {
ffi::lua_clonefunction(state, -1);
} else {
protect_lua!(state, 1, 1, fn(state) ffi::lua_clonefunction(state, -1))?;
}
Ok(Function(lua.pop_ref()))
}
}
}
pub(crate) struct WrappedFunction<'lua>(pub(crate) Callback<'lua, 'static>);
struct WrappedFunction(pub(crate) Callback);
#[cfg(feature = "async")]
pub(crate) struct WrappedAsyncFunction<'lua>(pub(crate) AsyncCallback<'lua, 'static>);
struct WrappedAsyncFunction(pub(crate) AsyncCallback);
impl<'lua> Function<'lua> {
/// Wraps a Rust function or closure, returning an opaque type that implements [`IntoLua`] trait.
impl Function {
/// 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>
pub fn wrap<F, A, R>(func: F) -> impl IntoLua
where
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
F: Fn(&'lua Lua, A) -> Result<R> + MaybeSend + 'static,
F: LuaNativeFn<A, Output = Result<R>> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
{
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)
func.call(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>
pub fn wrap_mut<F, A, R>(func: F) -> impl IntoLua
where
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
F: FnMut(&'lua Lua, A) -> Result<R> + MaybeSend + 'static,
F: LuaNativeFnMut<A, Output = Result<R>> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
{
let func = RefCell::new(func);
WrappedFunction(Box::new(move |lua, nargs| unsafe {
let mut func = func
.try_borrow_mut()
.map_err(|_| Error::RecursiveMutCallback)?;
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)
func.call(args)?.push_into_stack_multi(lua)
}))
}
/// Wraps a Rust async function or closure, returning an opaque type that implements [`IntoLua`] trait.
/// Wraps a Rust function or closure, returning an opaque type that implements [`IntoLua`]
/// trait.
///
/// This function is similar to [`Function::wrap`] but any returned `Result` will be converted
/// to a `ok, err` tuple without throwing an exception.
#[inline]
pub fn wrap_raw<F, A>(func: F) -> impl IntoLua
where
F: LuaNativeFn<A> + MaybeSend + 'static,
A: FromLuaMulti,
{
WrappedFunction(Box::new(move |lua, nargs| unsafe {
let args = A::from_stack_args(nargs, 1, None, lua)?;
func.call(args).push_into_stack_multi(lua)
}))
}
/// Wraps a Rust mutable closure, returning an opaque type that implements [`IntoLua`] trait.
///
/// This function is similar to [`Function::wrap_mut`] but any returned `Result` will be
/// converted to a `ok, err` tuple without throwing an exception.
#[inline]
pub fn wrap_raw_mut<F, A>(func: F) -> impl IntoLua
where
F: LuaNativeFnMut<A> + MaybeSend + 'static,
A: FromLuaMulti,
{
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.call(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>
pub fn wrap_async<F, A, R>(func: F) -> impl IntoLua
where
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
F: Fn(&'lua Lua, A) -> FR + MaybeSend + 'static,
FR: Future<Output = Result<R>> + 'lua,
F: LuaNativeAsyncFn<A, Output = Result<R>> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
{
WrappedAsyncFunction(Box::new(move |lua, args| unsafe {
let args = match A::from_lua_args(args, 1, None, lua) {
WrappedAsyncFunction(Box::new(move |rawlua, nargs| unsafe {
let args = match A::from_stack_args(nargs, 1, None, rawlua) {
Ok(args) => args,
Err(e) => return Box::pin(future::err(e)),
Err(e) => return Box::pin(future::ready(Err(e))),
};
let fut = func(lua, args);
Box::pin(async move { fut.await?.push_into_stack_multi(lua) })
let lua = rawlua.lua();
let fut = func.call(args);
Box::pin(async move { fut.await?.push_into_stack_multi(lua.raw_lua()) })
}))
}
/// Wraps a Rust async function or closure, returning an opaque type that implements [`IntoLua`]
/// trait.
///
/// This function is similar to [`Function::wrap_async`] but any returned `Result` will be
/// converted to a `ok, err` tuple without throwing an exception.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub fn wrap_raw_async<F, A>(func: F) -> impl IntoLua
where
F: LuaNativeAsyncFn<A> + MaybeSend + 'static,
A: FromLuaMulti,
{
WrappedAsyncFunction(Box::new(move |rawlua, nargs| unsafe {
let args = match A::from_stack_args(nargs, 1, None, rawlua) {
Ok(args) => args,
Err(e) => return Box::pin(future::ready(Err(e))),
};
let lua = rawlua.lua();
let fut = func.call(args);
Box::pin(async move { fut.await.push_into_stack_multi(lua.raw_lua()) })
}))
}
}
impl<'lua> IntoLua<'lua> for WrappedFunction<'lua> {
impl IntoLua for WrappedFunction {
#[inline]
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
lua.create_callback(self.0).map(Value::Function)
fn into_lua(self, lua: &Lua) -> Result<Value> {
lua.lock().create_callback(self.0).map(Value::Function)
}
}
#[cfg(feature = "async")]
impl<'lua> IntoLua<'lua> for WrappedAsyncFunction<'lua> {
impl IntoLua for WrappedAsyncFunction {
#[inline]
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>> {
lua.create_async_callback(self.0).map(Value::Function)
fn into_lua(self, lua: &Lua) -> Result<Value> {
lua.lock().create_async_callback(self.0).map(Value::Function)
}
}
impl LuaType for Function {
const TYPE_ID: c_int = ffi::LUA_TFUNCTION;
}
#[cfg(feature = "async")]
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct AsyncCallFuture<R: FromLuaMulti>(Result<AsyncThread<R>>);
#[cfg(feature = "async")]
impl<R: FromLuaMulti> AsyncCallFuture<R> {
pub(crate) fn error(err: Error) -> Self {
AsyncCallFuture(Err(err))
}
}
#[cfg(feature = "async")]
impl<R: FromLuaMulti> Future for AsyncCallFuture<R> {
type Output = Result<R>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
// Safety: We're not moving any pinned data
let this = unsafe { self.get_unchecked_mut() };
match &mut this.0 {
Ok(thread) => {
let pinned_thread = unsafe { Pin::new_unchecked(thread) };
pinned_thread.poll(cx)
}
Err(err) => Poll::Ready(Err(err.clone())),
}
}
}
@@ -618,8 +685,11 @@ impl<'lua> IntoLua<'lua> for WrappedAsyncFunction<'lua> {
mod assertions {
use super::*;
#[cfg(not(feature = "send"))]
static_assertions::assert_not_impl_any!(Function: Send);
#[cfg(feature = "send")]
static_assertions::assert_impl_all!(Function: Send, Sync);
#[cfg(all(feature = "unstable", not(feature = "send")))]
static_assertions::assert_not_impl_any!(OwnedFunction: Send);
#[cfg(all(feature = "async", feature = "send"))]
static_assertions::assert_impl_all!(AsyncCallFuture<()>: Send);
}
+62 -72
View File
@@ -27,126 +27,125 @@
//!
//! # Serde support
//!
//! The [`LuaSerdeExt`] trait implemented for [`Lua`] allows conversion from Rust types to Lua values
//! and vice versa using serde. Any user defined data type that implements [`serde::Serialize`] or
//! [`serde::Deserialize`] can be converted.
//! The [`LuaSerdeExt`] trait implemented for [`Lua`] allows conversion from Rust types to Lua
//! values and vice versa using serde. Any user defined data type that implements
//! [`serde::Serialize`] or [`serde::Deserialize`] can be converted.
//! For convenience, additional functionality to handle `NULL` values and arrays is provided.
//!
//! The [`Value`] enum implements [`serde::Serialize`] trait to support serializing Lua values
//! (including [`UserData`]) into Rust values.
//! The [`Value`] enum and other types implement [`serde::Serialize`] trait to support serializing
//! Lua values into Rust values.
//!
//! Requires `feature = "serialize"`.
//! Requires `feature = "serde"`.
//!
//! # Async/await support
//!
//! The [`create_async_function`] allows creating non-blocking functions that returns [`Future`].
//! Lua code with async capabilities can be executed by [`call_async`] family of functions or polling
//! [`AsyncThread`] using any runtime (eg. Tokio).
//! The [`Lua::create_async_function`] allows creating non-blocking functions that returns
//! [`Future`]. Lua code with async capabilities can be executed by [`Function::call_async`] family
//! of functions or polling [`AsyncThread`] using any runtime (eg. Tokio).
//!
//! Requires `feature = "async"`.
//!
//! # `Send` requirement
//! By default `mlua` is `!Send`. This can be changed by enabling `feature = "send"` that adds `Send` requirement
//! to [`Function`]s and [`UserData`].
//! # `Send` and `Sync` support
//!
//! By default `mlua` is `!Send`. This can be changed by enabling `feature = "send"` that adds
//! `Send` requirement to Rust functions and [`UserData`] types.
//!
//! In this case [`Lua`] object and their types can be send or used from other threads. Internally
//! access to Lua VM is synchronized using a reentrant mutex that can be locked many times within
//! the same thread.
//!
//! [Lua programming language]: https://www.lua.org/
//! [`Lua`]: crate::Lua
//! [executing]: crate::Chunk::exec
//! [evaluating]: crate::Chunk::eval
//! [globals]: crate::Lua::globals
//! [`IntoLua`]: crate::IntoLua
//! [`FromLua`]: crate::FromLua
//! [`IntoLuaMulti`]: crate::IntoLuaMulti
//! [`FromLuaMulti`]: crate::FromLuaMulti
//! [`Function`]: crate::Function
//! [`UserData`]: crate::UserData
//! [`UserDataFields`]: crate::UserDataFields
//! [`UserDataMethods`]: crate::UserDataMethods
//! [`LuaSerdeExt`]: crate::LuaSerdeExt
//! [`Value`]: crate::Value
//! [`create_async_function`]: crate::Lua::create_async_function
//! [`call_async`]: crate::Function::call_async
//! [`AsyncThread`]: crate::AsyncThread
//! [`Future`]: std::future::Future
//! [`serde::Serialize`]: https://docs.serde.rs/serde/ser/trait.Serialize.html
//! [`serde::Deserialize`]: https://docs.serde.rs/serde/de/trait.Deserialize.html
// Deny warnings inside doc tests / examples. When this isn't present, rustdoc doesn't show *any*
// warnings at all.
#![doc(test(attr(deny(warnings))))]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(not(send), allow(clippy::arc_with_non_send_sync))]
#![allow(clippy::ptr_eq)]
#![allow(unsafe_op_in_unsafe_fn)]
#[macro_use]
mod macros;
mod buffer;
mod chunk;
mod conversion;
mod debug;
mod error;
mod function;
mod hook;
mod lua;
#[cfg(feature = "luau")]
#[cfg(any(feature = "luau", doc))]
mod luau;
mod memory;
mod multi;
mod scope;
mod state;
mod stdlib;
mod string;
mod table;
mod thread;
mod traits;
mod types;
mod userdata;
mod userdata_ext;
mod userdata_impl;
mod util;
mod value;
mod vector;
pub mod prelude;
pub use bstr::BString;
pub use ffi::{self, lua_CFunction, lua_State};
pub use crate::chunk::{AsChunk, Chunk, ChunkMode};
pub use crate::debug::{Debug, DebugEvent, DebugNames, DebugSource, DebugStack};
pub use crate::error::{Error, ErrorContext, ExternalError, ExternalResult, Result};
pub use crate::function::{Function, FunctionInfo};
pub use crate::hook::{Debug, DebugEvent, DebugNames, DebugSource, DebugStack};
pub use crate::lua::{GCMode, Lua, LuaOptions};
pub use crate::multi::Variadic;
pub use crate::multi::{MultiValue, Variadic};
pub use crate::scope::Scope;
pub use crate::state::{GCMode, Lua, LuaOptions, WeakLua};
pub use crate::stdlib::StdLib;
pub use crate::string::String;
pub use crate::table::{Table, TableExt, TablePairs, TableSequence};
pub use crate::string::{BorrowedBytes, BorrowedStr, String};
pub use crate::table::{Table, TablePairs, TableSequence};
pub use crate::thread::{Thread, ThreadStatus};
pub use crate::types::{AppDataRef, AppDataRefMut, Integer, LightUserData, Number, RegistryKey};
pub use crate::userdata::{
AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMetatable, UserDataMethods,
UserDataRef, UserDataRefMut,
pub use crate::traits::{
FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, LuaNativeFn, LuaNativeFnMut, ObjectLike,
};
pub use crate::userdata_ext::AnyUserDataExt;
pub use crate::userdata_impl::UserDataRegistry;
pub use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, MultiValue, Nil, Value};
pub use crate::types::{
AppDataRef, AppDataRefMut, Either, Integer, LightUserData, MaybeSend, Number, RegistryKey, VmState,
};
pub use crate::userdata::{
AnyUserData, MetaMethod, UserData, UserDataFields, UserDataMetatable, UserDataMethods, UserDataRef,
UserDataRefMut, UserDataRegistry,
};
pub use crate::value::{Nil, Value};
#[cfg(not(feature = "luau"))]
pub use crate::hook::HookTriggers;
pub use crate::debug::HookTriggers;
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub use crate::{
chunk::Compiler,
buffer::Buffer,
chunk::{CompileConstant, Compiler},
function::CoverageInfo,
types::{Vector, VmState},
luau::{NavigateError, Require, TextRequirer},
vector::Vector,
};
#[cfg(feature = "async")]
pub use crate::thread::AsyncThread;
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
pub use crate::{thread::AsyncThread, traits::LuaNativeAsyncFn};
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
#[doc(inline)]
pub use crate::serde::{
de::Options as DeserializeOptions, ser::Options as SerializeOptions, LuaSerdeExt,
};
pub use crate::serde::{de::Options as DeserializeOptions, ser::Options as SerializeOptions, LuaSerdeExt};
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
pub mod serde;
#[cfg(feature = "mlua_derive")]
@@ -154,13 +153,6 @@ pub mod serde;
#[macro_use]
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.
///
/// This macro allows to write Lua code directly in Rust code.
@@ -201,18 +193,14 @@ pub use crate::{
///
/// Other minor limitations:
///
/// - Certain escape codes in string literals don't work.
/// (Specifically: `\a`, `\b`, `\f`, `\v`, `\123` (octal escape codes), `\u`, and `\U`).
/// - Certain escape codes in string literals don't work. (Specifically: `\a`, `\b`, `\f`, `\v`,
/// `\123` (octal escape codes), `\u`, and `\U`).
///
/// These are accepted: : `\\`, `\n`, `\t`, `\r`, `\xAB` (hex escape codes), and `\0`.
///
/// - The `//` (floor division) operator is unusable, as its start a comment.
///
/// Everything else should work.
///
/// [`AsChunk`]: crate::AsChunk
/// [`UserData`]: crate::UserData
/// [`IntoLua`]: crate::IntoLua
#[cfg(feature = "macros")]
#[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
pub use mlua_derive::chunk;
@@ -229,7 +217,7 @@ pub use mlua_derive::FromLua;
///
/// You can register multiple entrypoints as required.
///
/// ```
/// ```ignore
/// use mlua::{Lua, Result, Table};
///
/// #[mlua::lua_module]
@@ -266,11 +254,13 @@ pub use mlua_derive::FromLua;
/// ...
/// }
/// ```
///
#[cfg(any(feature = "module", docsrs))]
#[cfg(all(feature = "mlua_derive", any(feature = "module", doc)))]
#[cfg_attr(docsrs, doc(cfg(feature = "module")))]
pub use mlua_derive::lua_module;
#[cfg(all(feature = "module", feature = "send"))]
compile_error!("`send` feature is not supported in module mode");
pub(crate) mod private {
use super::*;
@@ -279,6 +269,6 @@ pub(crate) mod private {
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<'_> {}
impl Sealed for Table {}
impl Sealed for AnyUserData {}
}
-3611
View File
File diff suppressed because it is too large Load Diff
+44 -33
View File
@@ -1,20 +1,32 @@
use std::ffi::CStr;
use std::os::raw::{c_float, c_int};
use std::os::raw::c_int;
use std::ptr;
use crate::chunk::ChunkMode;
use crate::error::Result;
use crate::lua::Lua;
use crate::function::Function;
use crate::state::{callback_error_ext, ExtraData, Lua};
use crate::traits::{FromLuaMulti, IntoLua};
use crate::types::MaybeSend;
pub use require::{NavigateError, Require, TextRequirer};
// Since Luau has some missing standard functions, we re-implement them here
impl Lua {
/// Create a custom Luau `require` function using provided [`Require`] implementation to find
/// and load modules.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn create_require_function<R: Require + MaybeSend + 'static>(&self, require: R) -> Result<Function> {
require::create_require_function(self, require)
}
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)?)?;
globals.raw_set("collectgarbage", self.create_c_function(lua_collectgarbage)?)?;
globals.raw_set("loadstring", self.create_c_function(lua_loadstring)?)?;
// Set `_VERSION` global to include version number
// The environment variable `LUAU_VERSION` set by the build script
@@ -22,11 +34,10 @@ impl Lua {
globals.raw_set("_VERSION", format!("Luau {version}"))?;
}
Ok(())
}
// Enable default `require` implementation
let require = self.create_require_function(require::TextRequirer::new())?;
self.globals().raw_set("require", require)?;
pub(crate) fn disable_c_modules(&self) -> Result<()> {
package::disable_dylibs(self);
Ok(())
}
}
@@ -35,16 +46,17 @@ unsafe extern "C-unwind" fn lua_collectgarbage(state: *mut ffi::lua_State) -> c_
let option = ffi::luaL_optstring(state, 1, cstr!("collect"));
let option = CStr::from_ptr(option);
let arg = ffi::luaL_optinteger(state, 2, 0);
let is_sandboxed = (*ExtraData::get(state)).sandboxed;
match option.to_str() {
Ok("collect") => {
Ok("collect") if !is_sandboxed => {
ffi::lua_gc(state, ffi::LUA_GCCOLLECT, 0);
0
}
Ok("stop") => {
Ok("stop") if !is_sandboxed => {
ffi::lua_gc(state, ffi::LUA_GCSTOP, 0);
0
}
Ok("restart") => {
Ok("restart") if !is_sandboxed => {
ffi::lua_gc(state, ffi::LUA_GCRESTART, 0);
0
}
@@ -54,12 +66,12 @@ unsafe extern "C-unwind" fn lua_collectgarbage(state: *mut ffi::lua_State) -> c_
ffi::lua_pushnumber(state, kbytes + kbytes_rem / 1024.0);
1
}
Ok("step") => {
let res = ffi::lua_gc(state, ffi::LUA_GCSTEP, arg);
Ok("step") if !is_sandboxed => {
let res = ffi::lua_gc(state, ffi::LUA_GCSTEP, arg as _);
ffi::lua_pushboolean(state, res);
1
}
Ok("isrunning") => {
Ok("isrunning") if !is_sandboxed => {
let res = ffi::lua_gc(state, ffi::LUA_GCISRUNNING, 0);
ffi::lua_pushboolean(state, res);
1
@@ -68,21 +80,20 @@ unsafe extern "C-unwind" fn lua_collectgarbage(state: *mut ffi::lua_State) -> c_
}
}
// 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
unsafe extern "C-unwind" fn lua_loadstring(state: *mut ffi::lua_State) -> c_int {
callback_error_ext(state, ptr::null_mut(), false, move |extra, nargs| {
let rawlua = (*extra).raw_lua();
let (chunk, chunk_name) =
<(String, Option<String>)>::from_stack_args(nargs, 1, Some("loadstring"), rawlua)?;
let chunk_name = chunk_name.as_deref().unwrap_or("=(loadstring)");
(rawlua.lua())
.load(chunk)
.set_name(chunk_name)
.set_mode(ChunkMode::Text)
.into_function()?
.push_into_stack(rawlua)?;
Ok(1)
})
}
pub(crate) use package::register_package_module;
mod package;
mod require;
-268
View File
@@ -1,268 +0,0 @@
use std::ffi::CStr;
use std::fmt::Write;
use std::os::raw::c_int;
use std::path::{PathBuf, MAIN_SEPARATOR_STR};
use std::string::String as StdString;
use std::{env, fs};
use crate::chunk::ChunkMode;
use crate::error::Result;
use crate::lua::Lua;
use crate::table::Table;
use crate::types::RegistryKey;
use crate::value::{IntoLua, Value};
#[cfg(unix)]
use {libloading::Library, rustc_hash::FxHashMap};
//
// Luau package module
//
#[cfg(unix)]
const TARGET_MLUA_LUAU_ABI_VERSION: u32 = 1;
#[cfg(all(unix, feature = "module"))]
#[no_mangle]
#[used]
pub static MLUA_LUAU_ABI_VERSION: u32 = TARGET_MLUA_LUAU_ABI_VERSION;
// We keep reference to the `package` table in registry under this key
struct PackageKey(RegistryKey);
// We keep reference to the loaded dylibs in application data
#[cfg(unix)]
struct LoadedDylibs(FxHashMap<PathBuf, Library>);
#[cfg(unix)]
impl std::ops::Deref for LoadedDylibs {
type Target = FxHashMap<PathBuf, Library>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[cfg(unix)]
impl std::ops::DerefMut for LoadedDylibs {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
pub(crate) fn register_package_module(lua: &Lua) -> Result<()> {
// Create the package table and store it in app_data for later use (bypassing globals lookup)
let package = lua.create_table()?;
lua.set_app_data(PackageKey(lua.create_registry_value(package.clone())?));
// Set `package.path`
let mut search_path = env::var("LUAU_PATH")
.or_else(|_| env::var("LUA_PATH"))
.unwrap_or_default();
if search_path.is_empty() {
search_path = "?.luau;?.lua".to_string();
}
package.raw_set("path", search_path)?;
// Set `package.cpath`
#[cfg(unix)]
{
let mut search_cpath = env::var("LUAU_CPATH")
.or_else(|_| env::var("LUA_CPATH"))
.unwrap_or_default();
if search_cpath.is_empty() {
if cfg!(any(target_os = "macos", target_os = "ios")) {
search_cpath = "?.dylib".to_string();
} else {
search_cpath = "?.so".to_string();
}
}
package.raw_set("cpath", search_cpath)?;
}
// Set `package.loaded` (table with a list of loaded modules)
let loaded = lua.create_table()?;
package.raw_set("loaded", loaded.clone())?;
lua.set_named_registry_value("_LOADED", loaded)?;
// Set `package.loaders`
let loaders = lua.create_sequence_from([lua.create_function(lua_loader)?])?;
package.raw_set("loaders", loaders.clone())?;
#[cfg(unix)]
{
loaders.push(lua.create_function(dylib_loader)?)?;
lua.set_app_data(LoadedDylibs(FxHashMap::default()));
}
lua.set_named_registry_value("_LOADERS", loaders)?;
// Register the module and `require` function in globals
let globals = lua.globals();
globals.raw_set("package", package)?;
globals.raw_set("require", unsafe { lua.create_c_function(lua_require)? })?;
Ok(())
}
pub(crate) fn disable_dylibs(lua: &Lua) {
// Presence of `LoadedDylibs` in app data is used as a flag
// to check whether binary modules are enabled
#[cfg(unix)]
lua.remove_app_data::<LoadedDylibs>();
}
unsafe extern "C-unwind" fn lua_require(state: *mut ffi::lua_State) -> c_int {
ffi::lua_settop(state, 1);
let name = ffi::luaL_checkstring(state, 1);
ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("_LOADED")); // _LOADED is at index 2
if ffi::lua_rawgetfield(state, 2, name) != ffi::LUA_TNIL {
return 1; // module is already loaded
}
ffi::lua_pop(state, 1); // remove nil
// load the module
let err_buf = ffi::lua_newuserdata_t::<StdString>(state);
err_buf.write(StdString::new());
ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("_LOADERS")); // _LOADERS is at index 3
for i in 1.. {
if ffi::lua_rawgeti(state, -1, i) == ffi::LUA_TNIL {
// no more loaders?
if (*err_buf).is_empty() {
ffi::luaL_error(state, cstr!("module '%s' not found"), name);
} else {
let bytes = (*err_buf).as_bytes();
let extra = ffi::lua_pushlstring(state, bytes.as_ptr() as *const _, bytes.len());
ffi::luaL_error(state, cstr!("module '%s' not found:%s"), name, extra);
}
}
ffi::lua_pushvalue(state, 1); // name arg
ffi::lua_call(state, 1, 2); // call loader
match ffi::lua_type(state, -2) {
ffi::LUA_TFUNCTION => break, // loader found
ffi::LUA_TSTRING => {
// error message
let msg = ffi::lua_tostring(state, -2);
let msg = CStr::from_ptr(msg).to_string_lossy();
_ = write!(&mut *err_buf, "\n\t{msg}");
}
_ => {}
}
ffi::lua_pop(state, 2); // remove both results
}
ffi::lua_pushvalue(state, 1); // name is 1st argument to module loader
ffi::lua_rotate(state, -2, 1); // loader data <-> name
// stack: ...; loader function; module name; loader data
ffi::lua_call(state, 2, 1);
// stack: ...; result from loader function
if ffi::lua_isnil(state, -1) != 0 {
ffi::lua_pop(state, 1);
ffi::lua_pushboolean(state, 1); // use true as result
}
ffi::lua_pushvalue(state, -1); // make copy of entrypoint result
ffi::lua_setfield(state, 2, name); /* _LOADED[name] = returned value */
1
}
/// Searches for the given `name` in the given `path`.
///
/// `path` is a string containing a sequence of templates separated by semicolons.
fn package_searchpath(name: &str, search_path: &str, try_prefix: bool) -> Option<PathBuf> {
let mut names = vec![name.replace('.', MAIN_SEPARATOR_STR)];
if try_prefix && name.contains('.') {
let prefix = name.split_once('.').map(|(prefix, _)| prefix).unwrap();
names.push(prefix.to_string());
}
for path in search_path.split(';') {
for name in &names {
let file_path = PathBuf::from(path.replace('?', name));
if let Ok(true) = fs::metadata(&file_path).map(|m| m.is_file()) {
return Some(file_path);
}
}
}
None
}
//
// Module loaders
//
/// Tries to load a lua (text) file
fn lua_loader(lua: &Lua, modname: StdString) -> Result<Value> {
let package = {
let key = lua.app_data_ref::<PackageKey>().unwrap();
lua.registry_value::<Table>(&key.0)
}?;
let search_path = package.get::<_, StdString>("path").unwrap_or_default();
if let Some(file_path) = package_searchpath(&modname, &search_path, false) {
match fs::read(&file_path) {
Ok(buf) => {
return lua
.load(&buf)
.set_name(&format!("={}", file_path.display()))
.set_mode(ChunkMode::Text)
.into_function()
.map(Value::Function);
}
Err(err) => {
return format!("cannot open '{}': {err}", file_path.display()).into_lua(lua);
}
}
}
Ok(Value::Nil)
}
/// Tries to load a dynamic library
#[cfg(unix)]
fn dylib_loader(lua: &Lua, modname: StdString) -> Result<Value> {
let package = {
let key = lua.app_data_ref::<PackageKey>().unwrap();
lua.registry_value::<Table>(&key.0)
}?;
let search_cpath = package.get::<_, StdString>("cpath").unwrap_or_default();
let find_symbol = |lib: &Library| unsafe {
if let Ok(entry) = lib.get::<ffi::lua_CFunction>(format!("luaopen_{modname}\0").as_bytes())
{
return lua.create_c_function(*entry).map(Value::Function);
}
// Try all in one mode
if let Ok(entry) = lib.get::<ffi::lua_CFunction>(
format!("luaopen_{}\0", modname.replace('.', "_")).as_bytes(),
) {
return lua.create_c_function(*entry).map(Value::Function);
}
"cannot find module entrypoint".into_lua(lua)
};
if let Some(file_path) = package_searchpath(&modname, &search_cpath, true) {
let file_path = file_path.canonicalize()?;
// Load the library and check for symbol
unsafe {
let mut loaded_dylibs = match lua.app_data_mut::<LoadedDylibs>() {
Some(loaded_dylibs) => loaded_dylibs,
None => return "dynamic libraries are disabled in safe mode".into_lua(lua),
};
// Check if it's already loaded
if let Some(lib) = loaded_dylibs.get(&file_path) {
return find_symbol(lib);
}
if let Ok(lib) = Library::new(&file_path) {
// Check version
let mod_version = lib.get::<*const u32>(b"MLUA_LUAU_ABI_VERSION");
let mod_version = mod_version.map(|v| **v).unwrap_or_default();
if mod_version != TARGET_MLUA_LUAU_ABI_VERSION {
let err = format!("wrong module ABI version (expected {TARGET_MLUA_LUAU_ABI_VERSION}, got {mod_version})");
return err.into_lua(lua);
}
let symbol = find_symbol(&lib);
loaded_dylibs.insert(file_path, lib);
return symbol;
}
}
}
Ok(Value::Nil)
}
+671
View File
@@ -0,0 +1,671 @@
use std::cell::RefCell;
use std::collections::VecDeque;
use std::ffi::CStr;
use std::io::Result as IoResult;
use std::ops::{Deref, DerefMut};
use std::os::raw::{c_char, c_int, c_void};
use std::path::{Component, Path, PathBuf};
use std::result::Result as StdResult;
use std::{env, fmt, fs, mem, ptr};
use crate::error::{Error, Result};
use crate::function::Function;
use crate::state::{callback_error_ext, Lua};
use crate::table::Table;
use crate::types::MaybeSend;
/// An error that can occur during navigation in the Luau `require-by-string` system.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
#[derive(Debug, Clone)]
pub enum NavigateError {
Ambiguous,
NotFound,
Other(Error),
}
#[cfg(feature = "luau")]
trait IntoNavigateResult {
fn into_nav_result(self) -> Result<ffi::luarequire_NavigateResult>;
}
#[cfg(feature = "luau")]
impl IntoNavigateResult for StdResult<(), NavigateError> {
fn into_nav_result(self) -> Result<ffi::luarequire_NavigateResult> {
match self {
Ok(()) => Ok(ffi::luarequire_NavigateResult::Success),
Err(NavigateError::Ambiguous) => Ok(ffi::luarequire_NavigateResult::Ambiguous),
Err(NavigateError::NotFound) => Ok(ffi::luarequire_NavigateResult::NotFound),
Err(NavigateError::Other(err)) => Err(err),
}
}
}
impl From<Error> for NavigateError {
fn from(err: Error) -> Self {
NavigateError::Other(err)
}
}
#[cfg(feature = "luau")]
type WriteResult = ffi::luarequire_WriteResult;
/// A trait for handling modules loading and navigation in the Luau `require-by-string` system.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub trait Require {
/// Returns `true` if "require" is permitted for the given chunk name.
fn is_require_allowed(&self, chunk_name: &str) -> bool;
/// Resets the internal state to point at the requirer module.
fn reset(&mut self, chunk_name: &str) -> StdResult<(), NavigateError>;
/// Resets the internal state to point at an aliased module.
///
/// This function received an exact path from a configuration file.
/// It's only called when an alias's path cannot be resolved relative to its
/// configuration file.
fn jump_to_alias(&mut self, path: &str) -> StdResult<(), NavigateError>;
// Navigate to parent directory
fn to_parent(&mut self) -> StdResult<(), NavigateError>;
/// Navigate to the given child directory.
fn to_child(&mut self, name: &str) -> StdResult<(), NavigateError>;
/// Returns whether the context is currently pointing at a module
fn has_module(&self) -> bool;
/// Provides a cache key representing the current module.
///
/// This function is only called if `has_module` returns true.
fn cache_key(&self) -> String;
/// Returns whether a configuration is present in the current context.
fn has_config(&self) -> bool;
/// Returns the contents of the configuration file in the current context.
///
/// This function is only called if `has_config` returns true.
fn config(&self) -> IoResult<Vec<u8>>;
/// Returns a loader function for the current module, that when called, loads the module
/// and returns the result.
///
/// Loader can be sync or async.
/// This function is only called if `has_module` returns true.
fn loader(&self, lua: &Lua) -> Result<Function>;
}
impl fmt::Debug for dyn Require {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "<dyn Require>")
}
}
/// The standard implementation of Luau `require-by-string` navigation.
#[derive(Default, Debug)]
pub struct TextRequirer {
/// An absolute path to the current Luau module (not mapped to a physical file)
abs_path: PathBuf,
/// A relative path to the current Luau module (not mapped to a physical file)
rel_path: PathBuf,
/// A physical path to the current Luau module, which is a file or a directory with an
/// `init.lua(u)` file
resolved_path: Option<PathBuf>,
}
impl TextRequirer {
/// The prefix used for chunk names in the require system.
/// Only chunk names starting with this prefix are allowed to be used in `require`.
const CHUNK_PREFIX: &str = "@";
/// The file extensions that are considered valid for Luau modules.
const FILE_EXTENSIONS: &[&str] = &["luau", "lua"];
/// Creates a new `TextRequirer` instance.
pub fn new() -> Self {
Self::default()
}
fn normalize_chunk_name(chunk_name: &str) -> &str {
if let Some((path, line)) = chunk_name.split_once(':') {
if line.parse::<u32>().is_ok() {
return path;
}
}
chunk_name
}
// Normalizes the path by removing unnecessary components
fn normalize_path(path: &Path) -> PathBuf {
let mut components = VecDeque::new();
for comp in path.components() {
match comp {
Component::Prefix(..) | Component::RootDir => {
components.push_back(comp);
}
Component::CurDir => {}
Component::ParentDir => {
if matches!(components.back(), None | Some(Component::ParentDir)) {
components.push_back(Component::ParentDir);
} else if matches!(components.back(), Some(Component::Normal(..))) {
components.pop_back();
}
}
Component::Normal(..) => components.push_back(comp),
}
}
if matches!(components.front(), None | Some(Component::Normal(..))) {
components.push_front(Component::CurDir);
}
// Join the components back together
components.into_iter().collect()
}
/// Resolve a Luau module path to a physical file or directory.
///
/// Empty directories without init files are considered valid as "intermediate" directories.
fn resolve_module(path: &Path) -> StdResult<Option<PathBuf>, NavigateError> {
let mut found_path = None;
if path.components().next_back() != Some(Component::Normal("init".as_ref())) {
let current_ext = (path.extension().and_then(|s| s.to_str()))
.map(|s| format!("{s}."))
.unwrap_or_default();
for ext in Self::FILE_EXTENSIONS {
let candidate = path.with_extension(format!("{current_ext}{ext}"));
if candidate.is_file() && found_path.replace(candidate).is_some() {
return Err(NavigateError::Ambiguous);
}
}
}
if path.is_dir() {
for component in Self::FILE_EXTENSIONS.iter().map(|ext| format!("init.{ext}")) {
let candidate = path.join(component);
if candidate.is_file() && found_path.replace(candidate).is_some() {
return Err(NavigateError::Ambiguous);
}
}
if found_path.is_none() {
// Directories without init files are considered valid "intermediate" path
return Ok(None);
}
}
Ok(Some(found_path.ok_or(NavigateError::NotFound)?))
}
}
impl Require for TextRequirer {
fn is_require_allowed(&self, chunk_name: &str) -> bool {
chunk_name.starts_with(Self::CHUNK_PREFIX)
}
fn reset(&mut self, chunk_name: &str) -> StdResult<(), NavigateError> {
if !chunk_name.starts_with(Self::CHUNK_PREFIX) {
return Err(NavigateError::NotFound);
}
let chunk_name = Self::normalize_chunk_name(&chunk_name[1..]);
let chunk_path = Self::normalize_path(chunk_name.as_ref());
if chunk_path.extension() == Some("rs".as_ref()) {
// Special case for Rust source files, reset to the current directory
let chunk_filename = chunk_path.file_name().unwrap();
let cwd = env::current_dir().map_err(|_| NavigateError::NotFound)?;
self.abs_path = Self::normalize_path(&cwd.join(chunk_filename));
self.rel_path = ([Component::CurDir, Component::Normal(chunk_filename)].into_iter()).collect();
self.resolved_path = None;
return Ok(());
}
if chunk_path.is_absolute() {
let resolved_path = Self::resolve_module(&chunk_path)?;
self.abs_path = chunk_path.clone();
self.rel_path = chunk_path;
self.resolved_path = resolved_path;
} else {
// Relative path
let cwd = env::current_dir().map_err(|_| NavigateError::NotFound)?;
let abs_path = Self::normalize_path(&cwd.join(&chunk_path));
let resolved_path = Self::resolve_module(&abs_path)?;
self.abs_path = abs_path;
self.rel_path = chunk_path;
self.resolved_path = resolved_path;
}
Ok(())
}
fn jump_to_alias(&mut self, path: &str) -> StdResult<(), NavigateError> {
let path = Self::normalize_path(path.as_ref());
let resolved_path = Self::resolve_module(&path)?;
self.abs_path = path.clone();
self.rel_path = path;
self.resolved_path = resolved_path;
Ok(())
}
fn to_parent(&mut self) -> StdResult<(), NavigateError> {
let mut abs_path = self.abs_path.clone();
if !abs_path.pop() {
// It's important to return `NotFound` if we reached the root, as it's a "recoverable" error if we
// cannot go beyond the root directory.
// Luau "require-by-string` has a special logic to search for config file to resolve aliases.
return Err(NavigateError::NotFound);
}
let mut rel_parent = self.rel_path.clone();
rel_parent.pop();
let resolved_path = Self::resolve_module(&abs_path)?;
self.abs_path = abs_path;
self.rel_path = Self::normalize_path(&rel_parent);
self.resolved_path = resolved_path;
Ok(())
}
fn to_child(&mut self, name: &str) -> StdResult<(), NavigateError> {
let abs_path = self.abs_path.join(name);
let rel_path = self.rel_path.join(name);
let resolved_path = Self::resolve_module(&abs_path)?;
self.abs_path = abs_path;
self.rel_path = rel_path;
self.resolved_path = resolved_path;
Ok(())
}
fn has_module(&self) -> bool {
(self.resolved_path.as_deref())
.map(Path::is_file)
.unwrap_or(false)
}
fn cache_key(&self) -> String {
self.resolved_path.as_deref().unwrap().display().to_string()
}
fn has_config(&self) -> bool {
self.abs_path.is_dir() && self.abs_path.join(".luaurc").is_file()
}
fn config(&self) -> IoResult<Vec<u8>> {
fs::read(self.abs_path.join(".luaurc"))
}
fn loader(&self, lua: &Lua) -> Result<Function> {
let name = format!("@{}", self.rel_path.display());
lua.load(self.resolved_path.as_deref().unwrap())
.set_name(name)
.into_function()
}
}
struct Context(Box<dyn Require>);
impl Deref for Context {
type Target = dyn Require;
fn deref(&self) -> &Self::Target {
&*self.0
}
}
impl DerefMut for Context {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut *self.0
}
}
macro_rules! try_borrow {
($state:expr, $ctx:expr) => {
match (*($ctx as *const RefCell<Context>)).try_borrow() {
Ok(ctx) => ctx,
Err(_) => ffi::luaL_error($state, cstr!("require context is already borrowed")),
}
};
}
macro_rules! try_borrow_mut {
($state:expr, $ctx:expr) => {
match (*($ctx as *const RefCell<Context>)).try_borrow_mut() {
Ok(ctx) => ctx,
Err(_) => ffi::luaL_error($state, cstr!("require context is already borrowed")),
}
};
}
#[cfg(feature = "luau")]
pub(super) unsafe extern "C-unwind" fn init_config(config: *mut ffi::luarequire_Configuration) {
if config.is_null() {
return;
}
unsafe extern "C-unwind" fn is_require_allowed(
state: *mut ffi::lua_State,
ctx: *mut c_void,
requirer_chunkname: *const c_char,
) -> bool {
if requirer_chunkname.is_null() {
return false;
}
let this = try_borrow!(state, ctx);
let chunk_name = CStr::from_ptr(requirer_chunkname).to_string_lossy();
this.is_require_allowed(&chunk_name)
}
unsafe extern "C-unwind" fn reset(
state: *mut ffi::lua_State,
ctx: *mut c_void,
requirer_chunkname: *const c_char,
) -> ffi::luarequire_NavigateResult {
let mut this = try_borrow_mut!(state, ctx);
let chunk_name = CStr::from_ptr(requirer_chunkname).to_string_lossy();
callback_error_ext(state, ptr::null_mut(), true, move |_, _| {
this.reset(&chunk_name).into_nav_result()
})
}
unsafe extern "C-unwind" fn jump_to_alias(
state: *mut ffi::lua_State,
ctx: *mut c_void,
path: *const c_char,
) -> ffi::luarequire_NavigateResult {
let mut this = try_borrow_mut!(state, ctx);
let path = CStr::from_ptr(path).to_string_lossy();
callback_error_ext(state, ptr::null_mut(), true, move |_, _| {
this.jump_to_alias(&path).into_nav_result()
})
}
unsafe extern "C-unwind" fn to_parent(
state: *mut ffi::lua_State,
ctx: *mut c_void,
) -> ffi::luarequire_NavigateResult {
let mut this = try_borrow_mut!(state, ctx);
callback_error_ext(state, ptr::null_mut(), true, move |_, _| {
this.to_parent().into_nav_result()
})
}
unsafe extern "C-unwind" fn to_child(
state: *mut ffi::lua_State,
ctx: *mut c_void,
name: *const c_char,
) -> ffi::luarequire_NavigateResult {
let mut this = try_borrow_mut!(state, ctx);
let name = CStr::from_ptr(name).to_string_lossy();
callback_error_ext(state, ptr::null_mut(), true, move |_, _| {
this.to_child(&name).into_nav_result()
})
}
unsafe extern "C-unwind" fn is_module_present(state: *mut ffi::lua_State, ctx: *mut c_void) -> bool {
let this = try_borrow!(state, ctx);
this.has_module()
}
unsafe extern "C-unwind" fn get_chunkname(
_state: *mut ffi::lua_State,
_ctx: *mut c_void,
buffer: *mut c_char,
buffer_size: usize,
size_out: *mut usize,
) -> WriteResult {
write_to_buffer(buffer, buffer_size, size_out, &[])
}
unsafe extern "C-unwind" fn get_loadname(
_state: *mut ffi::lua_State,
_ctx: *mut c_void,
buffer: *mut c_char,
buffer_size: usize,
size_out: *mut usize,
) -> WriteResult {
write_to_buffer(buffer, buffer_size, size_out, &[])
}
unsafe extern "C-unwind" fn get_cache_key(
state: *mut ffi::lua_State,
ctx: *mut c_void,
buffer: *mut c_char,
buffer_size: usize,
size_out: *mut usize,
) -> WriteResult {
let this = try_borrow!(state, ctx);
let cache_key = this.cache_key();
write_to_buffer(buffer, buffer_size, size_out, cache_key.as_bytes())
}
unsafe extern "C-unwind" fn is_config_present(state: *mut ffi::lua_State, ctx: *mut c_void) -> bool {
let this = try_borrow!(state, ctx);
this.has_config()
}
unsafe extern "C-unwind" fn get_config(
state: *mut ffi::lua_State,
ctx: *mut c_void,
buffer: *mut c_char,
buffer_size: usize,
size_out: *mut usize,
) -> WriteResult {
let this = try_borrow!(state, ctx);
let config = callback_error_ext(state, ptr::null_mut(), true, move |_, _| Ok(this.config()?));
write_to_buffer(buffer, buffer_size, size_out, &config)
}
unsafe extern "C-unwind" fn load(
state: *mut ffi::lua_State,
ctx: *mut c_void,
_path: *const c_char,
_chunkname: *const c_char,
_loadname: *const c_char,
) -> c_int {
let this = try_borrow!(state, ctx);
callback_error_ext(state, ptr::null_mut(), true, move |extra, _| {
let rawlua = (*extra).raw_lua();
let loader = this.loader(rawlua.lua())?;
rawlua.push(loader)?;
Ok(1)
})
}
(*config).is_require_allowed = is_require_allowed;
(*config).reset = reset;
(*config).jump_to_alias = jump_to_alias;
(*config).to_parent = to_parent;
(*config).to_child = to_child;
(*config).is_module_present = is_module_present;
(*config).get_chunkname = get_chunkname;
(*config).get_loadname = get_loadname;
(*config).get_cache_key = get_cache_key;
(*config).is_config_present = is_config_present;
(*config).get_alias = None;
(*config).get_config = Some(get_config);
(*config).load = load;
}
/// Helper function to write data to a buffer
#[cfg(feature = "luau")]
unsafe fn write_to_buffer(
buffer: *mut c_char,
buffer_size: usize,
size_out: *mut usize,
data: &[u8],
) -> WriteResult {
// the buffer must be null terminated as it's a c++ `std::string` data() buffer
let is_null_terminated = data.last() == Some(&0);
*size_out = data.len() + if is_null_terminated { 0 } else { 1 };
if *size_out > buffer_size {
return WriteResult::BufferTooSmall;
}
ptr::copy_nonoverlapping(data.as_ptr(), buffer as *mut _, data.len());
if !is_null_terminated {
*buffer.add(data.len()) = 0;
}
WriteResult::Success
}
#[cfg(feature = "luau")]
pub(super) fn create_require_function<R: Require + MaybeSend + 'static>(
lua: &Lua,
require: R,
) -> Result<Function> {
unsafe extern "C-unwind" fn find_current_file(state: *mut ffi::lua_State) -> c_int {
let mut ar: ffi::lua_Debug = mem::zeroed();
for level in 2.. {
if ffi::lua_getinfo(state, level, cstr!("s"), &mut ar) == 0 {
ffi::luaL_error(state, cstr!("require is not supported in this context"));
}
if CStr::from_ptr(ar.what) != c"C" {
break;
}
}
ffi::lua_pushstring(state, ar.source);
1
}
unsafe extern "C-unwind" fn get_cache_key(state: *mut ffi::lua_State) -> c_int {
let ctx = ffi::lua_touserdata(state, ffi::lua_upvalueindex(1));
let ctx = try_borrow!(state, ctx);
let cache_key = ctx.cache_key();
ffi::lua_pushlstring(state, cache_key.as_ptr() as *const _, cache_key.len());
1
}
let (get_cache_key, find_current_file, proxyrequire, registered_modules, loader_cache) = unsafe {
lua.exec_raw::<(Function, Function, Function, Table, Table)>((), move |state| {
let context = Context(Box::new(require));
let context_ptr = ffi::lua_newuserdata_t(state, RefCell::new(context));
ffi::lua_pushcclosured(state, get_cache_key, cstr!("get_cache_key"), 1);
ffi::lua_pushcfunctiond(state, find_current_file, cstr!("find_current_file"));
ffi::luarequire_pushproxyrequire(state, init_config, context_ptr as *mut _);
ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, ffi::LUA_REGISTERED_MODULES_TABLE);
ffi::luaL_getsubtable(state, ffi::LUA_REGISTRYINDEX, cstr!("__MLUA_LOADER_CACHE"));
})
}?;
unsafe extern "C-unwind" fn error(state: *mut ffi::lua_State) -> c_int {
ffi::luaL_where(state, 1);
ffi::lua_pushvalue(state, 1);
ffi::lua_concat(state, 2);
ffi::lua_error(state);
}
unsafe extern "C-unwind" fn r#type(state: *mut ffi::lua_State) -> c_int {
ffi::lua_pushstring(state, ffi::lua_typename(state, ffi::lua_type(state, 1)));
1
}
let (error, r#type) = unsafe {
lua.exec_raw::<(Function, Function)>((), move |state| {
ffi::lua_pushcfunctiond(state, error, cstr!("error"));
ffi::lua_pushcfunctiond(state, r#type, cstr!("type"));
})
}?;
// Prepare environment for the "require" function
let env = lua.create_table_with_capacity(0, 7)?;
env.raw_set("get_cache_key", get_cache_key)?;
env.raw_set("find_current_file", find_current_file)?;
env.raw_set("proxyrequire", proxyrequire)?;
env.raw_set("REGISTERED_MODULES", registered_modules)?;
env.raw_set("LOADER_CACHE", loader_cache)?;
env.raw_set("error", error)?;
env.raw_set("type", r#type)?;
lua.load(
r#"
local path = ...
if type(path) ~= "string" then
error("bad argument #1 to 'require' (string expected, got " .. type(path) .. ")")
end
-- Check if the module (path) is explicitly registered
local maybe_result = REGISTERED_MODULES[path]
if maybe_result ~= nil then
return maybe_result
end
local loader = proxyrequire(path, find_current_file())
local cache_key = get_cache_key()
-- Check if the loader result is already cached
local result = LOADER_CACHE[cache_key]
if result ~= nil then
return result
end
-- Call the loader function and cache the result
result = loader()
if result == nil then
result = true
end
LOADER_CACHE[cache_key] = result
return result
"#,
)
.try_cache()
.set_name("=__mlua_require")
.set_environment(env)
.into_function()
}
#[cfg(test)]
mod tests {
use std::path::Path;
use super::TextRequirer;
#[test]
fn test_path_normalize() {
for (input, expected) in [
// Basic formatting checks
("", "./"),
(".", "./"),
("a/relative/path", "./a/relative/path"),
// Paths containing extraneous '.' and '/' symbols
("./remove/extraneous/symbols/", "./remove/extraneous/symbols"),
("./remove/extraneous//symbols", "./remove/extraneous/symbols"),
("./remove/extraneous/symbols/.", "./remove/extraneous/symbols"),
("./remove/extraneous/./symbols", "./remove/extraneous/symbols"),
("../remove/extraneous/symbols/", "../remove/extraneous/symbols"),
("../remove/extraneous//symbols", "../remove/extraneous/symbols"),
("../remove/extraneous/symbols/.", "../remove/extraneous/symbols"),
("../remove/extraneous/./symbols", "../remove/extraneous/symbols"),
("/remove/extraneous/symbols/", "/remove/extraneous/symbols"),
("/remove/extraneous//symbols", "/remove/extraneous/symbols"),
("/remove/extraneous/symbols/.", "/remove/extraneous/symbols"),
("/remove/extraneous/./symbols", "/remove/extraneous/symbols"),
// Paths containing '..'
("./remove/me/..", "./remove"),
("./remove/me/../", "./remove"),
("../remove/me/..", "../remove"),
("../remove/me/../", "../remove"),
("/remove/me/..", "/remove"),
("/remove/me/../", "/remove"),
("./..", "../"),
("./../", "../"),
("../..", "../../"),
("../../", "../../"),
// '..' disappears if path is absolute and component is non-erasable
("/../", "/"),
] {
let path = TextRequirer::normalize_path(input.as_ref());
assert_eq!(
&path,
expected.as_ref() as &Path,
"wrong normalization for {input}"
);
}
}
}
+1 -2
View File
@@ -10,8 +10,7 @@ macro_rules! bug_msg {
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
concat!($s, "\0") as *const str as *const [::std::os::raw::c_char] as *const ::std::os::raw::c_char
};
}
+23 -6
View File
@@ -18,15 +18,32 @@ pub(crate) struct MemoryState {
}
impl MemoryState {
#[cfg(feature = "luau")]
#[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");
ffi::lua_getallocf(state, &mut mem_state);
mlua_assert!(!mem_state.is_null(), "Luau state has no allocator userdata");
mem_state as *mut MemoryState
}
#[cfg(not(feature = "luau"))]
#[rustversion::since(1.85)]
#[inline]
#[allow(clippy::incompatible_msrv)]
pub(crate) unsafe fn get(state: *mut ffi::lua_State) -> *mut Self {
let mut mem_state = ptr::null_mut();
if !ptr::fn_addr_eq(ffi::lua_getallocf(state, &mut mem_state), ALLOCATOR) {
mem_state = ptr::null_mut();
}
#[cfg(not(feature = "luau"))]
mem_state as *mut MemoryState
}
#[cfg(not(feature = "luau"))]
#[rustversion::before(1.85)]
#[inline]
pub(crate) unsafe fn get(state: *mut ffi::lua_State) -> *mut Self {
let mut mem_state = ptr::null_mut();
if ffi::lua_getallocf(state, &mut mem_state) != ALLOCATOR {
mem_state = ptr::null_mut();
}
@@ -80,7 +97,7 @@ impl MemoryState {
}
}
unsafe extern "C-unwind" fn allocator(
unsafe extern "C" fn allocator(
extra: *mut c_void,
ptr: *mut c_void,
osize: usize,
+221 -102
View File
@@ -1,108 +1,212 @@
use std::collections::{vec_deque, VecDeque};
use std::iter::FromIterator;
use std::mem;
use std::ops::{Deref, DerefMut};
use std::os::raw::c_int;
use std::result::Result as StdResult;
use crate::error::Result;
use crate::lua::Lua;
use crate::state::{Lua, RawLua};
use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti};
use crate::util::check_stack;
use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, MultiValue, Nil};
use crate::value::{Nil, Value};
/// 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.
impl<'lua, T: IntoLua<'lua>, E: IntoLua<'lua>> IntoLuaMulti<'lua> for StdResult<T, E> {
impl<T: IntoLua, E: IntoLua> IntoLuaMulti for StdResult<T, E> {
#[inline]
fn into_lua_multi(self, lua: &'lua Lua) -> Result<MultiValue<'lua>> {
let mut result = MultiValue::with_lua_and_capacity(lua, 2);
fn into_lua_multi(self, lua: &Lua) -> Result<MultiValue> {
match self {
Ok(v) => result.push_front(v.into_lua(lua)?),
Err(e) => {
result.push_front(e.into_lua(lua)?);
result.push_front(Nil);
}
Ok(val) => (val,).into_lua_multi(lua),
Err(err) => (Nil, err).into_lua_multi(lua),
}
Ok(result)
}
}
impl<'lua, E: IntoLua<'lua>> IntoLuaMulti<'lua> for StdResult<(), E> {
#[inline]
fn into_lua_multi(self, lua: &'lua Lua) -> Result<MultiValue<'lua>> {
unsafe fn push_into_stack_multi(self, lua: &RawLua) -> Result<c_int> {
match self {
Ok(_) => return Ok(MultiValue::new()),
Err(e) => {
let mut result = MultiValue::with_lua_and_capacity(lua, 2);
result.push_front(e.into_lua(lua)?);
result.push_front(Nil);
Ok(result)
}
Ok(val) => (val,).push_into_stack_multi(lua),
Err(err) => (Nil, err).push_into_stack_multi(lua),
}
}
}
impl<'lua, T: IntoLua<'lua>> IntoLuaMulti<'lua> for T {
impl<E: IntoLua> IntoLuaMulti for StdResult<(), E> {
#[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)?);
fn into_lua_multi(self, lua: &Lua) -> Result<MultiValue> {
match self {
Ok(_) => const { Ok(MultiValue::new()) },
Err(err) => (Nil, err).into_lua_multi(lua),
}
}
#[inline]
unsafe fn push_into_stack_multi(self, lua: &RawLua) -> Result<c_int> {
match self {
Ok(_) => Ok(0),
Err(err) => (Nil, err).push_into_stack_multi(lua),
}
}
}
impl<T: IntoLua> IntoLuaMulti for T {
#[inline]
fn into_lua_multi(self, lua: &Lua) -> Result<MultiValue> {
let mut v = MultiValue::with_capacity(1);
v.push_back(self.into_lua(lua)?);
Ok(v)
}
#[inline]
unsafe fn push_into_stack_multi(self, lua: &'lua Lua) -> Result<c_int> {
unsafe fn push_into_stack_multi(self, lua: &RawLua) -> Result<c_int> {
self.push_into_stack(lua)?;
Ok(1)
}
}
impl<'lua, T: FromLua<'lua>> FromLuaMulti<'lua> for T {
impl<T: FromLua> FromLuaMulti for T {
#[inline]
fn from_lua_multi(mut values: MultiValue<'lua>, lua: &'lua Lua) -> Result<Self> {
fn from_lua_multi(mut values: MultiValue, lua: &Lua) -> Result<Self> {
T::from_lua(values.pop_front().unwrap_or(Nil), lua)
}
#[inline]
fn from_lua_args(
mut args: MultiValue<'lua>,
i: usize,
to: Option<&str>,
lua: &'lua Lua,
) -> Result<Self> {
fn from_lua_args(mut args: MultiValue, i: usize, to: Option<&str>, 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> {
unsafe fn from_stack_multi(nvals: c_int, lua: &RawLua) -> Result<Self> {
if nvals == 0 {
return T::from_lua(Nil, lua);
return T::from_lua(Nil, lua.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> {
unsafe fn from_stack_args(nargs: c_int, i: usize, to: Option<&str>, lua: &RawLua) -> Result<Self> {
if nargs == 0 {
return T::from_lua_arg(Nil, i, to, lua);
return T::from_lua_arg(Nil, i, to, lua.lua());
}
T::from_stack_arg(-nargs, i, to, lua)
}
}
impl<'lua> IntoLuaMulti<'lua> for MultiValue<'lua> {
/// Multiple Lua values used for both argument passing and also for multiple return values.
#[derive(Default, Debug, Clone)]
pub struct MultiValue(VecDeque<Value>);
impl Deref for MultiValue {
type Target = VecDeque<Value>;
#[inline]
fn into_lua_multi(self, _: &'lua Lua) -> Result<MultiValue<'lua>> {
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for MultiValue {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl MultiValue {
/// Creates an empty `MultiValue` containing no values.
#[inline]
pub const fn new() -> MultiValue {
MultiValue(VecDeque::new())
}
/// Creates an empty `MultiValue` container with space for at least `capacity` elements.
pub fn with_capacity(capacity: usize) -> MultiValue {
MultiValue(VecDeque::with_capacity(capacity))
}
/// Creates a `MultiValue` container from vector of values.
///
/// This method works in *O*(1) time and does not allocate any additional memory.
#[inline]
pub fn from_vec(vec: Vec<Value>) -> MultiValue {
vec.into()
}
/// Consumes the `MultiValue` and returns a vector of values.
///
/// This method needs *O*(*n*) data movement if the circular buffer doesn't happen to be at the
/// beginning of the allocation.
#[inline]
pub fn into_vec(self) -> Vec<Value> {
self.into()
}
#[inline]
pub(crate) fn from_lua_iter<T: IntoLua>(lua: &Lua, iter: impl IntoIterator<Item = T>) -> Result<Self> {
let iter = iter.into_iter();
let mut multi_value = MultiValue::with_capacity(iter.size_hint().0);
for value in iter {
multi_value.push_back(value.into_lua(lua)?);
}
Ok(multi_value)
}
}
impl From<Vec<Value>> for MultiValue {
#[inline]
fn from(value: Vec<Value>) -> Self {
MultiValue(value.into())
}
}
impl From<MultiValue> for Vec<Value> {
#[inline]
fn from(value: MultiValue) -> Self {
value.0.into()
}
}
impl FromIterator<Value> for MultiValue {
#[inline]
fn from_iter<I: IntoIterator<Item = Value>>(iter: I) -> Self {
let mut multi_value = MultiValue::new();
multi_value.extend(iter);
multi_value
}
}
impl IntoIterator for MultiValue {
type Item = Value;
type IntoIter = vec_deque::IntoIter<Value>;
#[inline]
fn into_iter(mut self) -> Self::IntoIter {
let deque = mem::take(&mut self.0);
mem::forget(self);
deque.into_iter()
}
}
impl<'a> IntoIterator for &'a MultiValue {
type Item = &'a Value;
type IntoIter = vec_deque::Iter<'a, Value>;
#[inline]
fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}
impl IntoLuaMulti for MultiValue {
#[inline]
fn into_lua_multi(self, _: &Lua) -> Result<MultiValue> {
Ok(self)
}
}
impl<'lua> FromLuaMulti<'lua> for MultiValue<'lua> {
impl FromLuaMulti for MultiValue {
#[inline]
fn from_lua_multi(values: MultiValue<'lua>, _: &'lua Lua) -> Result<Self> {
fn from_lua_multi(values: MultiValue, _: &Lua) -> Result<Self> {
Ok(values)
}
}
@@ -130,10 +234,7 @@ impl<'lua> FromLuaMulti<'lua> for MultiValue<'lua> {
/// # Ok(())
/// # }
/// ```
///
/// [`FromLua`]: crate::FromLua
/// [`MultiValue`]: crate::MultiValue
#[derive(Debug, Clone)]
#[derive(Default, Debug, Clone)]
pub struct Variadic<T>(Vec<T>);
impl<T> Variadic<T> {
@@ -141,11 +242,38 @@ impl<T> Variadic<T> {
pub const fn new() -> Variadic<T> {
Variadic(Vec::new())
}
/// Creates an empty `Variadic` container with space for at least `capacity` elements.
pub fn with_capacity(capacity: usize) -> Variadic<T> {
Variadic(Vec::with_capacity(capacity))
}
}
impl<T> Default for Variadic<T> {
fn default() -> Variadic<T> {
Variadic::new()
impl<T> Deref for Variadic<T> {
type Target = Vec<T>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> DerefMut for Variadic<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<T> From<Vec<T>> for Variadic<T> {
#[inline]
fn from(vec: Vec<T>) -> Self {
Variadic(vec)
}
}
impl<T> From<Variadic<T>> for Vec<T> {
#[inline]
fn from(value: Variadic<T>) -> Self {
value.0
}
}
@@ -164,35 +292,19 @@ impl<T> IntoIterator for Variadic<T> {
}
}
impl<T> Deref for Variadic<T> {
type Target = Vec<T>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> DerefMut for Variadic<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl<'lua, T: IntoLua<'lua>> IntoLuaMulti<'lua> for Variadic<T> {
impl<T: IntoLua> IntoLuaMulti for Variadic<T> {
#[inline]
fn into_lua_multi(self, lua: &'lua Lua) -> Result<MultiValue<'lua>> {
let mut values = MultiValue::with_lua_and_capacity(lua, self.0.len());
values.refill(self.0.into_iter().map(|e| e.into_lua(lua)))?;
Ok(values)
fn into_lua_multi(self, lua: &Lua) -> Result<MultiValue> {
MultiValue::from_lua_iter(lua, self)
}
}
impl<'lua, T: FromLua<'lua>> FromLuaMulti<'lua> for Variadic<T> {
impl<T: FromLua> FromLuaMulti for Variadic<T> {
#[inline]
fn from_lua_multi(mut values: MultiValue<'lua>, lua: &'lua Lua) -> Result<Self> {
fn from_lua_multi(mut values: MultiValue, lua: &Lua) -> Result<Self> {
values
.drain_all()
.map(|e| T::from_lua(e, lua))
.drain(..)
.map(|val| T::from_lua(val, lua))
.collect::<Result<Vec<T>>>()
.map(Variadic)
}
@@ -200,42 +312,39 @@ impl<'lua, T: FromLua<'lua>> FromLuaMulti<'lua> for Variadic<T> {
macro_rules! impl_tuple {
() => (
impl<'lua> IntoLuaMulti<'lua> for () {
impl IntoLuaMulti for () {
#[inline]
fn into_lua_multi(self, lua: &'lua Lua) -> Result<MultiValue<'lua>> {
Ok(MultiValue::with_lua_and_capacity(lua, 0))
fn into_lua_multi(self, _: &Lua) -> Result<MultiValue> {
const { Ok(MultiValue::new()) }
}
#[inline]
unsafe fn push_into_stack_multi(self, _lua: &'lua Lua) -> Result<c_int> {
unsafe fn push_into_stack_multi(self, _lua: &RawLua) -> Result<c_int> {
Ok(0)
}
}
impl<'lua> FromLuaMulti<'lua> for () {
impl FromLuaMulti for () {
#[inline]
fn from_lua_multi(_values: MultiValue<'lua>, _lua: &'lua Lua) -> Result<Self> {
fn from_lua_multi(_values: MultiValue, _lua: &Lua) -> Result<Self> {
Ok(())
}
#[inline]
unsafe fn from_stack_multi(nvals: c_int, lua: &'lua Lua) -> Result<Self> {
if nvals > 0 {
ffi::lua_pop(lua.state(), nvals);
}
unsafe fn from_stack_multi(_nvals: c_int, _lua: &RawLua) -> Result<Self> {
Ok(())
}
}
);
($last:ident $($name:ident)*) => (
impl<'lua, $($name,)* $last> IntoLuaMulti<'lua> for ($($name,)* $last,)
where $($name: IntoLua<'lua>,)*
$last: IntoLuaMulti<'lua>
impl<$($name,)* $last> IntoLuaMulti for ($($name,)* $last,)
where $($name: IntoLua,)*
$last: IntoLuaMulti
{
#[allow(unused_mut, non_snake_case)]
#[inline]
fn into_lua_multi(self, lua: &'lua Lua) -> Result<MultiValue<'lua>> {
fn into_lua_multi(self, lua: &Lua) -> Result<MultiValue> {
let ($($name,)* $last,) = self;
let mut results = $last.into_lua_multi(lua)?;
@@ -245,7 +354,7 @@ macro_rules! impl_tuple {
#[allow(non_snake_case)]
#[inline]
unsafe fn push_into_stack_multi(self, lua: &'lua Lua) -> Result<c_int> {
unsafe fn push_into_stack_multi(self, lua: &RawLua) -> Result<c_int> {
let ($($name,)* $last,) = self;
let mut nresults = 0;
$(
@@ -261,13 +370,13 @@ macro_rules! impl_tuple {
}
}
impl<'lua, $($name,)* $last> FromLuaMulti<'lua> for ($($name,)* $last,)
where $($name: FromLua<'lua>,)*
$last: FromLuaMulti<'lua>
impl<$($name,)* $last> FromLuaMulti for ($($name,)* $last,)
where $($name: FromLua,)*
$last: FromLuaMulti
{
#[allow(unused_mut, non_snake_case)]
#[inline]
fn from_lua_multi(mut values: MultiValue<'lua>, lua: &'lua Lua) -> Result<Self> {
fn from_lua_multi(mut values: MultiValue, lua: &Lua) -> Result<Self> {
$(let $name = FromLua::from_lua(values.pop_front().unwrap_or(Nil), lua)?;)*
let $last = FromLuaMulti::from_lua_multi(values, lua)?;
Ok(($($name,)* $last,))
@@ -275,7 +384,7 @@ macro_rules! impl_tuple {
#[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> {
fn from_lua_args(mut args: MultiValue, mut i: usize, to: Option<&str>, lua: &Lua) -> Result<Self> {
$(
let $name = FromLua::from_lua_arg(args.pop_front().unwrap_or(Nil), i, to, lua)?;
i += 1;
@@ -286,13 +395,13 @@ macro_rules! impl_tuple {
#[allow(unused_mut, non_snake_case)]
#[inline]
unsafe fn from_stack_multi(mut nvals: c_int, lua: &'lua Lua) -> Result<Self> {
unsafe fn from_stack_multi(mut nvals: c_int, lua: &RawLua) -> Result<Self> {
$(
let $name = if nvals > 0 {
nvals -= 1;
FromLua::from_stack(-(nvals + 1), lua)
} else {
FromLua::from_lua(Nil, lua)
FromLua::from_lua(Nil, lua.lua())
}?;
)*
let $last = FromLuaMulti::from_stack_multi(nvals, lua)?;
@@ -301,13 +410,13 @@ macro_rules! impl_tuple {
#[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> {
unsafe fn from_stack_args(mut nargs: c_int, mut i: usize, to: Option<&str>, lua: &RawLua) -> 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)
FromLua::from_lua_arg(Nil, i, to, lua.lua())
}?;
i += 1;
)*
@@ -348,3 +457,13 @@ impl_tuple!(A B C D E F G H I J K L M);
impl_tuple!(A B C D E F G H I J K L M N);
impl_tuple!(A B C D E F G H I J K L M N O);
impl_tuple!(A B C D E F G H I J K L M N O P);
#[cfg(test)]
mod assertions {
use super::*;
#[cfg(not(feature = "send"))]
static_assertions::assert_not_impl_any!(MultiValue: Send);
#[cfg(feature = "send")]
static_assertions::assert_impl_all!(MultiValue: Send, Sync);
}
+18 -22
View File
@@ -2,19 +2,19 @@
#[doc(no_inline)]
pub use crate::{
AnyUserData as LuaAnyUserData, AnyUserDataExt as LuaAnyUserDataExt, Chunk as LuaChunk,
Error as LuaError, ErrorContext as LuaErrorContext, ExternalError as LuaExternalError,
ExternalResult as LuaExternalResult, FromLua, FromLuaMulti, Function as LuaFunction,
FunctionInfo as LuaFunctionInfo, GCMode as LuaGCMode, Integer as LuaInteger, IntoLua,
IntoLuaMulti, LightUserData as LuaLightUserData, Lua, LuaOptions, MetaMethod as LuaMetaMethod,
MultiValue as LuaMultiValue, Nil as LuaNil, Number as LuaNumber, RegistryKey as LuaRegistryKey,
Result as LuaResult, StdLib as LuaStdLib, String as LuaString, Table as LuaTable,
TableExt as LuaTableExt, TablePairs as LuaTablePairs, TableSequence as LuaTableSequence,
AnyUserData as LuaAnyUserData, BorrowedBytes as LuaBorrowedBytes, BorrowedStr as LuaBorrowedStr,
Chunk as LuaChunk, Either as LuaEither, Error as LuaError, ErrorContext as LuaErrorContext,
ExternalError as LuaExternalError, ExternalResult as LuaExternalResult, FromLua, FromLuaMulti,
Function as LuaFunction, FunctionInfo as LuaFunctionInfo, GCMode as LuaGCMode, Integer as LuaInteger,
IntoLua, IntoLuaMulti, LightUserData as LuaLightUserData, Lua, LuaNativeFn, LuaNativeFnMut, LuaOptions,
MetaMethod as LuaMetaMethod, MultiValue as LuaMultiValue, Nil as LuaNil, Number as LuaNumber,
ObjectLike as LuaObjectLike, RegistryKey as LuaRegistryKey, Result as LuaResult, StdLib as LuaStdLib,
String as LuaString, Table as LuaTable, TablePairs as LuaTablePairs, TableSequence as LuaTableSequence,
Thread as LuaThread, ThreadStatus as LuaThreadStatus, UserData as LuaUserData,
UserDataFields as LuaUserDataFields, UserDataMetatable as LuaUserDataMetatable,
UserDataMethods as LuaUserDataMethods, UserDataRef as LuaUserDataRef,
UserDataRefMut as LuaUserDataRefMut, UserDataRegistry as LuaUserDataRegistry,
Value as LuaValue,
UserDataRefMut as LuaUserDataRefMut, UserDataRegistry as LuaUserDataRegistry, Value as LuaValue,
Variadic as LuaVariadic, VmState as LuaVmState, WeakLua,
};
#[cfg(not(feature = "luau"))]
@@ -23,22 +23,18 @@ pub use crate::HookTriggers as LuaHookTriggers;
#[cfg(feature = "luau")]
#[doc(no_inline)]
pub use crate::{CoverageInfo as LuaCoverageInfo, Vector as LuaVector, VmState as LuaVmState};
pub use crate::{
CompileConstant as LuaCompileConstant, CoverageInfo as LuaCoverageInfo,
NavigateError as LuaNavigateError, Require as LuaRequire, TextRequirer as LuaTextRequirer,
Vector as LuaVector,
};
#[cfg(feature = "async")]
#[doc(no_inline)]
pub use crate::AsyncThread as LuaAsyncThread;
pub use crate::{AsyncThread as LuaAsyncThread, LuaNativeAsyncFn};
#[cfg(feature = "serialize")]
#[cfg(feature = "serde")]
#[doc(no_inline)]
pub use crate::{
DeserializeOptions as LuaDeserializeOptions, LuaSerdeExt,
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,
DeserializeOptions as LuaDeserializeOptions, LuaSerdeExt, SerializeOptions as LuaSerializeOptions,
};
+202 -823
View File
File diff suppressed because it is too large Load Diff
+108 -78
View File
@@ -1,5 +1,6 @@
//! Deserialize Lua values to a Rust data structure.
use std::cell::RefCell;
use std::convert::TryInto;
use std::os::raw::c_void;
use std::rc::Rc;
use std::result::Result as StdResult;
@@ -15,8 +16,8 @@ use crate::value::Value;
/// A struct for deserializing Lua values into Rust values.
#[derive(Debug)]
pub struct Deserializer<'lua> {
value: Value<'lua>,
pub struct Deserializer {
value: Value,
options: Options,
visited: Rc<RefCell<FxHashSet<*const c_void>>>,
}
@@ -48,11 +49,16 @@ pub struct Options {
///
/// Default: **false**
pub sort_keys: bool,
/// If true, empty Lua tables will be encoded as array, instead of map.
///
/// Default: **false**
pub encode_empty_tables_as_array: bool,
}
impl Default for Options {
fn default() -> Self {
Self::new()
const { Self::new() }
}
}
@@ -63,6 +69,7 @@ impl Options {
deny_unsupported_types: true,
deny_recursive_tables: true,
sort_keys: false,
encode_empty_tables_as_array: false,
}
}
@@ -92,16 +99,25 @@ impl Options {
self.sort_keys = enabled;
self
}
/// Sets [`encode_empty_tables_as_array`] option.
///
/// [`encode_empty_tables_as_array`]: #structfield.encode_empty_tables_as_array
#[must_use]
pub const fn encode_empty_tables_as_array(mut self, enabled: bool) -> Self {
self.encode_empty_tables_as_array = enabled;
self
}
}
impl<'lua> Deserializer<'lua> {
/// Creates a new Lua Deserializer for the `Value`.
pub fn new(value: Value<'lua>) -> Self {
impl Deserializer {
/// Creates a new Lua Deserializer for the [`Value`].
pub fn new(value: Value) -> Self {
Self::new_with_options(value, Options::default())
}
/// Creates a new Lua Deserializer for the `Value` with custom options.
pub fn new_with_options(value: Value<'lua>, options: Options) -> Self {
/// Creates a new Lua Deserializer for the [`Value`] with custom options.
pub fn new_with_options(value: Value, options: Options) -> Self {
Deserializer {
value,
options,
@@ -109,11 +125,7 @@ impl<'lua> Deserializer<'lua> {
}
}
fn from_parts(
value: Value<'lua>,
options: Options,
visited: Rc<RefCell<FxHashSet<*const c_void>>>,
) -> Self {
fn from_parts(value: Value, options: Options, visited: Rc<RefCell<FxHashSet<*const c_void>>>) -> Self {
Deserializer {
value,
options,
@@ -122,7 +134,7 @@ impl<'lua> Deserializer<'lua> {
}
}
impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
impl<'de> serde::Deserializer<'de> for Deserializer {
type Error = Error;
#[inline]
@@ -134,28 +146,35 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
Value::Nil => visitor.visit_unit(),
Value::Boolean(b) => visitor.visit_bool(b),
#[allow(clippy::useless_conversion)]
Value::Integer(i) => {
visitor.visit_i64(i.try_into().expect("cannot convert lua_Integer to i64"))
}
Value::Integer(i) => visitor.visit_i64(i.into()),
#[allow(clippy::useless_conversion)]
Value::Number(n) => visitor.visit_f64(n.into()),
#[cfg(feature = "luau")]
Value::Vector(_) => self.deserialize_seq(visitor),
Value::String(s) => match s.to_str() {
Ok(s) => visitor.visit_str(s),
Err(_) => visitor.visit_bytes(s.as_bytes()),
Ok(s) => visitor.visit_str(&s),
Err(_) => visitor.visit_bytes(&s.as_bytes()),
},
Value::Table(ref t) if t.raw_len() > 0 || t.is_array() => self.deserialize_seq(visitor),
Value::Table(ref t) if self.options.encode_empty_tables_as_array && t.is_empty() => {
self.deserialize_seq(visitor)
}
Value::Table(_) => self.deserialize_map(visitor),
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::Buffer(buf) => {
let lua = buf.0.lua.lock();
visitor.visit_bytes(buf.as_slice(&lua))
}
Value::Function(_)
| Value::Thread(_)
| Value::UserData(_)
| Value::LightUserData(_)
| Value::Error(_) => {
| Value::Error(_)
| Value::Other(_) => {
if self.options.deny_unsupported_types {
let msg = format!("unsupported value type `{}`", self.value.type_name());
Err(de::Error::custom(msg))
@@ -261,10 +280,7 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
if deserializer.seq.count() == 0 {
Ok(seq)
} else {
Err(de::Error::invalid_length(
len,
&"fewer elements in the table",
))
Err(de::Error::invalid_length(len, &"fewer elements in the table"))
}
}
Value::UserData(ud) if ud.is_serializable() => {
@@ -286,12 +302,7 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
}
#[inline]
fn deserialize_tuple_struct<V>(
self,
_name: &'static str,
_len: usize,
visitor: V,
) -> Result<V::Value>
fn deserialize_tuple_struct<V>(self, _name: &'static str, _len: usize, visitor: V) -> Result<V::Value>
where
V: de::Visitor<'de>,
{
@@ -308,7 +319,7 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
let _guard = RecursionGuard::new(&t, &self.visited);
let mut deserializer = MapDeserializer {
pairs: MapPairs::new(t, self.options.sort_keys)?,
pairs: MapPairs::new(&t, self.options.sort_keys)?,
value: None,
options: self.options,
visited: self.visited,
@@ -389,13 +400,13 @@ impl<'lua, 'de> serde::Deserializer<'de> for Deserializer<'lua> {
}
}
struct SeqDeserializer<'lua> {
seq: TableSequence<'lua, Value<'lua>>,
struct SeqDeserializer<'a> {
seq: TableSequence<'a, Value>,
options: Options,
visited: Rc<RefCell<FxHashSet<*const c_void>>>,
}
impl<'lua, 'de> de::SeqAccess<'de> for SeqDeserializer<'lua> {
impl<'de> de::SeqAccess<'de> for SeqDeserializer<'_> {
type Error = Error;
fn next_element_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>>
@@ -430,7 +441,7 @@ impl<'lua, 'de> de::SeqAccess<'de> for SeqDeserializer<'lua> {
#[cfg(feature = "luau")]
struct VecDeserializer {
vec: crate::types::Vector,
vec: crate::Vector,
next: usize,
options: Options,
visited: Rc<RefCell<FxHashSet<*const c_void>>>,
@@ -448,8 +459,7 @@ impl<'de> de::SeqAccess<'de> for VecDeserializer {
Some(&n) => {
self.next += 1;
let visited = Rc::clone(&self.visited);
let deserializer =
Deserializer::from_parts(Value::Number(n as _), self.options, visited);
let deserializer = Deserializer::from_parts(Value::Number(n as _), self.options, visited);
seed.deserialize(deserializer).map(Some)
}
None => Ok(None),
@@ -457,20 +467,20 @@ impl<'de> de::SeqAccess<'de> for VecDeserializer {
}
fn size_hint(&self) -> Option<usize> {
Some(crate::types::Vector::SIZE)
Some(crate::Vector::SIZE)
}
}
pub(crate) enum MapPairs<'lua> {
Iter(TablePairs<'lua, Value<'lua>, Value<'lua>>),
Vec(Vec<(Value<'lua>, Value<'lua>)>),
pub(crate) enum MapPairs<'a> {
Iter(TablePairs<'a, Value, Value>),
Vec(Vec<(Value, Value)>),
}
impl<'lua> MapPairs<'lua> {
pub(crate) fn new(t: Table<'lua>, sort_keys: bool) -> Result<Self> {
impl<'a> MapPairs<'a> {
pub(crate) fn new(t: &'a Table, 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
pairs.sort_by(|(a, _), (b, _)| b.sort_cmp(a)); // reverse order as we pop values from the end
Ok(MapPairs::Vec(pairs))
} else {
Ok(MapPairs::Iter(t.pairs::<Value, Value>()))
@@ -492,8 +502,8 @@ impl<'lua> MapPairs<'lua> {
}
}
impl<'lua> Iterator for MapPairs<'lua> {
type Item = Result<(Value<'lua>, Value<'lua>)>;
impl Iterator for MapPairs<'_> {
type Item = Result<(Value, Value)>;
fn next(&mut self) -> Option<Self::Item> {
match self {
@@ -503,21 +513,16 @@ impl<'lua> Iterator for MapPairs<'lua> {
}
}
struct MapDeserializer<'lua> {
pairs: MapPairs<'lua>,
value: Option<Value<'lua>>,
struct MapDeserializer<'a> {
pairs: MapPairs<'a>,
value: Option<Value>,
options: Options,
visited: Rc<RefCell<FxHashSet<*const c_void>>>,
processed: usize,
}
impl<'lua, 'de> de::MapAccess<'de> for MapDeserializer<'lua> {
type Error = Error;
fn next_key_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>>
where
T: de::DeserializeSeed<'de>,
{
impl MapDeserializer<'_> {
fn next_key_deserializer(&mut self) -> Result<Option<Deserializer>> {
loop {
match self.pairs.next() {
Some(item) => {
@@ -533,23 +538,45 @@ impl<'lua, 'de> de::MapAccess<'de> for MapDeserializer<'lua> {
self.value = Some(value);
let visited = Rc::clone(&self.visited);
let key_de = Deserializer::from_parts(key, self.options, visited);
return seed.deserialize(key_de).map(Some);
return Ok(Some(key_de));
}
None => return Ok(None),
}
}
}
fn next_value_deserializer(&mut self) -> Result<Deserializer> {
match self.value.take() {
Some(value) => {
let visited = Rc::clone(&self.visited);
Ok(Deserializer::from_parts(value, self.options, visited))
}
None => Err(de::Error::custom("value is missing")),
}
}
}
impl<'de> de::MapAccess<'de> for MapDeserializer<'_> {
type Error = Error;
fn next_key_seed<T>(&mut self, seed: T) -> Result<Option<T::Value>>
where
T: de::DeserializeSeed<'de>,
{
match self.next_key_deserializer() {
Ok(Some(key_de)) => seed.deserialize(key_de).map(Some),
Ok(None) => Ok(None),
Err(error) => Err(error),
}
}
fn next_value_seed<T>(&mut self, seed: T) -> Result<T::Value>
where
T: de::DeserializeSeed<'de>,
{
match self.value.take() {
Some(value) => {
let visited = Rc::clone(&self.visited);
seed.deserialize(Deserializer::from_parts(value, self.options, visited))
}
None => Err(de::Error::custom("value is missing")),
match self.next_value_deserializer() {
Ok(value_de) => seed.deserialize(value_de),
Err(error) => Err(error),
}
}
@@ -561,16 +588,16 @@ impl<'lua, 'de> de::MapAccess<'de> for MapDeserializer<'lua> {
}
}
struct EnumDeserializer<'lua> {
struct EnumDeserializer {
variant: StdString,
value: Option<Value<'lua>>,
value: Option<Value>,
options: Options,
visited: Rc<RefCell<FxHashSet<*const c_void>>>,
}
impl<'lua, 'de> de::EnumAccess<'de> for EnumDeserializer<'lua> {
impl<'de> de::EnumAccess<'de> for EnumDeserializer {
type Error = Error;
type Variant = VariantDeserializer<'lua>;
type Variant = VariantDeserializer;
fn variant_seed<T>(self, seed: T) -> Result<(T::Value, Self::Variant)>
where
@@ -586,13 +613,13 @@ impl<'lua, 'de> de::EnumAccess<'de> for EnumDeserializer<'lua> {
}
}
struct VariantDeserializer<'lua> {
value: Option<Value<'lua>>,
struct VariantDeserializer {
value: Option<Value>,
options: Options,
visited: Rc<RefCell<FxHashSet<*const c_void>>>,
}
impl<'lua, 'de> de::VariantAccess<'de> for VariantDeserializer<'lua> {
impl<'de> de::VariantAccess<'de> for VariantDeserializer {
type Error = Error;
fn unit_variant(self) -> Result<()> {
@@ -610,9 +637,7 @@ impl<'lua, 'de> de::VariantAccess<'de> for VariantDeserializer<'lua> {
T: de::DeserializeSeed<'de>,
{
match self.value {
Some(value) => {
seed.deserialize(Deserializer::from_parts(value, self.options, self.visited))
}
Some(value) => seed.deserialize(Deserializer::from_parts(value, self.options, self.visited)),
None => Err(de::Error::invalid_type(
de::Unexpected::UnitVariant,
&"newtype variant",
@@ -655,14 +680,14 @@ impl<'lua, 'de> de::VariantAccess<'de> for VariantDeserializer<'lua> {
// Adds `ptr` to the `visited` map and removes on drop
// Used to track recursive tables but allow to traverse same tables multiple times
struct RecursionGuard {
pub(crate) struct RecursionGuard {
ptr: *const c_void,
visited: Rc<RefCell<FxHashSet<*const c_void>>>,
}
impl RecursionGuard {
#[inline]
fn new(table: &Table, visited: &Rc<RefCell<FxHashSet<*const c_void>>>) -> Self {
pub(crate) fn new(table: &Table, visited: &Rc<RefCell<FxHashSet<*const c_void>>>) -> Self {
let visited = Rc::clone(visited);
let ptr = table.to_pointer();
visited.borrow_mut().insert(ptr);
@@ -711,6 +736,11 @@ 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()))
match serde_value::to_value(ud) {
Ok(value) => match f(value) {
Ok(r) => Ok(r),
Err(error) => Err(Error::DeserializeError(error.to_string())),
},
Err(error) => Err(Error::SerializeError(error.to_string())),
}
}
+12 -29
View File
@@ -2,22 +2,21 @@
use std::os::raw::c_void;
use serde::{de::DeserializeOwned, ser::Serialize};
use serde::de::DeserializeOwned;
use serde::ser::Serialize;
use crate::error::Result;
use crate::lua::Lua;
use crate::private::Sealed;
use crate::state::Lua;
use crate::table::Table;
use crate::util::check_stack;
use crate::value::Value;
/// Trait for serializing/deserializing Lua values using Serde.
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
pub trait LuaSerdeExt: Sealed {
/// A special value (lightuserdata) to encode/decode optional (none) values.
///
/// Requires `feature = "serialize"`
///
/// # Example
///
/// ```
@@ -41,8 +40,6 @@ pub trait LuaSerdeExt: Sealed {
/// As result, encoded Array will contain only sequence part of the table, with the same length
/// as the `#` operator on that table.
///
/// Requires `feature = "serialize"`
///
/// # Example
///
/// ```
@@ -70,8 +67,6 @@ pub trait LuaSerdeExt: Sealed {
/// Converts `T` into a [`Value`] instance.
///
/// Requires `feature = "serialize"`
///
/// [`Value`]: crate::Value
///
/// # Example
@@ -99,14 +94,10 @@ pub trait LuaSerdeExt: Sealed {
/// "#).exec()
/// }
/// ```
fn to_value<'lua, T: Serialize + ?Sized>(&'lua self, t: &T) -> Result<Value<'lua>>;
fn to_value<T: Serialize + ?Sized>(&self, t: &T) -> Result<Value>;
/// Converts `T` into a [`Value`] instance with options.
///
/// Requires `feature = "serialize"`
///
/// [`Value`]: crate::Value
///
/// # Example
///
/// ```
@@ -124,16 +115,12 @@ pub trait LuaSerdeExt: Sealed {
/// "#).exec()
/// }
/// ```
fn to_value_with<'lua, T>(&'lua self, t: &T, options: ser::Options) -> Result<Value<'lua>>
fn to_value_with<T>(&self, t: &T, options: ser::Options) -> Result<Value>
where
T: Serialize + ?Sized;
/// Deserializes a [`Value`] into any serde deserializable object.
///
/// Requires `feature = "serialize"`
///
/// [`Value`]: crate::Value
///
/// # Example
///
/// ```
@@ -161,10 +148,6 @@ pub trait LuaSerdeExt: Sealed {
/// Deserializes a [`Value`] into any serde deserializable object with options.
///
/// Requires `feature = "serialize"`
///
/// [`Value`]: crate::Value
///
/// # Example
///
/// ```
@@ -189,8 +172,7 @@ pub trait LuaSerdeExt: Sealed {
/// }
/// ```
#[allow(clippy::wrong_self_convention)]
fn from_value_with<T: DeserializeOwned>(&self, value: Value, options: de::Options)
-> Result<T>;
fn from_value_with<T: DeserializeOwned>(&self, value: Value, options: de::Options) -> Result<T>;
}
impl LuaSerdeExt for Lua {
@@ -199,20 +181,21 @@ impl LuaSerdeExt for Lua {
}
fn array_metatable(&self) -> Table {
let lua = self.lock();
unsafe {
push_array_metatable(self.ref_thread());
Table(self.pop_ref_thread())
push_array_metatable(lua.ref_thread());
Table(lua.pop_ref_thread())
}
}
fn to_value<'lua, T>(&'lua self, t: &T) -> Result<Value<'lua>>
fn to_value<T>(&self, t: &T) -> Result<Value>
where
T: Serialize + ?Sized,
{
t.serialize(ser::Serializer::new(self))
}
fn to_value_with<'lua, T>(&'lua self, t: &T, options: ser::Options) -> Result<Value<'lua>>
fn to_value_with<T>(&self, t: &T, options: ser::Options) -> Result<Value>
where
T: Serialize + ?Sized,
{
+166 -102
View File
@@ -1,16 +1,18 @@
//! Serialize a Rust data structure into Lua value.
use serde::{ser, Serialize};
use super::LuaSerdeExt;
use crate::error::{Error, Result};
use crate::lua::Lua;
use crate::string::String;
use crate::state::Lua;
use crate::table::Table;
use crate::value::{IntoLua, Value};
use crate::traits::IntoLua;
use crate::value::Value;
/// A struct for serializing Rust values into Lua values.
#[derive(Debug)]
pub struct Serializer<'lua> {
lua: &'lua Lua,
pub struct Serializer<'a> {
lua: &'a Lua,
options: Options,
}
@@ -43,11 +45,17 @@ pub struct Options {
/// [`null`]: crate::LuaSerdeExt::null
/// [`Nil`]: crate::Value::Nil
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 {
fn default() -> Self {
Self::new()
const { Self::new() }
}
}
@@ -58,6 +66,7 @@ impl Options {
set_array_metatable: true,
serialize_none_to_null: true,
serialize_unit_to_null: true,
detect_serde_json_arbitrary_precision: false,
}
}
@@ -87,16 +96,30 @@ impl Options {
self.serialize_unit_to_null = enabled;
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<'a> Serializer<'a> {
/// Creates a new Lua Serializer with default options.
pub fn new(lua: &'lua Lua) -> Self {
pub fn new(lua: &'a Lua) -> Self {
Self::new_with_options(lua, Options::default())
}
/// Creates a new Lua Serializer with custom options.
pub fn new_with_options(lua: &'lua Lua, options: Options) -> Self {
pub fn new_with_options(lua: &'a Lua, options: Options) -> Self {
Serializer { lua, options }
}
}
@@ -104,28 +127,28 @@ impl<'lua> Serializer<'lua> {
macro_rules! lua_serialize_number {
($name:ident, $t:ty) => {
#[inline]
fn $name(self, value: $t) -> Result<Value<'lua>> {
fn $name(self, value: $t) -> Result<Value> {
value.into_lua(self.lua)
}
};
}
impl<'lua> ser::Serializer for Serializer<'lua> {
type Ok = Value<'lua>;
impl<'a> ser::Serializer for Serializer<'a> {
type Ok = Value;
type Error = Error;
// Associated types for keeping track of additional state while serializing
// compound data structures like sequences and maps.
type SerializeSeq = SerializeSeq<'lua>;
type SerializeTuple = SerializeSeq<'lua>;
type SerializeTupleStruct = SerializeSeq<'lua>;
type SerializeTupleVariant = SerializeTupleVariant<'lua>;
type SerializeMap = SerializeMap<'lua>;
type SerializeStruct = SerializeMap<'lua>;
type SerializeStructVariant = SerializeStructVariant<'lua>;
type SerializeSeq = SerializeSeq<'a>;
type SerializeTuple = SerializeSeq<'a>;
type SerializeTupleStruct = SerializeSeq<'a>;
type SerializeTupleVariant = SerializeTupleVariant<'a>;
type SerializeMap = SerializeMap<'a>;
type SerializeStruct = SerializeStruct<'a>;
type SerializeStructVariant = SerializeStructVariant<'a>;
#[inline]
fn serialize_bool(self, value: bool) -> Result<Value<'lua>> {
fn serialize_bool(self, value: bool) -> Result<Value> {
Ok(Value::Boolean(value))
}
@@ -144,22 +167,22 @@ impl<'lua> ser::Serializer for Serializer<'lua> {
lua_serialize_number!(serialize_f64, f64);
#[inline]
fn serialize_char(self, value: char) -> Result<Value<'lua>> {
fn serialize_char(self, value: char) -> Result<Value> {
self.serialize_str(&value.to_string())
}
#[inline]
fn serialize_str(self, value: &str) -> Result<Value<'lua>> {
fn serialize_str(self, value: &str) -> Result<Value> {
self.lua.create_string(value).map(Value::String)
}
#[inline]
fn serialize_bytes(self, value: &[u8]) -> Result<Value<'lua>> {
fn serialize_bytes(self, value: &[u8]) -> Result<Value> {
self.lua.create_string(value).map(Value::String)
}
#[inline]
fn serialize_none(self) -> Result<Value<'lua>> {
fn serialize_none(self) -> Result<Value> {
if self.options.serialize_none_to_null {
Ok(self.lua.null())
} else {
@@ -168,7 +191,7 @@ impl<'lua> ser::Serializer for Serializer<'lua> {
}
#[inline]
fn serialize_some<T>(self, value: &T) -> Result<Value<'lua>>
fn serialize_some<T>(self, value: &T) -> Result<Value>
where
T: Serialize + ?Sized,
{
@@ -176,7 +199,7 @@ impl<'lua> ser::Serializer for Serializer<'lua> {
}
#[inline]
fn serialize_unit(self) -> Result<Value<'lua>> {
fn serialize_unit(self) -> Result<Value> {
if self.options.serialize_unit_to_null {
Ok(self.lua.null())
} else {
@@ -185,7 +208,7 @@ impl<'lua> ser::Serializer for Serializer<'lua> {
}
#[inline]
fn serialize_unit_struct(self, _name: &'static str) -> Result<Value<'lua>> {
fn serialize_unit_struct(self, _name: &'static str) -> Result<Value> {
if self.options.serialize_unit_to_null {
Ok(self.lua.null())
} else {
@@ -199,12 +222,12 @@ impl<'lua> ser::Serializer for Serializer<'lua> {
_name: &'static str,
_variant_index: u32,
variant: &'static str,
) -> Result<Value<'lua>> {
) -> Result<Value> {
self.serialize_str(variant)
}
#[inline]
fn serialize_newtype_struct<T>(self, _name: &'static str, value: &T) -> Result<Value<'lua>>
fn serialize_newtype_struct<T>(self, _name: &'static str, value: &T) -> Result<Value>
where
T: Serialize + ?Sized,
{
@@ -218,7 +241,7 @@ impl<'lua> ser::Serializer for Serializer<'lua> {
_variant_index: u32,
variant: &'static str,
value: &T,
) -> Result<Value<'lua>>
) -> Result<Value>
where
T: Serialize + ?Sized,
{
@@ -233,9 +256,9 @@ impl<'lua> ser::Serializer for Serializer<'lua> {
fn serialize_seq(self, len: Option<usize>) -> Result<Self::SerializeSeq> {
let table = self.lua.create_table_with_capacity(len.unwrap_or(0), 0)?;
if self.options.set_array_metatable {
table.set_metatable(Some(self.lua.array_metatable()));
table.set_metatable(Some(self.lua.array_metatable()))?;
}
Ok(SerializeSeq::new(table, self.options))
Ok(SerializeSeq::new(self.lua, table, self.options))
}
#[inline]
@@ -244,13 +267,9 @@ impl<'lua> ser::Serializer for Serializer<'lua> {
}
#[inline]
fn serialize_tuple_struct(
self,
name: &'static str,
len: usize,
) -> Result<Self::SerializeTupleStruct> {
fn serialize_tuple_struct(self, name: &'static str, len: usize) -> Result<Self::SerializeTupleStruct> {
#[cfg(feature = "luau")]
if name == "Vector" && len == crate::types::Vector::SIZE {
if name == "Vector" && len == crate::Vector::SIZE {
return Ok(SerializeSeq::new_vector(self.lua, self.options));
}
_ = name;
@@ -266,7 +285,8 @@ impl<'lua> ser::Serializer for Serializer<'lua> {
_len: usize,
) -> Result<Self::SerializeTupleVariant> {
Ok(SerializeTupleVariant {
name: self.lua.create_string(variant)?,
lua: self.lua,
variant,
table: self.lua.create_table()?,
options: self.options,
})
@@ -275,6 +295,7 @@ impl<'lua> ser::Serializer for Serializer<'lua> {
#[inline]
fn serialize_map(self, len: Option<usize>) -> Result<Self::SerializeMap> {
Ok(SerializeMap {
lua: self.lua,
key: None,
table: self.lua.create_table_with_capacity(0, len.unwrap_or(0))?,
options: self.options,
@@ -282,8 +303,23 @@ impl<'lua> ser::Serializer for Serializer<'lua> {
}
#[inline]
fn serialize_struct(self, _name: &'static str, len: usize) -> Result<Self::SerializeStruct> {
self.serialize_map(Some(len))
fn serialize_struct(self, name: &'static str, len: usize) -> Result<Self::SerializeStruct> {
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]
@@ -295,7 +331,8 @@ impl<'lua> ser::Serializer for Serializer<'lua> {
len: usize,
) -> Result<Self::SerializeStructVariant> {
Ok(SerializeStructVariant {
name: self.lua.create_string(variant)?,
lua: self.lua,
variant,
table: self.lua.create_table_with_capacity(0, len)?,
options: self.options,
})
@@ -303,19 +340,19 @@ impl<'lua> ser::Serializer for Serializer<'lua> {
}
#[doc(hidden)]
pub struct SerializeSeq<'lua> {
lua: &'lua Lua,
pub struct SerializeSeq<'a> {
lua: &'a Lua,
#[cfg(feature = "luau")]
vector: Option<crate::types::Vector>,
table: Option<Table<'lua>>,
vector: Option<crate::Vector>,
table: Option<Table>,
next: usize,
options: Options,
}
impl<'lua> SerializeSeq<'lua> {
const fn new(table: Table<'lua>, options: Options) -> Self {
impl<'a> SerializeSeq<'a> {
fn new(lua: &'a Lua, table: Table, options: Options) -> Self {
Self {
lua: table.0.lua,
lua,
#[cfg(feature = "luau")]
vector: None,
table: Some(table),
@@ -325,10 +362,10 @@ impl<'lua> SerializeSeq<'lua> {
}
#[cfg(feature = "luau")]
const fn new_vector(lua: &'lua Lua, options: Options) -> Self {
const fn new_vector(lua: &'a Lua, options: Options) -> Self {
Self {
lua,
vector: Some(crate::types::Vector::zero()),
vector: Some(crate::Vector::zero()),
table: None,
next: 0,
options,
@@ -336,8 +373,8 @@ impl<'lua> SerializeSeq<'lua> {
}
}
impl<'lua> ser::SerializeSeq for SerializeSeq<'lua> {
type Ok = Value<'lua>;
impl ser::SerializeSeq for SerializeSeq<'_> {
type Ok = Value;
type Error = Error;
fn serialize_element<T>(&mut self, value: &T) -> Result<()>
@@ -351,13 +388,13 @@ impl<'lua> ser::SerializeSeq for SerializeSeq<'lua> {
Ok(())
}
fn end(self) -> Result<Value<'lua>> {
fn end(self) -> Result<Value> {
Ok(Value::Table(self.table.unwrap()))
}
}
impl<'lua> ser::SerializeTuple for SerializeSeq<'lua> {
type Ok = Value<'lua>;
impl ser::SerializeTuple for SerializeSeq<'_> {
type Ok = Value;
type Error = Error;
fn serialize_element<T>(&mut self, value: &T) -> Result<()>
@@ -367,13 +404,13 @@ impl<'lua> ser::SerializeTuple for SerializeSeq<'lua> {
ser::SerializeSeq::serialize_element(self, value)
}
fn end(self) -> Result<Value<'lua>> {
fn end(self) -> Result<Value> {
ser::SerializeSeq::end(self)
}
}
impl<'lua> ser::SerializeTupleStruct for SerializeSeq<'lua> {
type Ok = Value<'lua>;
impl ser::SerializeTupleStruct for SerializeSeq<'_> {
type Ok = Value;
type Error = Error;
fn serialize_field<T>(&mut self, value: &T) -> Result<()>
@@ -391,7 +428,7 @@ impl<'lua> ser::SerializeTupleStruct for SerializeSeq<'lua> {
ser::SerializeSeq::serialize_element(self, value)
}
fn end(self) -> Result<Value<'lua>> {
fn end(self) -> Result<Value> {
#[cfg(feature = "luau")]
if let Some(vector) = self.vector {
return Ok(Value::Vector(vector));
@@ -401,49 +438,48 @@ impl<'lua> ser::SerializeTupleStruct for SerializeSeq<'lua> {
}
#[doc(hidden)]
pub struct SerializeTupleVariant<'lua> {
name: String<'lua>,
table: Table<'lua>,
pub struct SerializeTupleVariant<'a> {
lua: &'a Lua,
variant: &'static str,
table: Table,
options: Options,
}
impl<'lua> ser::SerializeTupleVariant for SerializeTupleVariant<'lua> {
type Ok = Value<'lua>;
impl ser::SerializeTupleVariant for SerializeTupleVariant<'_> {
type Ok = Value;
type Error = Error;
fn serialize_field<T>(&mut self, value: &T) -> Result<()>
where
T: Serialize + ?Sized,
{
let lua = self.table.0.lua;
self.table.raw_push(lua.to_value_with(value, self.options)?)
self.table.raw_push(self.lua.to_value_with(value, self.options)?)
}
fn end(self) -> Result<Value<'lua>> {
let lua = self.table.0.lua;
let table = lua.create_table()?;
table.raw_set(self.name, self.table)?;
fn end(self) -> Result<Value> {
let table = self.lua.create_table()?;
table.raw_set(self.variant, self.table)?;
Ok(Value::Table(table))
}
}
#[doc(hidden)]
pub struct SerializeMap<'lua> {
table: Table<'lua>,
key: Option<Value<'lua>>,
pub struct SerializeMap<'a> {
lua: &'a Lua,
table: Table,
key: Option<Value>,
options: Options,
}
impl<'lua> ser::SerializeMap for SerializeMap<'lua> {
type Ok = Value<'lua>;
impl ser::SerializeMap for SerializeMap<'_> {
type Ok = Value;
type Error = Error;
fn serialize_key<T>(&mut self, key: &T) -> Result<()>
where
T: Serialize + ?Sized,
{
let lua = self.table.0.lua;
self.key = Some(lua.to_value_with(key, self.options)?);
self.key = Some(self.lua.to_value_with(key, self.options)?);
Ok(())
}
@@ -451,62 +487,90 @@ impl<'lua> ser::SerializeMap for SerializeMap<'lua> {
where
T: Serialize + ?Sized,
{
let lua = self.table.0.lua;
let key = mlua_expect!(
self.key.take(),
"serialize_value called before serialize_key"
);
let value = lua.to_value_with(value, self.options)?;
let key = mlua_expect!(self.key.take(), "serialize_value called before serialize_key");
let value = self.lua.to_value_with(value, self.options)?;
self.table.raw_set(key, value)
}
fn end(self) -> Result<Value<'lua>> {
fn end(self) -> Result<Value> {
Ok(Value::Table(self.table))
}
}
impl<'lua> ser::SerializeStruct for SerializeMap<'lua> {
type Ok = Value<'lua>;
#[doc(hidden)]
pub struct SerializeStruct<'a> {
lua: &'a Lua,
inner: Option<Value>,
options: Options,
}
impl ser::SerializeStruct for SerializeStruct<'_> {
type Ok = Value;
type Error = Error;
fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<()>
where
T: Serialize + ?Sized,
{
ser::SerializeMap::serialize_key(self, key)?;
ser::SerializeMap::serialize_value(self, value)
match self.inner {
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>> {
ser::SerializeMap::end(self)
fn end(self) -> Result<Value> {
match self.inner {
Some(table @ Value::Table(_)) => Ok(table),
Some(value @ Value::String(_)) if self.options.detect_serde_json_arbitrary_precision => {
let number_s = value.to_string()?;
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)]
pub struct SerializeStructVariant<'lua> {
name: String<'lua>,
table: Table<'lua>,
pub struct SerializeStructVariant<'a> {
lua: &'a Lua,
variant: &'static str,
table: Table,
options: Options,
}
impl<'lua> ser::SerializeStructVariant for SerializeStructVariant<'lua> {
type Ok = Value<'lua>;
impl ser::SerializeStructVariant for SerializeStructVariant<'_> {
type Ok = Value;
type Error = Error;
fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<()>
where
T: Serialize + ?Sized,
{
let lua = self.table.0.lua;
self.table
.raw_set(key, lua.to_value_with(value, self.options)?)?;
.raw_set(key, self.lua.to_value_with(value, self.options)?)?;
Ok(())
}
fn end(self) -> Result<Value<'lua>> {
let lua = self.table.0.lua;
let table = lua.create_table()?;
table.raw_set(self.name, self.table)?;
fn end(self) -> Result<Value> {
let table = self.lua.create_table_with_capacity(0, 1)?;
table.raw_set(self.variant, self.table)?;
Ok(Value::Table(table))
}
}
+2205
View File
File diff suppressed because it is too large Load Diff
+290
View File
@@ -0,0 +1,290 @@
use std::any::TypeId;
use std::cell::UnsafeCell;
use std::mem::MaybeUninit;
use std::os::raw::{c_int, c_void};
use std::ptr;
use std::rc::Rc;
use std::sync::Arc;
use parking_lot::Mutex;
use rustc_hash::FxHashMap;
use crate::error::Result;
use crate::state::RawLua;
use crate::stdlib::StdLib;
use crate::types::{AppData, ReentrantMutex, XRc};
use crate::userdata::RawUserDataRegistry;
use crate::util::{get_internal_metatable, push_internal_userdata, TypeKey, WrappedFailure};
#[cfg(any(feature = "luau", doc))]
use crate::chunk::Compiler;
#[cfg(feature = "async")]
use {futures_util::task::noop_waker_ref, std::ptr::NonNull, std::task::Waker};
use super::{Lua, WeakLua};
// Unique key to store `ExtraData` in the registry
static EXTRA_REGISTRY_KEY: u8 = 0;
const WRAPPED_FAILURE_POOL_DEFAULT_CAPACITY: usize = 64;
const REF_STACK_RESERVE: c_int = 3;
/// Data associated with the Lua state.
pub(crate) struct ExtraData {
pub(super) lua: MaybeUninit<Lua>,
pub(super) weak: MaybeUninit<WeakLua>,
pub(super) owned: bool,
pub(super) pending_userdata_reg: FxHashMap<TypeId, RawUserDataRegistry>,
pub(super) registered_userdata_t: FxHashMap<TypeId, c_int>,
pub(super) registered_userdata_mt: FxHashMap<*const c_void, Option<TypeId>>,
pub(super) last_checked_userdata_mt: (*const c_void, Option<TypeId>),
// When Lua instance dropped, setting `None` would prevent collecting `RegistryKey`s
pub(super) registry_unref_list: Arc<Mutex<Option<Vec<c_int>>>>,
// Containers to store arbitrary data (extensions)
pub(super) app_data: AppData,
pub(super) app_data_priv: AppData,
pub(super) safe: bool,
pub(super) libs: StdLib,
// Used in module mode
pub(super) skip_memory_check: bool,
// Auxiliary thread to store references
pub(super) ref_thread: *mut ffi::lua_State,
pub(super) ref_stack_size: c_int,
pub(super) ref_stack_top: c_int,
pub(super) ref_free: Vec<c_int>,
// Pool of `WrappedFailure` enums in the ref thread (as userdata)
pub(super) wrapped_failure_pool: Vec<c_int>,
pub(super) wrapped_failure_top: usize,
// Pool of `Thread`s (coroutines) for async execution
#[cfg(feature = "async")]
pub(super) thread_pool: Vec<c_int>,
// Address of `WrappedFailure` metatable
pub(super) wrapped_failure_mt_ptr: *const c_void,
// Waker for polling futures
#[cfg(feature = "async")]
pub(super) waker: NonNull<Waker>,
#[cfg(not(feature = "luau"))]
pub(super) hook_callback: Option<crate::types::HookCallback>,
#[cfg(not(feature = "luau"))]
pub(super) hook_triggers: crate::debug::HookTriggers,
#[cfg(feature = "lua54")]
pub(super) warn_callback: Option<crate::types::WarnCallback>,
#[cfg(feature = "luau")]
pub(super) interrupt_callback: Option<crate::types::InterruptCallback>,
#[cfg(feature = "luau")]
pub(super) thread_creation_callback: Option<crate::types::ThreadCreationCallback>,
#[cfg(feature = "luau")]
pub(super) thread_collection_callback: Option<crate::types::ThreadCollectionCallback>,
#[cfg(feature = "luau")]
pub(crate) running_gc: bool,
#[cfg(feature = "luau")]
pub(crate) sandboxed: bool,
#[cfg(feature = "luau")]
pub(super) compiler: Option<Compiler>,
#[cfg(feature = "luau-jit")]
pub(super) enable_jit: bool,
}
impl Drop for ExtraData {
fn drop(&mut self) {
unsafe {
if !self.owned {
self.lua.assume_init_drop();
}
self.weak.assume_init_drop();
}
*self.registry_unref_list.lock() = None;
}
}
static EXTRA_TYPE_KEY: u8 = 0;
impl TypeKey for XRc<UnsafeCell<ExtraData>> {
#[inline(always)]
fn type_key() -> *const c_void {
&EXTRA_TYPE_KEY as *const u8 as *const c_void
}
}
impl ExtraData {
// Index of `error_traceback` function in auxiliary thread stack
#[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))]
pub(super) const ERROR_TRACEBACK_IDX: c_int = 1;
pub(super) unsafe fn init(state: *mut ffi::lua_State, owned: bool) -> XRc<UnsafeCell<Self>> {
// Create ref stack thread and place it in the registry to prevent it
// from being garbage collected.
let ref_thread = mlua_expect!(
protect_lua!(state, 0, 0, |state| {
let thread = ffi::lua_newthread(state);
ffi::luaL_ref(state, ffi::LUA_REGISTRYINDEX);
thread
}),
"Error while creating ref thread",
);
let wrapped_failure_mt_ptr = {
get_internal_metatable::<WrappedFailure>(state);
let ptr = ffi::lua_topointer(state, -1);
ffi::lua_pop(state, 1);
ptr
};
// Store `error_traceback` function on the ref stack
#[cfg(any(feature = "lua51", feature = "luajit", feature = "luau"))]
{
ffi::lua_pushcfunction(ref_thread, crate::util::error_traceback);
assert_eq!(ffi::lua_gettop(ref_thread), Self::ERROR_TRACEBACK_IDX);
}
#[allow(clippy::arc_with_non_send_sync)]
let extra = XRc::new(UnsafeCell::new(ExtraData {
lua: MaybeUninit::uninit(),
weak: MaybeUninit::uninit(),
owned,
pending_userdata_reg: FxHashMap::default(),
registered_userdata_t: FxHashMap::default(),
registered_userdata_mt: FxHashMap::default(),
last_checked_userdata_mt: (ptr::null(), None),
registry_unref_list: Arc::new(Mutex::new(Some(Vec::new()))),
app_data: AppData::default(),
app_data_priv: AppData::default(),
safe: false,
libs: StdLib::NONE,
skip_memory_check: false,
ref_thread,
// We need some reserved stack space to move values in and out of the ref stack.
ref_stack_size: ffi::LUA_MINSTACK - REF_STACK_RESERVE,
ref_stack_top: ffi::lua_gettop(ref_thread),
ref_free: Vec::new(),
wrapped_failure_pool: Vec::with_capacity(WRAPPED_FAILURE_POOL_DEFAULT_CAPACITY),
wrapped_failure_top: 0,
#[cfg(feature = "async")]
thread_pool: Vec::new(),
wrapped_failure_mt_ptr,
#[cfg(feature = "async")]
waker: NonNull::from(noop_waker_ref()),
#[cfg(not(feature = "luau"))]
hook_callback: None,
#[cfg(not(feature = "luau"))]
hook_triggers: Default::default(),
#[cfg(feature = "lua54")]
warn_callback: None,
#[cfg(feature = "luau")]
interrupt_callback: None,
#[cfg(feature = "luau")]
thread_creation_callback: None,
#[cfg(feature = "luau")]
thread_collection_callback: None,
#[cfg(feature = "luau")]
sandboxed: false,
#[cfg(feature = "luau")]
compiler: None,
#[cfg(feature = "luau-jit")]
enable_jit: true,
#[cfg(feature = "luau")]
running_gc: false,
}));
// Store it in the registry
mlua_expect!(Self::store(&extra, state), "Error while storing extra data");
extra
}
pub(super) unsafe fn set_lua(&mut self, raw: &XRc<ReentrantMutex<RawLua>>) {
self.lua.write(Lua {
raw: XRc::clone(raw),
collect_garbage: false,
});
self.weak.write(WeakLua(XRc::downgrade(raw)));
}
pub(crate) unsafe fn get(state: *mut ffi::lua_State) -> *mut Self {
#[cfg(feature = "luau")]
if cfg!(not(feature = "module")) {
// In the main app we can use `lua_callbacks` to access ExtraData
return (*ffi::lua_callbacks(state)).userdata as *mut _;
}
let extra_key = &EXTRA_REGISTRY_KEY as *const u8 as *const c_void;
if ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, extra_key) != ffi::LUA_TUSERDATA {
// `ExtraData` can be null only when Lua state is foreign.
// This case in used in `Lua::try_from_ptr()`.
ffi::lua_pop(state, 1);
return ptr::null_mut();
}
let extra_ptr = ffi::lua_touserdata(state, -1) as *mut Rc<UnsafeCell<ExtraData>>;
ffi::lua_pop(state, 1);
(*extra_ptr).get()
}
unsafe fn store(extra: &XRc<UnsafeCell<Self>>, state: *mut ffi::lua_State) -> Result<()> {
#[cfg(feature = "luau")]
if cfg!(not(feature = "module")) {
(*ffi::lua_callbacks(state)).userdata = extra.get() as *mut _;
return Ok(());
}
push_internal_userdata(state, XRc::clone(extra), true)?;
protect_lua!(state, 1, 0, fn(state) {
let extra_key = &EXTRA_REGISTRY_KEY as *const u8 as *const c_void;
ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, extra_key);
})
}
#[inline(always)]
pub(super) unsafe fn lua(&self) -> &Lua {
self.lua.assume_init_ref()
}
#[inline(always)]
pub(crate) unsafe fn raw_lua(&self) -> &RawLua {
&*self.lua.assume_init_ref().raw.data_ptr()
}
#[inline(always)]
pub(super) unsafe fn weak(&self) -> &WeakLua {
self.weak.assume_init_ref()
}
/// Pops a reference from top of the auxiliary stack and move it to a first free slot.
pub(super) unsafe fn ref_stack_pop(&mut self) -> c_int {
if let Some(free) = self.ref_free.pop() {
ffi::lua_replace(self.ref_thread, free);
return free;
}
// Try to grow max stack size
if self.ref_stack_top >= self.ref_stack_size {
let mut inc = self.ref_stack_size; // Try to double stack size
while inc > 0 && ffi::lua_checkstack(self.ref_thread, inc) == 0 {
inc /= 2;
}
if inc == 0 {
// Pop item on top of the stack to avoid stack leaking and successfully run destructors
// during unwinding.
ffi::lua_pop(self.ref_thread, 1);
let top = self.ref_stack_top;
// It is a user error to create too many references to exhaust the Lua max stack size
// for the ref thread.
panic!("cannot create a Lua reference, out of auxiliary stack space (used {top} slots)");
}
self.ref_stack_size += inc;
}
self.ref_stack_top += 1;
self.ref_stack_top
}
}
+1499
View File
File diff suppressed because it is too large Load Diff
+152
View File
@@ -0,0 +1,152 @@
use std::os::raw::c_int;
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::ptr;
use std::sync::Arc;
use crate::error::{Error, Result};
use crate::state::{ExtraData, RawLua};
use crate::util::{self, get_internal_metatable, WrappedFailure};
struct StateGuard<'a>(&'a RawLua, *mut ffi::lua_State);
impl<'a> StateGuard<'a> {
fn new(inner: &'a RawLua, mut state: *mut ffi::lua_State) -> Self {
state = inner.state.replace(state);
Self(inner, state)
}
}
impl Drop for StateGuard<'_> {
fn drop(&mut self) {
self.0.state.set(self.1);
}
}
// An optimized version of `callback_error` that does not allocate `WrappedFailure` userdata
// and instead reuses unused values from previous calls (or allocates new).
pub(crate) unsafe fn callback_error_ext<F, R>(
state: *mut ffi::lua_State,
mut extra: *mut ExtraData,
wrap_error: bool,
f: F,
) -> R
where
F: FnOnce(*mut ExtraData, c_int) -> Result<R>,
{
if extra.is_null() {
extra = ExtraData::get(state);
}
let nargs = ffi::lua_gettop(state);
enum PreallocatedFailure {
New(*mut WrappedFailure),
Reserved,
}
impl PreallocatedFailure {
unsafe fn reserve(state: *mut ffi::lua_State, extra: *mut ExtraData) -> Self {
if (*extra).wrapped_failure_top > 0 {
(*extra).wrapped_failure_top -= 1;
return PreallocatedFailure::Reserved;
}
// We need to check stack for Luau in case when callback is called from interrupt
// See https://github.com/luau-lang/luau/issues/446 and mlua #142 and #153
#[cfg(feature = "luau")]
ffi::lua_rawcheckstack(state, 2);
// Place it to the beginning of the stack
let ud = WrappedFailure::new_userdata(state);
ffi::lua_insert(state, 1);
PreallocatedFailure::New(ud)
}
#[cold]
unsafe fn r#use(&self, state: *mut ffi::lua_State, extra: *mut ExtraData) -> *mut WrappedFailure {
let ref_thread = (*extra).ref_thread;
match *self {
PreallocatedFailure::New(ud) => {
ffi::lua_settop(state, 1);
ud
}
PreallocatedFailure::Reserved => {
let index = (*extra).wrapped_failure_pool.pop().unwrap();
ffi::lua_settop(state, 0);
#[cfg(feature = "luau")]
ffi::lua_rawcheckstack(state, 2);
ffi::lua_xpush(ref_thread, state, index);
ffi::lua_pushnil(ref_thread);
ffi::lua_replace(ref_thread, index);
(*extra).ref_free.push(index);
ffi::lua_touserdata(state, -1) as *mut WrappedFailure
}
}
}
unsafe fn release(self, state: *mut ffi::lua_State, extra: *mut ExtraData) {
let ref_thread = (*extra).ref_thread;
match self {
PreallocatedFailure::New(_) => {
ffi::lua_rotate(state, 1, -1);
ffi::lua_xmove(state, ref_thread, 1);
let index = (*extra).ref_stack_pop();
(*extra).wrapped_failure_pool.push(index);
(*extra).wrapped_failure_top += 1;
}
PreallocatedFailure::Reserved => (*extra).wrapped_failure_top += 1,
}
}
}
// We cannot shadow Rust errors with Lua ones, so we need to reserve pre-allocated memory
// to store a wrapped failure (error or panic) *before* we proceed.
let prealloc_failure = PreallocatedFailure::reserve(state, extra);
match catch_unwind(AssertUnwindSafe(|| {
let rawlua = (*extra).raw_lua();
let _guard = StateGuard::new(rawlua, state);
f(extra, nargs)
})) {
Ok(Ok(r)) => {
// Return unused `WrappedFailure` to the pool
prealloc_failure.release(state, extra);
r
}
Ok(Err(err)) => {
let wrapped_error = prealloc_failure.r#use(state, extra);
if !wrap_error {
ptr::write(wrapped_error, WrappedFailure::Error(err));
get_internal_metatable::<WrappedFailure>(state);
ffi::lua_setmetatable(state, -2);
ffi::lua_error(state)
}
// Build `CallbackError` with traceback
let traceback = if ffi::lua_checkstack(state, ffi::LUA_TRACEBACK_STACK) != 0 {
ffi::luaL_traceback(state, state, ptr::null(), 0);
let traceback = util::to_string(state, -1);
ffi::lua_pop(state, 1);
traceback
} else {
"<not enough stack space for traceback>".to_string()
};
let cause = Arc::new(err);
ptr::write(
wrapped_error,
WrappedFailure::Error(Error::CallbackError { traceback, cause }),
);
get_internal_metatable::<WrappedFailure>(state);
ffi::lua_setmetatable(state, -2);
ffi::lua_error(state)
}
Err(p) => {
let wrapped_panic = prealloc_failure.r#use(state, extra);
ptr::write(wrapped_panic, WrappedFailure::Panic(Some(p)));
get_internal_metatable::<WrappedFailure>(state);
ffi::lua_setmetatable(state, -2);
ffi::lua_error(state)
}
}
}
+19 -19
View File
@@ -1,5 +1,4 @@
use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign};
use std::u32;
/// Flags describing the set of lua standard libraries to load.
#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
@@ -7,14 +6,11 @@ pub struct StdLib(u32);
impl StdLib {
/// [`coroutine`](https://www.lua.org/manual/5.4/manual.html#6.2) library
///
/// Requires `feature = "lua54/lua53/lua52/luau"`
#[cfg(any(
feature = "lua54",
feature = "lua53",
feature = "lua52",
feature = "luau"
))]
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau"))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luau")))
)]
pub const COROUTINE: StdLib = StdLib(1);
/// [`table`](https://www.lua.org/manual/5.4/manual.html#6.6) library
@@ -32,38 +28,42 @@ impl StdLib {
pub const STRING: StdLib = StdLib(1 << 4);
/// [`utf8`](https://www.lua.org/manual/5.4/manual.html#6.5) library
///
/// Requires `feature = "lua54/lua53/luau"`
#[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))))]
pub const UTF8: StdLib = StdLib(1 << 5);
/// [`bit`](https://www.lua.org/manual/5.2/manual.html#6.7) library
///
/// Requires `feature = "lua52/luajit/luau"`
#[cfg(any(feature = "lua52", feature = "luajit", feature = "luau", doc))]
#[cfg_attr(
docsrs,
doc(cfg(any(feature = "lua52", feature = "luajit", feature = "luau")))
)]
pub const BIT: StdLib = StdLib(1 << 6);
/// [`math`](https://www.lua.org/manual/5.4/manual.html#6.7) library
pub const MATH: StdLib = StdLib(1 << 7);
/// [`package`](https://www.lua.org/manual/5.4/manual.html#6.3) library
#[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub const PACKAGE: StdLib = StdLib(1 << 8);
/// [`buffer`](https://luau-lang.org/library#buffer-library) library
/// [`buffer`](https://luau.org/library#buffer-library) library
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub const BUFFER: StdLib = StdLib(1 << 9);
/// [`vector`](https://luau.org/library#vector-library) library
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub const VECTOR: StdLib = StdLib(1 << 10);
/// [`jit`](http://luajit.org/ext_jit.html) library
///
/// Requires `feature = "luajit"`
#[cfg(any(feature = "luajit", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luajit")))]
pub const JIT: StdLib = StdLib(1 << 9);
pub const JIT: StdLib = StdLib(1 << 11);
/// (**unsafe**) [`ffi`](http://luajit.org/ext_ffi.html) library
///
/// Requires `feature = "luajit"`
#[cfg(any(feature = "luajit", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luajit")))]
pub const FFI: StdLib = StdLib(1 << 30);
+290 -125
View File
@@ -1,47 +1,30 @@
use std::borrow::{Borrow, Cow};
use std::hash::{Hash, Hasher};
use std::os::raw::c_void;
use std::ops::Deref;
use std::os::raw::{c_int, c_void};
use std::string::String as StdString;
use std::{fmt, slice, str};
use std::{cmp, fmt, slice, str};
#[cfg(feature = "serialize")]
use crate::error::{Error, Result};
use crate::state::Lua;
use crate::traits::IntoLua;
use crate::types::{LuaType, ValueRef};
use crate::value::Value;
#[cfg(feature = "serde")]
use {
serde::ser::{Serialize, Serializer},
std::result::Result as StdResult,
};
use crate::error::{Error, Result};
use crate::types::LuaRef;
/// Handle to an internal Lua string.
///
/// Unlike Rust strings, Lua strings may not be valid UTF-8.
#[derive(Clone)]
pub struct String<'lua>(pub(crate) LuaRef<'lua>);
pub struct String(pub(crate) ValueRef);
/// 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> {
/// Get a `&str` slice if the Lua string is valid UTF-8.
impl String {
/// Get a [`BorrowedStr`] if the Lua string is valid UTF-8.
///
/// # Examples
///
@@ -60,19 +43,19 @@ impl<'lua> String<'lua> {
/// # }
/// ```
#[inline]
pub fn to_str(&self) -> Result<&str> {
str::from_utf8(self.as_bytes()).map_err(|e| Error::FromLuaConversionError {
from: "string",
to: "&str",
message: Some(e.to_string()),
})
pub fn to_str(&self) -> Result<BorrowedStr<'_>> {
BorrowedStr::try_from(self)
}
/// Converts this string to a [`Cow<str>`].
/// Converts this string to a [`StdString`].
///
/// Any non-Unicode sequences are replaced with [`U+FFFD REPLACEMENT CHARACTER`][U+FFFD].
///
/// This method returns [`StdString`] instead of [`Cow<'_, str>`] because lifetime cannot be
/// bound to a weak Lua object.
///
/// [U+FFFD]: std::char::REPLACEMENT_CHARACTER
/// [`Cow<'_, str>`]: std::borrow::Cow
///
/// # Examples
///
@@ -87,13 +70,23 @@ impl<'lua> String<'lua> {
/// # }
/// ```
#[inline]
pub fn to_string_lossy(&self) -> Cow<'_, str> {
StdString::from_utf8_lossy(self.as_bytes())
pub fn to_string_lossy(&self) -> StdString {
StdString::from_utf8_lossy(&self.as_bytes()).into_owned()
}
/// Returns an object that implements [`Display`] for safely printing a Lua [`String`] that may
/// contain non-Unicode data.
///
/// This may perform lossy conversion.
///
/// [`Display`]: fmt::Display
pub fn display(&self) -> impl fmt::Display + '_ {
Display(self)
}
/// Get the bytes that make up this string.
///
/// The returned slice will not contain the terminating nul byte, but will contain any nul
/// The returned slice will not contain the terminating null byte, but will contain any null
/// bytes embedded into the Lua string.
///
/// # Examples
@@ -109,30 +102,40 @@ impl<'lua> String<'lua> {
/// # }
/// ```
#[inline]
pub fn as_bytes(&self) -> &[u8] {
let nulled = self.as_bytes_with_nul();
&nulled[..nulled.len() - 1]
pub fn as_bytes(&self) -> BorrowedBytes<'_> {
BorrowedBytes::from(self)
}
/// Get the bytes that make up this string, including the trailing nul byte.
pub fn as_bytes_with_nul(&self) -> &[u8] {
let ref_thread = self.0.lua.ref_thread();
unsafe {
/// Get the bytes that make up this string, including the trailing null byte.
pub fn as_bytes_with_nul(&self) -> BorrowedBytes<'_> {
let BorrowedBytes { buf, borrow, _lua } = BorrowedBytes::from(self);
// Include the trailing null byte (it's always present but excluded by default)
let buf = unsafe { slice::from_raw_parts((*buf).as_ptr(), (*buf).len() + 1) };
BorrowedBytes { buf, borrow, _lua }
}
// Does not return the terminating null byte
unsafe fn to_slice(&self) -> (&[u8], Lua) {
let lua = self.0.lua.upgrade();
let slice = {
let rawlua = lua.lock();
let ref_thread = rawlua.ref_thread();
mlua_debug_assert!(
ffi::lua_type(ref_thread, self.0.index) == ffi::LUA_TSTRING,
"string ref is not string type"
);
let mut size = 0;
// This will not trigger a 'm' error, because the reference is guaranteed to be of
// string type
let mut size = 0;
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)
};
(slice, lua)
}
/// Converts the string to a generic C pointer.
/// Converts this string to a generic C pointer.
///
/// There is no way to convert the pointer back to its original value.
///
@@ -141,66 +144,30 @@ impl<'lua> String<'lua> {
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) -> OwnedString {
OwnedString(self.0.into_owned())
}
}
impl<'lua> fmt::Debug for String<'lua> {
impl fmt::Debug for String {
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) {
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(())
write!(f, "b")?;
<bstr::BStr as fmt::Debug>::fmt(bstr::BStr::new(&bytes), f)
}
}
impl<'lua> AsRef<[u8]> for String<'lua> {
fn as_ref(&self) -> &[u8] {
self.as_bytes()
}
}
impl<'lua> Borrow<[u8]> for String<'lua> {
fn borrow(&self) -> &[u8] {
self.as_bytes()
}
}
// Lua strings are basically &[u8] slices, so implement PartialEq for anything resembling that.
// Lua strings are basically `&[u8]` slices, so implement `PartialEq` for anything resembling that.
//
// This makes our `String` comparable with `Vec<u8>`, `[u8]`, `&str`, `String` and `mlua::String`
// itself.
// This makes our `String` comparable with `Vec<u8>`, `[u8]`, `&str` and `String`.
//
// The only downside is that this disallows a comparison with `Cow<str>`, as that only implements
// `AsRef<str>`, which collides with this impl. Requiring `AsRef<str>` would fix that, but limit us
// in other ways.
impl<'lua, T> PartialEq<T> for String<'lua>
impl<T> PartialEq<T> for String
where
T: AsRef<[u8]> + ?Sized,
{
@@ -209,61 +176,259 @@ where
}
}
impl<'lua> Eq for String<'lua> {}
impl PartialEq for String {
fn eq(&self, other: &String) -> bool {
self.as_bytes() == other.as_bytes()
}
}
impl<'lua> Hash for String<'lua> {
impl Eq for String {}
impl<T> PartialOrd<T> for String
where
T: AsRef<[u8]> + ?Sized,
{
fn partial_cmp(&self, other: &T) -> Option<cmp::Ordering> {
self.as_bytes().partial_cmp(&other.as_ref())
}
}
impl PartialOrd for String {
fn partial_cmp(&self, other: &String) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for String {
fn cmp(&self, other: &String) -> cmp::Ordering {
self.as_bytes().cmp(&other.as_bytes())
}
}
impl Hash for String {
fn hash<H: Hasher>(&self, state: &mut H) {
self.as_bytes().hash(state);
}
}
#[cfg(feature = "serialize")]
impl<'lua> Serialize for String<'lua> {
#[cfg(feature = "serde")]
impl Serialize for String {
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
where
S: Serializer,
{
match self.to_str() {
Ok(s) => serializer.serialize_str(s),
Err(_) => serializer.serialize_bytes(self.as_bytes()),
Ok(s) => serializer.serialize_str(&s),
Err(_) => serializer.serialize_bytes(&self.as_bytes()),
}
}
}
// 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()) }
}
struct Display<'a>(&'a String);
/// 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()) }
impl fmt::Display for Display<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let bytes = self.0.as_bytes();
<bstr::BStr as fmt::Display>::fmt(bstr::BStr::new(&bytes), f)
}
}
#[cfg(feature = "unstable")]
impl fmt::Debug for OwnedString {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.to_ref().fmt(f)
/// A borrowed string (`&str`) that holds a strong reference to the Lua state.
pub struct BorrowedStr<'a> {
// `buf` points to a readonly memory managed by Lua
pub(crate) buf: &'a str,
pub(crate) borrow: Cow<'a, String>,
pub(crate) _lua: Lua,
}
impl Deref for BorrowedStr<'_> {
type Target = str;
#[inline(always)]
fn deref(&self) -> &str {
self.buf
}
}
impl Borrow<str> for BorrowedStr<'_> {
#[inline(always)]
fn borrow(&self) -> &str {
self.buf
}
}
impl AsRef<str> for BorrowedStr<'_> {
#[inline(always)]
fn as_ref(&self) -> &str {
self.buf
}
}
impl fmt::Display for BorrowedStr<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.buf.fmt(f)
}
}
impl fmt::Debug for BorrowedStr<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.buf.fmt(f)
}
}
impl<T> PartialEq<T> for BorrowedStr<'_>
where
T: AsRef<str>,
{
fn eq(&self, other: &T) -> bool {
self.buf == other.as_ref()
}
}
impl Eq for BorrowedStr<'_> {}
impl<T> PartialOrd<T> for BorrowedStr<'_>
where
T: AsRef<str>,
{
fn partial_cmp(&self, other: &T) -> Option<cmp::Ordering> {
self.buf.partial_cmp(other.as_ref())
}
}
impl Ord for BorrowedStr<'_> {
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.buf.cmp(other.buf)
}
}
impl<'a> TryFrom<&'a String> for BorrowedStr<'a> {
type Error = Error;
#[inline]
fn try_from(value: &'a String) -> Result<Self> {
let BorrowedBytes { buf, borrow, _lua } = BorrowedBytes::from(value);
let buf = str::from_utf8(buf).map_err(|e| Error::FromLuaConversionError {
from: "string",
to: "&str".to_string(),
message: Some(e.to_string()),
})?;
Ok(Self { buf, borrow, _lua })
}
}
/// A borrowed byte slice (`&[u8]`) that holds a strong reference to the Lua state.
pub struct BorrowedBytes<'a> {
// `buf` points to a readonly memory managed by Lua
pub(crate) buf: &'a [u8],
pub(crate) borrow: Cow<'a, String>,
pub(crate) _lua: Lua,
}
impl Deref for BorrowedBytes<'_> {
type Target = [u8];
#[inline(always)]
fn deref(&self) -> &[u8] {
self.buf
}
}
impl Borrow<[u8]> for BorrowedBytes<'_> {
#[inline(always)]
fn borrow(&self) -> &[u8] {
self.buf
}
}
impl AsRef<[u8]> for BorrowedBytes<'_> {
#[inline(always)]
fn as_ref(&self) -> &[u8] {
self.buf
}
}
impl fmt::Debug for BorrowedBytes<'_> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.buf.fmt(f)
}
}
impl<T> PartialEq<T> for BorrowedBytes<'_>
where
T: AsRef<[u8]>,
{
fn eq(&self, other: &T) -> bool {
self.buf == other.as_ref()
}
}
impl Eq for BorrowedBytes<'_> {}
impl<T> PartialOrd<T> for BorrowedBytes<'_>
where
T: AsRef<[u8]>,
{
fn partial_cmp(&self, other: &T) -> Option<cmp::Ordering> {
self.buf.partial_cmp(other.as_ref())
}
}
impl Ord for BorrowedBytes<'_> {
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.buf.cmp(other.buf)
}
}
impl<'a> IntoIterator for &'a BorrowedBytes<'_> {
type Item = &'a u8;
type IntoIter = slice::Iter<'a, u8>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<'a> From<&'a String> for BorrowedBytes<'a> {
#[inline]
fn from(value: &'a String) -> Self {
let (buf, _lua) = unsafe { value.to_slice() };
let borrow = Cow::Borrowed(value);
Self { buf, borrow, _lua }
}
}
struct WrappedString<T: AsRef<[u8]>>(T);
impl String {
/// Wraps bytes, returning an opaque type that implements [`IntoLua`] trait.
///
/// This function uses [`Lua::create_string`] under the hood.
pub fn wrap(data: impl AsRef<[u8]>) -> impl IntoLua {
WrappedString(data)
}
}
impl<T: AsRef<[u8]>> IntoLua for WrappedString<T> {
fn into_lua(self, lua: &Lua) -> Result<Value> {
lua.create_string(self.0).map(Value::String)
}
}
impl LuaType for String {
const TYPE_ID: c_int = ffi::LUA_TSTRING;
}
#[cfg(test)]
mod assertions {
use super::*;
#[cfg(not(feature = "send"))]
static_assertions::assert_not_impl_any!(String: Send);
#[cfg(feature = "send")]
static_assertions::assert_impl_all!(String: Send, Sync);
#[cfg(feature = "send")]
static_assertions::assert_impl_all!(BorrowedBytes: Send, Sync);
#[cfg(feature = "send")]
static_assertions::assert_impl_all!(BorrowedStr: Send, Sync);
}
+349 -442
View File
File diff suppressed because it is too large Load Diff
+336 -243
View File
@@ -1,21 +1,21 @@
use std::os::raw::c_int;
use std::fmt;
use std::os::raw::{c_int, c_void};
use crate::error::{Error, Result};
#[allow(unused)]
use crate::lua::Lua;
use crate::types::LuaRef;
use crate::function::Function;
use crate::state::RawLua;
use crate::traits::{FromLuaMulti, IntoLuaMulti};
use crate::types::{LuaType, ValueRef};
use crate::util::{check_stack, error_traceback_thread, pop_error, StackGuard};
use crate::value::{FromLuaMulti, IntoLuaMulti};
#[cfg(not(feature = "luau"))]
use crate::{
hook::{Debug, HookTriggers},
types::MaybeSend,
debug::{Debug, HookTriggers},
types::HookKind,
};
#[cfg(feature = "async")]
use {
crate::{lua::ASYNC_POLL_PENDING, value::MultiValue},
futures_util::stream::Stream,
std::{
future::Future,
@@ -29,86 +29,88 @@ use {
/// Status of a Lua thread (coroutine).
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum ThreadStatus {
/// The thread was just created, or is suspended because it has called `coroutine.yield`.
/// The thread was just created or is suspended (yielded).
///
/// If a thread is in this state, it can be resumed by calling [`Thread::resume`].
///
/// [`Thread::resume`]: crate::Thread::resume
Resumable,
/// Either the thread has finished executing, or the thread is currently running.
Unresumable,
/// The thread is currently running.
Running,
/// The thread has finished executing.
Finished,
/// The thread has raised a Lua error during execution.
Error,
}
/// Handle to an internal Lua thread (coroutine).
#[derive(Clone, Debug)]
pub struct Thread<'lua>(pub(crate) LuaRef<'lua>, pub(crate) *mut ffi::lua_State);
/// Owned handle to an internal Lua thread (coroutine).
/// Internal representation of a Lua thread status.
///
/// 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,
);
/// The number in `New` and `Yielded` variants is the number of arguments pushed
/// to the thread stack.
#[derive(Clone, Copy)]
enum ThreadStatusInner {
New(c_int),
Running,
Yielded(c_int),
Finished,
Error,
}
#[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)
impl ThreadStatusInner {
#[cfg(feature = "async")]
#[inline(always)]
fn is_resumable(self) -> bool {
matches!(self, ThreadStatusInner::New(_) | ThreadStatusInner::Yielded(_))
}
#[cfg(feature = "async")]
#[inline(always)]
fn is_yielded(self) -> bool {
matches!(self, ThreadStatusInner::Yielded(_))
}
}
/// Handle to an internal Lua thread (coroutine).
#[derive(Clone)]
pub struct Thread(pub(crate) ValueRef, pub(crate) *mut ffi::lua_State);
#[cfg(feature = "send")]
unsafe impl Send for Thread {}
#[cfg(feature = "send")]
unsafe impl Sync for Thread {}
/// Thread (coroutine) representation as an async [`Future`] or [`Stream`].
///
/// Requires `feature = "async"`
///
/// [`Future`]: std::future::Future
/// [`Stream`]: futures_util::stream::Stream
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
#[must_use = "futures do nothing unless you `.await` or poll them"]
pub struct AsyncThread<'lua, R> {
thread: Thread<'lua>,
init_args: Option<Result<MultiValue<'lua>>>,
ret: PhantomData<R>,
pub struct AsyncThread<R> {
thread: Thread,
ret: PhantomData<fn() -> R>,
recycle: bool,
}
impl<'lua> Thread<'lua> {
impl Thread {
/// Returns reference to the Lua state that this thread is associated with.
#[doc(hidden)]
#[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 {
pub fn state(&self) -> *mut ffi::lua_State {
self.1
}
/// Resumes execution of this thread.
///
/// Equivalent to `coroutine.resume`.
/// Equivalent to [`coroutine.resume`].
///
/// Passes `args` as arguments to the thread. If the coroutine has called `coroutine.yield`, it
/// will return these arguments. Otherwise, the coroutine wasn't yet started, so the arguments
/// are passed to its main function.
/// Passes `args` as arguments to the thread. If the coroutine has called [`coroutine.yield`],
/// it will return these arguments. Otherwise, the coroutine wasn't yet started, so the
/// arguments are passed to its main function.
///
/// If the thread is no longer in `Active` state (meaning it has finished execution or
/// encountered an error), this will return `Err(CoroutineInactive)`, otherwise will return `Ok`
/// as follows:
/// If the thread is no longer resumable (meaning it has finished execution or encountered an
/// error), this will return [`Error::CoroutineUnresumable`], otherwise will return `Ok` as
/// follows:
///
/// If the thread calls `coroutine.yield`, returns the values passed to `yield`. If the thread
/// If the thread calls [`coroutine.yield`], returns the values passed to `yield`. If the thread
/// `return`s values from its main function, returns those.
///
/// # Examples
@@ -126,101 +128,167 @@ impl<'lua> Thread<'lua> {
/// end)
/// "#).eval()?;
///
/// assert_eq!(thread.resume::<_, u32>(42)?, 123);
/// assert_eq!(thread.resume::<_, u32>(43)?, 987);
/// assert_eq!(thread.resume::<u32>(42)?, 123);
/// assert_eq!(thread.resume::<u32>(43)?, 987);
///
/// // The coroutine has now returned, so `resume` will fail
/// match thread.resume::<_, u32>(()) {
/// Err(Error::CoroutineInactive) => {},
/// match thread.resume::<u32>(()) {
/// Err(Error::CoroutineUnresumable) => {},
/// unexpected => panic!("unexpected result {:?}", unexpected),
/// }
/// # Ok(())
/// # }
/// ```
pub fn resume<A, R>(&self, args: A) -> Result<R>
///
/// [`coroutine.resume`]: https://www.lua.org/manual/5.4/manual.html#pdf-coroutine.resume
/// [`coroutine.yield`]: https://www.lua.org/manual/5.4/manual.html#pdf-coroutine.yield
pub fn resume<R>(&self, args: impl IntoLuaMulti) -> Result<R>
where
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua>,
R: FromLuaMulti,
{
let lua = self.0.lua;
let lua = self.0.lua.lock();
let mut pushed_nargs = match self.status_inner(&lua) {
ThreadStatusInner::New(nargs) | ThreadStatusInner::Yielded(nargs) => nargs,
_ => return Err(Error::CoroutineUnresumable),
};
let state = lua.state();
let thread_state = self.state();
unsafe {
let _sg = StackGuard::new(state);
let _thread_sg = StackGuard::with_top(thread_state, 0);
let nresults = self.resume_inner(args)?;
let nargs = args.push_into_stack_multi(&lua)?;
if nargs > 0 {
check_stack(thread_state, nargs)?;
ffi::lua_xmove(state, thread_state, nargs);
pushed_nargs += nargs;
}
let (_, nresults) = self.resume_inner(&lua, pushed_nargs)?;
check_stack(state, nresults + 1)?;
ffi::lua_xmove(thread_state, state, nresults);
R::from_stack_multi(nresults, lua)
R::from_stack_multi(nresults, &lua)
}
}
/// Resumes execution of this thread, immediately raising an error.
///
/// This is a Luau specific extension.
#[cfg(feature = "luau")]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
pub fn resume_error<R>(&self, error: impl crate::IntoLua) -> Result<R>
where
R: FromLuaMulti,
{
let lua = self.0.lua.lock();
match self.status_inner(&lua) {
ThreadStatusInner::New(_) | ThreadStatusInner::Yielded(_) => {}
_ => return Err(Error::CoroutineUnresumable),
};
let state = lua.state();
let thread_state = self.state();
unsafe {
let _sg = StackGuard::new(state);
let _thread_sg = StackGuard::with_top(thread_state, 0);
check_stack(state, 1)?;
error.push_into_stack(&lua)?;
ffi::lua_xmove(state, thread_state, 1);
let (_, nresults) = self.resume_inner(&lua, ffi::LUA_RESUMEERROR)?;
check_stack(state, nresults + 1)?;
ffi::lua_xmove(thread_state, state, nresults);
R::from_stack_multi(nresults, &lua)
}
}
/// Resumes execution of this thread.
///
/// It's similar to `resume()` but leaves `nresults` values on the thread stack.
unsafe fn resume_inner<A: IntoLuaMulti<'lua>>(&self, args: A) -> Result<c_int> {
let lua = self.0.lua;
unsafe fn resume_inner(&self, lua: &RawLua, nargs: c_int) -> Result<(ThreadStatusInner, c_int)> {
let state = lua.state();
let thread_state = self.state();
if self.status() != ThreadStatus::Resumable {
return Err(Error::CoroutineInactive);
}
let nargs = args.push_into_stack_multi(lua)?;
if nargs > 0 {
check_stack(thread_state, nargs)?;
ffi::lua_xmove(state, thread_state, nargs);
}
let mut nresults = 0;
#[cfg(not(feature = "luau"))]
let ret = ffi::lua_resume(thread_state, state, nargs, &mut nresults as *mut c_int);
if ret != ffi::LUA_OK && ret != ffi::LUA_YIELD {
if ret == ffi::LUA_ERRMEM {
#[cfg(feature = "luau")]
let ret = ffi::lua_resumex(thread_state, state, nargs, &mut nresults as *mut c_int);
match ret {
ffi::LUA_OK => Ok((ThreadStatusInner::Finished, nresults)),
ffi::LUA_YIELD => Ok((ThreadStatusInner::Yielded(0), nresults)),
ffi::LUA_ERRMEM => {
// Don't call error handler for memory errors
return Err(pop_error(thread_state, ret));
Err(pop_error(thread_state, ret))
}
_ => {
check_stack(state, 3)?;
protect_lua!(state, 0, 1, |state| error_traceback_thread(state, thread_state))?;
Err(pop_error(state, ret))
}
check_stack(state, 3)?;
protect_lua!(state, 0, 1, |state| error_traceback_thread(
state,
thread_state
))?;
return Err(pop_error(state, ret));
}
Ok(nresults)
}
/// Gets the status of the thread.
pub fn status(&self) -> ThreadStatus {
let thread_state = self.state();
unsafe {
let status = ffi::lua_status(thread_state);
if status != ffi::LUA_OK && status != ffi::LUA_YIELD {
ThreadStatus::Error
} else if status == ffi::LUA_YIELD || ffi::lua_gettop(thread_state) > 0 {
ThreadStatus::Resumable
} else {
ThreadStatus::Unresumable
}
match self.status_inner(&self.0.lua.lock()) {
ThreadStatusInner::New(_) | ThreadStatusInner::Yielded(_) => ThreadStatus::Resumable,
ThreadStatusInner::Running => ThreadStatus::Running,
ThreadStatusInner::Finished => ThreadStatus::Finished,
ThreadStatusInner::Error => ThreadStatus::Error,
}
}
/// Sets a 'hook' function that will periodically be called as Lua code executes.
/// Gets the status of the thread (internal implementation).
fn status_inner(&self, lua: &RawLua) -> ThreadStatusInner {
let thread_state = self.state();
if thread_state == lua.state() {
// The thread is currently running
return ThreadStatusInner::Running;
}
let status = unsafe { ffi::lua_status(thread_state) };
let top = unsafe { ffi::lua_gettop(thread_state) };
match status {
ffi::LUA_YIELD => ThreadStatusInner::Yielded(top),
ffi::LUA_OK if top > 0 => ThreadStatusInner::New(top - 1),
ffi::LUA_OK => ThreadStatusInner::Finished,
_ => ThreadStatusInner::Error,
}
}
/// 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()`].
/// This function is similar or [`Lua::set_hook`] except that it sets for the thread.
/// You can have multiple hooks for different threads.
///
/// To remove a hook call [`Thread::remove_hook`].
///
/// [`Lua::set_hook`]: crate::Lua::set_hook
#[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub fn set_hook<F>(&self, triggers: HookTriggers, callback: F)
pub fn set_hook<F>(&self, triggers: HookTriggers, callback: F) -> Result<()>
where
F: Fn(&Lua, Debug) -> Result<()> + MaybeSend + 'static,
F: Fn(&crate::Lua, &Debug) -> Result<crate::VmState> + crate::MaybeSend + 'static,
{
let lua = self.0.lua;
let lua = self.0.lua.lock();
unsafe {
lua.set_thread_hook(self.state(), triggers, callback);
lua.set_thread_hook(
self.state(),
HookKind::Thread(triggers, crate::types::XRc::new(callback)),
)
}
}
/// Removes any hook function from this thread.
#[cfg(not(feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(not(feature = "luau"))))]
pub fn remove_hook(&self) {
let _lua = self.0.lua.lock();
unsafe {
ffi::lua_sethook(self.state(), None, 0, 0);
}
}
@@ -233,27 +301,17 @@ impl<'lua> Thread<'lua> {
/// 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.
///
/// Other Lua versions can reset only new or finished threads.
///
/// Sets a Lua function for the thread afterwards.
///
/// Requires `feature = "lua54"` OR `feature = "luau"`.
///
/// [Lua 5.4]: https://www.lua.org/manual/5.4/manual.html#lua_closethread
#[cfg(any(feature = "lua54", feature = "luau"))]
#[cfg_attr(docsrs, doc(cfg(any(feature = "lua54", feature = "luau"))))]
pub fn reset(&self, func: crate::function::Function<'lua>) -> Result<()> {
let lua = self.0.lua;
pub fn reset(&self, func: Function) -> Result<()> {
let lua = self.0.lua.lock();
let thread_state = self.state();
unsafe {
#[cfg(all(feature = "lua54", not(feature = "vendored")))]
let status = ffi::lua_resetthread(thread_state);
#[cfg(all(feature = "lua54", feature = "vendored"))]
let status = ffi::lua_closethread(thread_state, lua.state());
#[cfg(feature = "lua54")]
if status != ffi::LUA_OK {
return Err(pop_error(thread_state, status));
}
#[cfg(feature = "luau")]
ffi::lua_resetthread(thread_state);
let status = self.status_inner(&lua);
self.reset_inner(status)?;
// Push function to the top of the thread stack
ffi::lua_xpush(lua.ref_thread(), thread_state, func.0.index);
@@ -269,27 +327,64 @@ impl<'lua> Thread<'lua> {
}
}
/// Converts Thread to an AsyncThread which implements [`Future`] and [`Stream`] traits.
unsafe fn reset_inner(&self, status: ThreadStatusInner) -> Result<()> {
match status {
ThreadStatusInner::New(_) => {
// The thread is new, so we can just set the top to 0
ffi::lua_settop(self.state(), 0);
Ok(())
}
ThreadStatusInner::Running => Err(Error::runtime("cannot reset a running thread")),
ThreadStatusInner::Finished => Ok(()),
#[cfg(not(any(feature = "lua54", feature = "luau")))]
ThreadStatusInner::Yielded(_) | ThreadStatusInner::Error => {
Err(Error::runtime("cannot reset non-finished thread"))
}
#[cfg(any(feature = "lua54", feature = "luau"))]
ThreadStatusInner::Yielded(_) | ThreadStatusInner::Error => {
let thread_state = self.state();
#[cfg(all(feature = "lua54", not(feature = "vendored")))]
let status = ffi::lua_resetthread(thread_state);
#[cfg(all(feature = "lua54", feature = "vendored"))]
let status = {
let lua = self.0.lua.lock();
ffi::lua_closethread(thread_state, lua.state())
};
#[cfg(feature = "lua54")]
if status != ffi::LUA_OK {
return Err(pop_error(thread_state, status));
}
#[cfg(feature = "luau")]
ffi::lua_resetthread(thread_state);
Ok(())
}
}
}
/// Converts [`Thread`] to an [`AsyncThread`] which implements [`Future`] and [`Stream`] traits.
///
/// `args` are passed as arguments to the thread function for first call.
/// The object calls [`resume()`] while polling and also allows to run rust futures
/// Only resumable threads can be converted to [`AsyncThread`].
///
/// `args` are pushed to the thread stack and will be used when the thread is resumed.
/// The object calls [`resume`] while polling and also allow to run Rust futures
/// to completion using an executor.
///
/// Using AsyncThread as a Stream allows to iterate through `coroutine.yield()`
/// values whereas Future version discards that values and poll until the final
/// Using [`AsyncThread`] as a [`Stream`] allow to iterate through [`coroutine.yield`]
/// values whereas [`Future`] version discards that values and poll until the final
/// one (returned from the thread function).
///
/// Requires `feature = "async"`
///
/// [`Future`]: std::future::Future
/// [`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
/// [`coroutine.yield`]: https://www.lua.org/manual/5.4/manual.html#pdf-coroutine.yield
///
/// # Examples
///
/// ```
/// # use mlua::{Lua, Result, Thread};
/// use futures::stream::TryStreamExt;
/// use futures_util::stream::TryStreamExt;
/// # #[tokio::main]
/// # async fn main() -> Result<()> {
/// # let lua = Lua::new();
@@ -303,7 +398,7 @@ impl<'lua> Thread<'lua> {
/// end)
/// "#).eval()?;
///
/// let mut stream = thread.into_async::<_, i64>(1);
/// let mut stream = thread.into_async::<i64>(1)?;
/// let mut sum = 0;
/// while let Some(n) = stream.try_next().await? {
/// sum += n;
@@ -316,17 +411,31 @@ impl<'lua> Thread<'lua> {
/// ```
#[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<R>(self, args: impl IntoLuaMulti) -> Result<AsyncThread<R>>
where
A: IntoLuaMulti<'lua>,
R: FromLuaMulti<'lua>,
R: FromLuaMulti,
{
let args = args.into_lua_multi(self.0.lua);
AsyncThread {
thread: self,
init_args: Some(args),
ret: PhantomData,
recycle: false,
let lua = self.0.lua.lock();
if !self.status_inner(&lua).is_resumable() {
return Err(Error::CoroutineUnresumable);
}
let state = lua.state();
let thread_state = self.state();
unsafe {
let _sg = StackGuard::new(state);
let nargs = args.push_into_stack_multi(&lua)?;
if nargs > 0 {
check_stack(thread_state, nargs)?;
ffi::lua_xmove(state, thread_state, nargs);
}
Ok(AsyncThread {
thread: self,
ret: PhantomData,
recycle: false,
})
}
}
@@ -335,7 +444,7 @@ impl<'lua> Thread<'lua> {
/// Under the hood replaces the global environment table with a new table,
/// that performs writes locally and proxies reads to caller's global environment.
///
/// This mode ideally should be used together with the global sandbox mode [`Lua::sandbox()`].
/// This mode ideally should be used together with the global sandbox mode [`Lua::sandbox`].
///
/// Please note that Luau links environment table with chunk when loading it into Lua state.
/// Therefore you need to load chunks into a thread to link with the thread environment.
@@ -344,28 +453,29 @@ impl<'lua> Thread<'lua> {
///
/// ```
/// # use mlua::{Lua, Result};
/// # #[cfg(feature = "luau")]
/// # fn main() -> Result<()> {
/// let lua = Lua::new();
/// let thread = lua.create_thread(lua.create_function(|lua2, ()| {
/// lua2.load("var = 123").exec()?;
/// assert_eq!(lua2.globals().get::<_, u32>("var")?, 123);
/// assert_eq!(lua2.globals().get::<u32>("var")?, 123);
/// Ok(())
/// })?)?;
/// thread.sandbox()?;
/// thread.resume(())?;
/// thread.resume::<()>(())?;
///
/// // The global environment should be unchanged
/// assert_eq!(lua.globals().get::<_, Option<u32>>("var")?, None);
/// assert_eq!(lua.globals().get::<Option<u32>>("var")?, None);
/// # Ok(())
/// # }
/// ```
///
/// Requires `feature = "luau"`
#[cfg(any(feature = "luau", docsrs))]
/// # #[cfg(not(feature = "luau"))]
/// # fn main() { }
/// ```
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
#[doc(hidden)]
pub fn sandbox(&self) -> Result<()> {
let lua = self.0.lua;
let lua = self.0.lua.lock();
let state = lua.state();
let thread_state = self.state();
unsafe {
@@ -375,64 +485,60 @@ impl<'lua> Thread<'lua> {
}
}
/// 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")))))]
/// 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 into_owned(self) -> OwnedThread {
OwnedThread(self.0.into_owned(), self.1)
pub fn to_pointer(&self) -> *const c_void {
self.0.to_pointer()
}
}
impl<'lua> PartialEq for Thread<'lua> {
impl fmt::Debug for Thread {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt.debug_tuple("Thread").field(&self.0).finish()
}
}
impl PartialEq for Thread {
fn eq(&self, other: &Self) -> bool {
self.0 == other.0
}
}
// 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()
}
impl LuaType for Thread {
const TYPE_ID: c_int = ffi::LUA_TTHREAD;
}
#[cfg(feature = "async")]
impl<'lua, R> AsyncThread<'lua, R> {
#[inline]
impl<R> AsyncThread<R> {
#[inline(always)]
pub(crate) fn set_recyclable(&mut self, recyclable: bool) {
self.recycle = recyclable;
}
}
#[cfg(feature = "async")]
#[cfg(any(feature = "lua54", feature = "luau"))]
impl<'lua, R> Drop for AsyncThread<'lua, R> {
impl<R> Drop for AsyncThread<R> {
fn drop(&mut self) {
if self.recycle {
unsafe {
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());
if let Some(lua) = self.thread.0.lua.try_lock() {
unsafe {
let mut status = self.thread.status_inner(&lua);
if matches!(status, ThreadStatusInner::Yielded(0)) {
// The thread is dropped while yielded, resume it with the "terminate" signal
ffi::lua_pushlightuserdata(self.thread.1, crate::Lua::poll_terminate().0);
if let Ok((new_status, _)) = self.thread.resume_inner(&lua, 1) {
// `new_status` should always be `ThreadStatusInner::Yielded(0)`
status = new_status;
}
}
// For Lua 5.4 this also closes all pending to-be-closed variables
if self.thread.reset_inner(status).is_ok() {
lua.recycle_thread(&mut self.thread);
}
}
}
@@ -441,88 +547,73 @@ impl<'lua, R> Drop for AsyncThread<'lua, R> {
}
#[cfg(feature = "async")]
impl<'lua, R> Stream for AsyncThread<'lua, R>
where
R: FromLuaMulti<'lua>,
{
impl<R: FromLuaMulti> Stream for AsyncThread<R> {
type Item = Result<R>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
if self.thread.status() != ThreadStatus::Resumable {
return Poll::Ready(None);
}
let lua = self.thread.0.lua.lock();
let nargs = match self.thread.status_inner(&lua) {
ThreadStatusInner::New(nargs) | ThreadStatusInner::Yielded(nargs) => nargs,
_ => return Poll::Ready(None),
};
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());
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(())?
};
let (status, nresults) = (self.thread).resume_inner(&lua, nargs)?;
if nresults == 1 && is_poll_pending(thread_state) {
return Poll::Pending;
if status.is_yielded() {
if nresults == 1 && is_poll_pending(thread_state) {
return Poll::Pending;
}
// Continue polling
cx.waker().wake_by_ref();
}
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)))
Poll::Ready(Some(R::from_stack_multi(nresults, &lua)))
}
}
}
#[cfg(feature = "async")]
impl<'lua, R> Future for AsyncThread<'lua, R>
where
R: FromLuaMulti<'lua>,
{
impl<R: FromLuaMulti> Future for AsyncThread<R> {
type Output = Result<R>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if self.thread.status() != ThreadStatus::Resumable {
return Poll::Ready(Err(Error::CoroutineInactive));
}
let lua = self.thread.0.lua.lock();
let nargs = match self.thread.status_inner(&lua) {
ThreadStatusInner::New(nargs) | ThreadStatusInner::Yielded(nargs) => nargs,
_ => return Poll::Ready(Err(Error::CoroutineUnresumable)),
};
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());
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(())?
};
let (status, nresults) = self.thread.resume_inner(&lua, nargs)?;
if nresults == 1 && is_poll_pending(thread_state) {
return Poll::Pending;
}
if ffi::lua_status(thread_state) == ffi::LUA_YIELD {
// Ignore value returned via yield()
cx.waker().wake_by_ref();
if status.is_yielded() {
if !(nresults == 1 && is_poll_pending(thread_state)) {
// 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))
Poll::Ready(R::from_stack_multi(nresults, &lua))
}
}
}
@@ -530,17 +621,12 @@ where
#[cfg(feature = "async")]
#[inline(always)]
unsafe fn is_poll_pending(state: *mut ffi::lua_State) -> bool {
if ffi::lua_islightuserdata(state, -1) != 0 {
let stack_ptr = ffi::lua_touserdata(state, -1) as *const u8;
let pending_ptr = &ASYNC_POLL_PENDING as *const u8;
return std::ptr::eq(stack_ptr, pending_ptr);
}
false
ffi::lua_tolightuserdata(state, -1) == crate::Lua::poll_pending().0
}
#[cfg(feature = "async")]
struct WakerGuard<'lua, 'a> {
lua: &'lua Lua,
lua: &'lua RawLua,
prev: NonNull<Waker>,
_phantom: PhantomData<&'a ()>,
}
@@ -548,7 +634,7 @@ struct WakerGuard<'lua, 'a> {
#[cfg(feature = "async")]
impl<'lua, 'a> WakerGuard<'lua, 'a> {
#[inline]
pub fn new(lua: &'lua Lua, waker: &'a Waker) -> Result<WakerGuard<'lua, 'a>> {
pub fn new(lua: &'lua RawLua, waker: &'a Waker) -> Result<WakerGuard<'lua, 'a>> {
let prev = unsafe { lua.set_waker(NonNull::from(waker)) };
Ok(WakerGuard {
lua,
@@ -559,7 +645,7 @@ impl<'lua, 'a> WakerGuard<'lua, 'a> {
}
#[cfg(feature = "async")]
impl<'lua, 'a> Drop for WakerGuard<'lua, 'a> {
impl Drop for WakerGuard<'_, '_> {
fn drop(&mut self) {
unsafe { self.lua.set_waker(self.prev) };
}
@@ -569,5 +655,12 @@ impl<'lua, 'a> Drop for WakerGuard<'lua, 'a> {
mod assertions {
use super::*;
#[cfg(not(feature = "send"))]
static_assertions::assert_not_impl_any!(Thread: Send);
#[cfg(feature = "send")]
static_assertions::assert_impl_all!(Thread: Send, Sync);
#[cfg(all(feature = "async", not(feature = "send")))]
static_assertions::assert_not_impl_any!(AsyncThread<()>: Send);
#[cfg(all(feature = "async", feature = "send"))]
static_assertions::assert_impl_all!(AsyncThread<()>: Send, Sync);
}
+307
View File
@@ -0,0 +1,307 @@
use std::os::raw::c_int;
use std::string::String as StdString;
use std::sync::Arc;
use crate::error::{Error, Result};
use crate::multi::MultiValue;
use crate::private::Sealed;
use crate::state::{Lua, RawLua};
use crate::types::MaybeSend;
use crate::util::{check_stack, short_type_name};
use crate::value::Value;
#[cfg(feature = "async")]
use {crate::function::AsyncCallFuture, std::future::Future};
/// Trait for types convertible to [`Value`].
pub trait IntoLua: Sized {
/// Performs the conversion.
fn into_lua(self, lua: &Lua) -> Result<Value>;
/// 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: &RawLua) -> Result<()> {
lua.push_value(&self.into_lua(lua.lua())?)
}
}
/// Trait for types convertible from [`Value`].
pub trait FromLua: Sized {
/// Performs the conversion.
fn from_lua(value: Value, 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, i: usize, to: Option<&str>, 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: &RawLua) -> Result<Self> {
Self::from_lua(lua.stack_value(idx, None), lua.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: &RawLua) -> 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),
})
}
}
/// Trait for types convertible to any number of Lua values.
///
/// This is a generalization of [`IntoLua`], allowing any number of resulting Lua values instead of
/// just one. Any type that implements [`IntoLua`] will automatically implement this trait.
pub trait IntoLuaMulti: Sized {
/// Performs the conversion.
fn into_lua_multi(self, lua: &Lua) -> Result<MultiValue>;
/// Pushes the values into the Lua stack.
///
/// Returns number of pushed values.
#[doc(hidden)]
#[inline]
unsafe fn push_into_stack_multi(self, lua: &RawLua) -> Result<c_int> {
let values = self.into_lua_multi(lua.lua())?;
let len: c_int = values.len().try_into().unwrap();
unsafe {
check_stack(lua.state(), len + 1)?;
for val in &values {
lua.push_value(val)?;
}
}
Ok(len)
}
}
/// Trait for types that can be created from an arbitrary number of Lua values.
///
/// This is a generalization of [`FromLua`], allowing an arbitrary number of Lua values to
/// participate in the conversion. Any type that implements [`FromLua`] will automatically
/// implement this trait.
pub trait FromLuaMulti: Sized {
/// Performs the conversion.
///
/// In case `values` contains more values than needed to perform the conversion, the excess
/// values should be ignored. This reflects the semantics of Lua when calling a function or
/// assigning values. Similarly, if not enough values are given, conversions should assume that
/// any missing values are nil.
fn from_lua_multi(values: MultiValue, 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, i: usize, to: Option<&str>, 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: &RawLua) -> Result<Self> {
let mut values = MultiValue::with_capacity(nvals as usize);
for idx in 0..nvals {
values.push_back(lua.stack_value(-nvals + idx, None));
}
Self::from_lua_multi(values, lua.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: &RawLua) -> Result<Self> {
let _ = (i, to);
Self::from_stack_multi(nargs, lua)
}
}
/// A trait for types that can be used as Lua objects (usually table and userdata).
pub trait ObjectLike: Sealed {
/// Gets the value associated to `key` from the object, assuming it has `__index` metamethod.
fn get<V: FromLua>(&self, key: impl IntoLua) -> Result<V>;
/// Sets the value associated to `key` in the object, assuming it has `__newindex` metamethod.
fn set(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()>;
/// Calls the object as a function assuming it has `__call` metamethod.
///
/// The metamethod is called with the object as its first argument, followed by the passed
/// arguments.
fn call<R>(&self, args: impl IntoLuaMulti) -> Result<R>
where
R: FromLuaMulti;
/// Asynchronously calls the object as a function assuming it has `__call` metamethod.
///
/// The metamethod is called with the object as its first argument, followed by the passed
/// arguments.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn call_async<R>(&self, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
where
R: FromLuaMulti;
/// Gets the function associated to key `name` from the object and calls it,
/// passing the object itself along with `args` as function arguments.
fn call_method<R>(&self, name: &str, args: impl IntoLuaMulti) -> Result<R>
where
R: FromLuaMulti;
/// Gets the function associated to key `name` from the object and asynchronously calls it,
/// passing the object itself along with `args` as function arguments.
///
/// This might invoke the `__index` metamethod.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn call_async_method<R>(&self, name: &str, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
where
R: FromLuaMulti;
/// Gets the function associated to key `name` from the object and calls it,
/// passing `args` as function arguments.
///
/// This might invoke the `__index` metamethod.
fn call_function<R>(&self, name: &str, args: impl IntoLuaMulti) -> Result<R>
where
R: FromLuaMulti;
/// Gets the function associated to key `name` from the object and asynchronously calls it,
/// passing `args` as function arguments.
///
/// This might invoke the `__index` metamethod.
#[cfg(feature = "async")]
#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
fn call_async_function<R>(&self, name: &str, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
where
R: FromLuaMulti;
/// Converts the object to a string in a human-readable format.
///
/// This might invoke the `__tostring` metamethod.
fn to_string(&self) -> Result<StdString>;
}
/// A trait for types that can be used as Lua functions.
pub trait LuaNativeFn<A: FromLuaMulti> {
type Output: IntoLuaMulti;
fn call(&self, args: A) -> Self::Output;
}
/// A trait for types with mutable state that can be used as Lua functions.
pub trait LuaNativeFnMut<A: FromLuaMulti> {
type Output: IntoLuaMulti;
fn call(&mut self, args: A) -> Self::Output;
}
/// A trait for types that returns a future and can be used as Lua functions.
#[cfg(feature = "async")]
pub trait LuaNativeAsyncFn<A: FromLuaMulti> {
type Output: IntoLuaMulti;
fn call(&self, args: A) -> impl Future<Output = Self::Output> + MaybeSend + 'static;
}
macro_rules! impl_lua_native_fn {
($($A:ident),*) => {
impl<FN, $($A,)* R> LuaNativeFn<($($A,)*)> for FN
where
FN: Fn($($A,)*) -> R + MaybeSend + 'static,
($($A,)*): FromLuaMulti,
R: IntoLuaMulti,
{
type Output = R;
#[allow(non_snake_case)]
fn call(&self, args: ($($A,)*)) -> Self::Output {
let ($($A,)*) = args;
self($($A,)*)
}
}
impl<FN, $($A,)* R> LuaNativeFnMut<($($A,)*)> for FN
where
FN: FnMut($($A,)*) -> R + MaybeSend + 'static,
($($A,)*): FromLuaMulti,
R: IntoLuaMulti,
{
type Output = R;
#[allow(non_snake_case)]
fn call(&mut self, args: ($($A,)*)) -> Self::Output {
let ($($A,)*) = args;
self($($A,)*)
}
}
#[cfg(feature = "async")]
impl<FN, $($A,)* Fut, R> LuaNativeAsyncFn<($($A,)*)> for FN
where
FN: Fn($($A,)*) -> Fut + MaybeSend + 'static,
($($A,)*): FromLuaMulti,
Fut: Future<Output = R> + MaybeSend + 'static,
R: IntoLuaMulti,
{
type Output = R;
#[allow(non_snake_case)]
fn call(&self, args: ($($A,)*)) -> impl Future<Output = Self::Output> + MaybeSend + 'static {
let ($($A,)*) = args;
self($($A,)*)
}
}
};
}
impl_lua_native_fn!();
impl_lua_native_fn!(A);
impl_lua_native_fn!(A, B);
impl_lua_native_fn!(A, B, C);
impl_lua_native_fn!(A, B, C, D);
impl_lua_native_fn!(A, B, C, D, E);
impl_lua_native_fn!(A, B, C, D, E, F);
impl_lua_native_fn!(A, B, C, D, E, F, G);
impl_lua_native_fn!(A, B, C, D, E, F, G, H);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L, M);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L, M, N);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O);
impl_lua_native_fn!(A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P);
pub(crate) trait ShortTypeName {
#[inline(always)]
fn type_name() -> StdString {
short_type_name::<Self>()
}
}
impl<T> ShortTypeName for T {}
+86 -475
View File
@@ -1,93 +1,114 @@
use std::any::{Any, TypeId};
use std::cell::{Cell, Ref, RefCell, RefMut, UnsafeCell};
use std::hash::{Hash, Hasher};
use std::ops::{Deref, DerefMut};
use std::cell::UnsafeCell;
use std::os::raw::{c_int, c_void};
use std::result::Result as StdResult;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
use std::{fmt, mem, ptr};
use rustc_hash::FxHashMap;
use crate::error::Result;
#[cfg(not(feature = "luau"))]
use crate::hook::Debug;
use crate::lua::{ExtraData, Lua};
use crate::debug::{Debug, HookTriggers};
use crate::error::Result;
use crate::state::{ExtraData, Lua, RawLua};
#[cfg(feature = "async")]
use {crate::value::MultiValue, futures_util::future::LocalBoxFuture};
// Re-export mutex wrappers
pub(crate) use sync::{ArcReentrantMutexGuard, ReentrantMutex, ReentrantMutexGuard, XRc, XWeak};
#[cfg(feature = "unstable")]
use {crate::lua::LuaInner, std::marker::PhantomData};
#[cfg(all(feature = "async", feature = "send"))]
pub(crate) type BoxFuture<'a, T> = futures_util::future::BoxFuture<'a, T>;
#[cfg(all(feature = "luau", feature = "serialize"))]
use serde::ser::{Serialize, SerializeTupleStruct, Serializer};
#[cfg(all(feature = "async", not(feature = "send")))]
pub(crate) type BoxFuture<'a, T> = futures_util::future::LocalBoxFuture<'a, T>;
pub use app_data::{AppData, AppDataRef, AppDataRefMut};
pub use either::Either;
pub use registry_key::RegistryKey;
pub(crate) use value_ref::ValueRef;
/// Type of Lua integer numbers.
pub type Integer = ffi::lua_Integer;
/// Type of Lua floating point numbers.
pub type Number = ffi::lua_Number;
// Represents different subtypes wrapped to AnyUserData
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub(crate) enum SubtypeId {
None,
#[cfg(feature = "luau")]
Buffer,
#[cfg(feature = "luajit")]
CData,
}
/// A "light" userdata value. Equivalent to an unmanaged raw pointer.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct LightUserData(pub *mut c_void);
pub(crate) type Callback<'lua, 'a> = Box<dyn Fn(&'lua Lua, c_int) -> Result<c_int> + 'a>;
#[cfg(feature = "send")]
unsafe impl Send for LightUserData {}
#[cfg(feature = "send")]
unsafe impl Sync for LightUserData {}
#[cfg(feature = "send")]
pub(crate) type Callback = Box<dyn Fn(&RawLua, c_int) -> Result<c_int> + Send + 'static>;
#[cfg(not(feature = "send"))]
pub(crate) type Callback = Box<dyn Fn(&RawLua, c_int) -> Result<c_int> + 'static>;
pub(crate) type ScopedCallback<'s> = Box<dyn Fn(&RawLua, c_int) -> Result<c_int> + 's>;
pub(crate) struct Upvalue<T> {
pub(crate) data: T,
pub(crate) extra: Arc<UnsafeCell<ExtraData>>,
pub(crate) extra: XRc<UnsafeCell<ExtraData>>,
}
pub(crate) type CallbackUpvalue = Upvalue<Callback<'static, 'static>>;
pub(crate) type CallbackUpvalue = Upvalue<Option<Callback>>;
#[cfg(all(feature = "async", feature = "send"))]
pub(crate) type AsyncCallback =
Box<dyn for<'a> Fn(&'a RawLua, c_int) -> BoxFuture<'a, Result<c_int>> + Send + 'static>;
#[cfg(all(feature = "async", not(feature = "send")))]
pub(crate) type AsyncCallback =
Box<dyn for<'a> Fn(&'a RawLua, c_int) -> BoxFuture<'a, Result<c_int>> + 'static>;
#[cfg(feature = "async")]
pub(crate) type AsyncCallback<'lua, 'a> =
Box<dyn Fn(&'lua Lua, MultiValue<'lua>) -> LocalBoxFuture<'lua, Result<c_int>> + 'a>;
pub(crate) type AsyncCallbackUpvalue = Upvalue<AsyncCallback>;
#[cfg(feature = "async")]
pub(crate) type AsyncCallbackUpvalue = Upvalue<AsyncCallback<'static, 'static>>;
pub(crate) type AsyncPollUpvalue = Upvalue<Option<BoxFuture<'static, Result<c_int>>>>;
#[cfg(feature = "async")]
pub(crate) type AsyncPollUpvalue = Upvalue<LocalBoxFuture<'static, Result<c_int>>>;
/// Type to set next Luau VM action after executing interrupt function.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
/// Type to set next Lua VM action after executing interrupt or hook function.
pub enum VmState {
Continue,
/// Yield the current thread.
///
/// Supported by Lua 5.3+ and Luau.
Yield,
}
#[cfg(not(feature = "luau"))]
pub(crate) enum HookKind {
Global,
Thread(HookTriggers, HookCallback),
}
#[cfg(all(feature = "send", not(feature = "luau")))]
pub(crate) type HookCallback = Arc<dyn Fn(&Lua, Debug) -> Result<()> + Send>;
pub(crate) type HookCallback = XRc<dyn Fn(&Lua, &Debug) -> Result<VmState> + Send>;
#[cfg(all(not(feature = "send"), not(feature = "luau")))]
pub(crate) type HookCallback = Arc<dyn Fn(&Lua, Debug) -> Result<()>>;
pub(crate) type HookCallback = XRc<dyn Fn(&Lua, &Debug) -> Result<VmState>>;
#[cfg(all(feature = "luau", feature = "send"))]
pub(crate) type InterruptCallback = Arc<dyn Fn(&Lua) -> Result<VmState> + Send>;
#[cfg(all(feature = "send", feature = "luau"))]
pub(crate) type InterruptCallback = XRc<dyn Fn(&Lua) -> Result<VmState> + Send>;
#[cfg(all(feature = "luau", not(feature = "send")))]
pub(crate) type InterruptCallback = Arc<dyn Fn(&Lua) -> Result<VmState>>;
#[cfg(all(not(feature = "send"), feature = "luau"))]
pub(crate) type InterruptCallback = XRc<dyn Fn(&Lua) -> Result<VmState>>;
#[cfg(all(feature = "send", feature = "luau"))]
pub(crate) type ThreadCreationCallback = XRc<dyn Fn(&Lua, crate::Thread) -> Result<()> + Send>;
#[cfg(all(not(feature = "send"), feature = "luau"))]
pub(crate) type ThreadCreationCallback = XRc<dyn Fn(&Lua, crate::Thread) -> Result<()>>;
#[cfg(all(feature = "send", feature = "luau"))]
pub(crate) type ThreadCollectionCallback = XRc<dyn Fn(crate::LightUserData) + Send>;
#[cfg(all(not(feature = "send"), feature = "luau"))]
pub(crate) type ThreadCollectionCallback = XRc<dyn Fn(crate::LightUserData)>;
#[cfg(all(feature = "send", feature = "lua54"))]
pub(crate) type WarnCallback = Box<dyn Fn(&Lua, &str, bool) -> Result<()> + Send>;
pub(crate) type WarnCallback = XRc<dyn Fn(&Lua, &str, bool) -> Result<()> + Send>;
#[cfg(all(not(feature = "send"), feature = "lua54"))]
pub(crate) type WarnCallback = Box<dyn Fn(&Lua, &str, bool) -> Result<()>>;
pub(crate) type WarnCallback = XRc<dyn Fn(&Lua, &str, bool) -> Result<()>>;
/// A trait that adds `Send` requirement if `send` feature is enabled.
#[cfg(feature = "send")]
pub trait MaybeSend: Send {}
#[cfg(feature = "send")]
@@ -98,445 +119,35 @@ pub trait MaybeSend {}
#[cfg(not(feature = "send"))]
impl<T> MaybeSend for T {}
/// A Luau vector type.
///
/// By default vectors are 3-dimensional, but can be 4-dimensional
/// if the `luau-vector4` feature is enabled.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
#[derive(Debug, Default, Clone, Copy, PartialEq)]
pub struct Vector(pub(crate) [f32; Self::SIZE]);
#[cfg(any(feature = "luau", doc))]
impl fmt::Display for Vector {
#[rustfmt::skip]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
#[cfg(not(feature = "luau-vector4"))]
return write!(f, "vector({}, {}, {})", self.x(), self.y(), self.z());
#[cfg(feature = "luau-vector4")]
return write!(f, "vector({}, {}, {}, {})", self.x(), self.y(), self.z(), self.w());
}
}
#[cfg(any(feature = "luau", doc))]
impl Vector {
pub(crate) const SIZE: usize = if cfg!(feature = "luau-vector4") { 4 } else { 3 };
/// Creates a new vector.
#[cfg(not(feature = "luau-vector4"))]
pub const fn new(x: f32, y: f32, z: f32) -> Self {
Self([x, y, z])
}
/// Creates a new vector.
#[cfg(feature = "luau-vector4")]
pub const fn new(x: f32, y: f32, z: f32, w: f32) -> Self {
Self([x, y, z, w])
}
/// Creates a new vector with all components set to `0.0`.
#[doc(hidden)]
pub const fn zero() -> Self {
Self([0.0; Self::SIZE])
}
/// Returns 1st component of the vector.
pub const fn x(&self) -> f32 {
self.0[0]
}
/// Returns 2nd component of the vector.
pub const fn y(&self) -> f32 {
self.0[1]
}
/// Returns 3rd component of the vector.
pub const fn z(&self) -> f32 {
self.0[2]
}
/// Returns 4th component of the vector.
#[cfg(any(feature = "luau-vector4", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau-vector4")))]
pub const fn w(&self) -> f32 {
self.0[3]
}
}
#[cfg(all(feature = "luau", feature = "serialize"))]
impl Serialize for Vector {
fn serialize<S: Serializer>(&self, serializer: S) -> StdResult<S::Ok, S::Error> {
let mut ts = serializer.serialize_tuple_struct("Vector", Self::SIZE)?;
ts.serialize_field(&self.x())?;
ts.serialize_field(&self.y())?;
ts.serialize_field(&self.z())?;
#[cfg(feature = "luau-vector4")]
ts.serialize_field(&self.w())?;
ts.end()
}
}
#[cfg(any(feature = "luau", doc))]
impl PartialEq<[f32; Self::SIZE]> for Vector {
#[inline]
fn eq(&self, other: &[f32; Self::SIZE]) -> bool {
self.0 == *other
}
}
pub(crate) struct DestructedUserdata;
/// An auto generated key into the Lua registry.
///
/// This is a handle to a value stored inside the Lua registry. It is not automatically
/// garbage collected on Drop, but it can be removed with [`Lua::remove_registry_value`],
/// and instances not manually removed can be garbage collected with [`Lua::expire_registry_values`].
///
/// Be warned, If you place this into Lua via a [`UserData`] type or a rust callback, it is *very
/// easy* to accidentally cause reference cycles that the Lua garbage collector cannot resolve.
/// Instead of placing a [`RegistryKey`] into a [`UserData`] type, prefer instead to use
/// [`AnyUserData::set_user_value`] / [`AnyUserData::user_value`].
///
/// [`UserData`]: crate::UserData
/// [`RegistryKey`]: crate::RegistryKey
/// [`Lua::remove_registry_value`]: crate::Lua::remove_registry_value
/// [`Lua::expire_registry_values`]: crate::Lua::expire_registry_values
/// [`AnyUserData::set_user_value`]: crate::AnyUserData::set_user_value
/// [`AnyUserData::user_value`]: crate::AnyUserData::user_value
pub struct RegistryKey {
pub(crate) registry_id: c_int,
pub(crate) is_nil: AtomicBool,
pub(crate) unref_list: Arc<Mutex<Option<Vec<c_int>>>>,
pub(crate) trait LuaType {
const TYPE_ID: c_int;
}
impl fmt::Debug for RegistryKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "RegistryKey({})", self.registry_id)
}
impl LuaType for bool {
const TYPE_ID: c_int = ffi::LUA_TBOOLEAN;
}
impl Hash for RegistryKey {
fn hash<H: Hasher>(&self, state: &mut H) {
self.registry_id.hash(state)
}
impl LuaType for Number {
const TYPE_ID: c_int = ffi::LUA_TNUMBER;
}
impl PartialEq for RegistryKey {
fn eq(&self, other: &RegistryKey) -> bool {
self.registry_id == other.registry_id && Arc::ptr_eq(&self.unref_list, &other.unref_list)
}
impl LuaType for LightUserData {
const TYPE_ID: c_int = ffi::LUA_TLIGHTUSERDATA;
}
impl Eq for RegistryKey {}
impl Drop for RegistryKey {
fn drop(&mut self) {
// We don't need to collect nil slot
if self.registry_id > ffi::LUA_REFNIL {
let mut unref_list = mlua_expect!(self.unref_list.lock(), "unref list poisoned");
if let Some(list) = unref_list.as_mut() {
list.push(self.registry_id);
}
}
}
}
impl RegistryKey {
// Creates a new instance of `RegistryKey`
pub(crate) const fn new(id: c_int, unref_list: Arc<Mutex<Option<Vec<c_int>>>>) -> Self {
RegistryKey {
registry_id: id,
is_nil: AtomicBool::new(id == ffi::LUA_REFNIL),
unref_list,
}
}
// Destroys the `RegistryKey` without adding to the unref list
pub(crate) fn take(self) -> c_int {
let registry_id = self.registry_id;
unsafe {
ptr::read(&self.unref_list);
mem::forget(self);
}
registry_id
}
// Returns true if this `RegistryKey` holds a nil value
#[inline(always)]
pub(crate) fn is_nil(&self) -> bool {
self.is_nil.load(Ordering::Relaxed)
}
// Marks value of this `RegistryKey` as `Nil`
#[inline(always)]
pub(crate) fn set_nil(&self, enabled: bool) {
// We cannot replace previous value with nil in as this will break
// Lua mechanism to find free keys.
// Instead, we set a special flag to mark value as nil.
self.is_nil.store(enabled, Ordering::Relaxed);
}
}
pub(crate) struct LuaRef<'lua> {
pub(crate) lua: &'lua Lua,
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> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Ref({:p})", self.to_pointer())
}
}
impl<'lua> Clone for LuaRef<'lua> {
fn clone(&self) -> Self {
self.lua.clone_ref(self)
}
}
impl<'lua> Drop for LuaRef<'lua> {
fn drop(&mut self) {
if self.drop {
self.lua.drop_ref_index(self.index);
}
}
}
impl<'lua> PartialEq for LuaRef<'lua> {
fn eq(&self, other: &Self) -> bool {
let ref_thread = self.lua.ref_thread();
assert!(
ref_thread == other.lua.ref_thread(),
"Lua instance passed Value created from a different main Lua state"
);
unsafe { ffi::lua_rawequal(ref_thread, self.index, other.index) == 1 }
}
}
#[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)
}
}
mod app_data;
mod registry_key;
mod sync;
mod value_ref;
#[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);
#[cfg(not(feature = "send"))]
static_assertions::assert_not_impl_any!(ValueRef: Send);
#[cfg(feature = "send")]
static_assertions::assert_impl_all!(ValueRef: Send, Sync);
}
+212
View File
@@ -0,0 +1,212 @@
use std::any::{Any, TypeId};
use std::cell::{BorrowError, BorrowMutError, Cell, Ref, RefCell, RefMut, UnsafeCell};
use std::fmt;
use std::ops::{Deref, DerefMut};
use std::result::Result as StdResult;
use rustc_hash::FxHashMap;
use super::MaybeSend;
use crate::state::LuaGuard;
#[cfg(not(feature = "send"))]
type Container = UnsafeCell<FxHashMap<TypeId, RefCell<Box<dyn Any>>>>;
#[cfg(feature = "send")]
type Container = UnsafeCell<FxHashMap<TypeId, RefCell<Box<dyn Any + Send>>>>;
/// A container for arbitrary data associated with the Lua state.
#[derive(Debug, Default)]
pub struct AppData {
container: Container,
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)))
}
#[inline]
#[track_caller]
pub(crate) fn borrow<T: 'static>(&self, guard: Option<LuaGuard>) -> Option<AppDataRef<'_, T>> {
match self.try_borrow(guard) {
Ok(data) => data,
Err(err) => panic!("already mutably borrowed: {err:?}"),
}
}
pub(crate) fn try_borrow<T: 'static>(
&self,
guard: Option<LuaGuard>,
) -> Result<Option<AppDataRef<'_, T>>, BorrowError> {
let data = unsafe { &*self.container.get() }
.get(&TypeId::of::<T>())
.map(|c| c.try_borrow())
.transpose()?
.and_then(|data| Ref::filter_map(data, |data| data.downcast_ref()).ok());
match data {
Some(data) => {
self.borrow.set(self.borrow.get() + 1);
Ok(Some(AppDataRef {
data,
borrow: &self.borrow,
_guard: guard,
}))
}
None => Ok(None),
}
}
#[inline]
#[track_caller]
pub(crate) fn borrow_mut<T: 'static>(&self, guard: Option<LuaGuard>) -> Option<AppDataRefMut<'_, T>> {
match self.try_borrow_mut(guard) {
Ok(data) => data,
Err(err) => panic!("already borrowed: {err:?}"),
}
}
pub(crate) fn try_borrow_mut<T: 'static>(
&self,
guard: Option<LuaGuard>,
) -> Result<Option<AppDataRefMut<'_, T>>, BorrowMutError> {
let data = unsafe { &*self.container.get() }
.get(&TypeId::of::<T>())
.map(|c| c.try_borrow_mut())
.transpose()?
.and_then(|data| RefMut::filter_map(data, |data| data.downcast_mut()).ok());
match data {
Some(data) => {
self.borrow.set(self.borrow.get() + 1);
Ok(Some(AppDataRefMut {
data,
borrow: &self.borrow,
_guard: guard,
}))
}
None => Ok(None),
}
}
#[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>,
_guard: Option<LuaGuard>,
}
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>,
_guard: Option<LuaGuard>,
}
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::*;
#[cfg(not(feature = "send"))]
static_assertions::assert_not_impl_any!(AppData: Send);
#[cfg(feature = "send")]
static_assertions::assert_impl_all!(AppData: Send);
// Must be !Send
static_assertions::assert_not_impl_any!(AppDataRef<()>: Send);
static_assertions::assert_not_impl_any!(AppDataRefMut<()>: Send);
}
+100
View File
@@ -0,0 +1,100 @@
use std::hash::{Hash, Hasher};
use std::os::raw::c_int;
use std::sync::Arc;
use std::{fmt, mem, ptr};
use parking_lot::Mutex;
/// An auto generated key into the Lua registry.
///
/// This is a handle to a value stored inside the Lua registry. It is not automatically
/// garbage collected on Drop, but it can be removed with [`Lua::remove_registry_value`],
/// and instances not manually removed can be garbage collected with
/// [`Lua::expire_registry_values`].
///
/// Be warned, If you place this into Lua via a [`UserData`] type or a Rust callback, it is *easy*
/// to accidentally cause reference cycles that the Lua garbage collector cannot resolve. Instead of
/// placing a [`RegistryKey`] into a [`UserData`] type, consider to use
/// [`AnyUserData::set_user_value`].
///
/// [`UserData`]: crate::UserData
/// [`RegistryKey`]: crate::RegistryKey
/// [`Lua::remove_registry_value`]: crate::Lua::remove_registry_value
/// [`Lua::expire_registry_values`]: crate::Lua::expire_registry_values
/// [`AnyUserData::set_user_value`]: crate::AnyUserData::set_user_value
pub struct RegistryKey {
pub(crate) registry_id: i32,
pub(crate) unref_list: Arc<Mutex<Option<Vec<c_int>>>>,
}
impl fmt::Debug for RegistryKey {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "RegistryKey({})", self.id())
}
}
impl Hash for RegistryKey {
fn hash<H: Hasher>(&self, state: &mut H) {
self.id().hash(state)
}
}
impl PartialEq for RegistryKey {
fn eq(&self, other: &RegistryKey) -> bool {
self.id() == other.id() && Arc::ptr_eq(&self.unref_list, &other.unref_list)
}
}
impl Eq for RegistryKey {}
impl Drop for RegistryKey {
fn drop(&mut self) {
let registry_id = self.id();
// We don't need to collect nil slot
if registry_id > ffi::LUA_REFNIL {
let mut unref_list = self.unref_list.lock();
if let Some(list) = unref_list.as_mut() {
list.push(registry_id);
}
}
}
}
impl RegistryKey {
/// Creates a new instance of `RegistryKey`
pub(crate) const fn new(id: c_int, unref_list: Arc<Mutex<Option<Vec<c_int>>>>) -> Self {
RegistryKey {
registry_id: id,
unref_list,
}
}
/// Returns the underlying Lua reference of this `RegistryKey`
#[inline(always)]
pub fn id(&self) -> c_int {
self.registry_id
}
/// Sets the unique Lua reference key of this `RegistryKey`
#[inline(always)]
pub(crate) fn set_id(&mut self, id: c_int) {
self.registry_id = id;
}
/// Destroys the `RegistryKey` without adding to the unref list
pub(crate) fn take(self) -> i32 {
let registry_id = self.id();
unsafe {
ptr::read(&self.unref_list);
mem::forget(self);
}
registry_id
}
}
#[cfg(test)]
mod assertions {
use super::*;
static_assertions::assert_impl_all!(RegistryKey: Send, Sync);
}
+77
View File
@@ -0,0 +1,77 @@
#[cfg(feature = "send")]
mod inner {
use parking_lot::{RawMutex, RawThreadId};
use std::sync::{Arc, Weak};
pub(crate) type XRc<T> = Arc<T>;
pub(crate) type XWeak<T> = Weak<T>;
pub(crate) type ReentrantMutex<T> = parking_lot::ReentrantMutex<T>;
pub(crate) type ReentrantMutexGuard<'a, T> = parking_lot::ReentrantMutexGuard<'a, T>;
pub(crate) type ArcReentrantMutexGuard<T> =
parking_lot::lock_api::ArcReentrantMutexGuard<RawMutex, RawThreadId, T>;
}
#[cfg(not(feature = "send"))]
mod inner {
use std::ops::Deref;
use std::rc::{Rc, Weak};
pub(crate) type XRc<T> = Rc<T>;
pub(crate) type XWeak<T> = Weak<T>;
pub(crate) struct ReentrantMutex<T>(T);
impl<T> ReentrantMutex<T> {
#[inline(always)]
pub(crate) fn new(val: T) -> Self {
ReentrantMutex(val)
}
#[inline(always)]
pub(crate) fn lock(&self) -> ReentrantMutexGuard<'_, T> {
ReentrantMutexGuard(&self.0)
}
#[inline(always)]
pub(crate) fn lock_arc(self: &XRc<Self>) -> ArcReentrantMutexGuard<T> {
ArcReentrantMutexGuard(Rc::clone(self))
}
#[inline(always)]
pub(crate) fn into_lock_arc(self: XRc<Self>) -> ArcReentrantMutexGuard<T> {
ArcReentrantMutexGuard(self)
}
#[inline(always)]
pub(crate) fn data_ptr(&self) -> *const T {
&self.0 as *const _
}
}
pub(crate) struct ReentrantMutexGuard<'a, T>(&'a T);
impl<T> Deref for ReentrantMutexGuard<'_, T> {
type Target = T;
#[inline(always)]
fn deref(&self) -> &Self::Target {
self.0
}
}
pub(crate) struct ArcReentrantMutexGuard<T>(XRc<ReentrantMutex<T>>);
impl<T> Deref for ArcReentrantMutexGuard<T> {
type Target = T;
#[inline(always)]
fn deref(&self) -> &Self::Target {
&self.0 .0
}
}
}
pub(crate) use inner::{ArcReentrantMutexGuard, ReentrantMutex, ReentrantMutexGuard, XRc, XWeak};
+71
View File
@@ -0,0 +1,71 @@
use std::fmt;
use std::os::raw::{c_int, c_void};
use crate::state::{RawLua, WeakLua};
/// A reference to a Lua (complex) value stored in the Lua auxiliary thread.
pub struct ValueRef {
pub(crate) lua: WeakLua,
pub(crate) index: c_int,
pub(crate) drop: bool,
}
impl ValueRef {
#[inline]
pub(crate) fn new(lua: &RawLua, index: c_int) -> Self {
ValueRef {
lua: lua.weak().clone(),
index,
drop: true,
}
}
#[inline]
pub(crate) fn to_pointer(&self) -> *const c_void {
let lua = self.lua.lock();
unsafe { ffi::lua_topointer(lua.ref_thread(), self.index) }
}
/// Returns a copy of the value, which is valid as long as the original value is held.
#[inline]
pub(crate) fn copy(&self) -> Self {
ValueRef {
lua: self.lua.clone(),
index: self.index,
drop: false,
}
}
}
impl fmt::Debug for ValueRef {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Ref({:p})", self.to_pointer())
}
}
impl Clone for ValueRef {
fn clone(&self) -> Self {
unsafe { self.lua.lock().clone_ref(self) }
}
}
impl Drop for ValueRef {
fn drop(&mut self) {
if self.drop {
if let Some(lua) = self.lua.try_lock() {
unsafe { lua.drop_ref(self) };
}
}
}
}
impl PartialEq for ValueRef {
fn eq(&self, other: &Self) -> bool {
assert!(
self.lua == other.lua,
"Lua instance passed Value created from a different main Lua state"
);
let lua = self.lua.lock();
unsafe { ffi::lua_rawequal(lua.ref_thread(), self.index, other.index) == 1 }
}
}
+397 -725
View File
File diff suppressed because it is too large Load Diff
+298
View File
@@ -0,0 +1,298 @@
use std::cell::{RefCell, UnsafeCell};
#[cfg(feature = "serde")]
use serde::ser::{Serialize, Serializer};
use crate::error::{Error, Result};
use crate::types::XRc;
use super::lock::{RawLock, UserDataLock};
use super::r#ref::{UserDataRef, UserDataRefMut};
#[cfg(all(feature = "serde", not(feature = "send")))]
type DynSerialize = dyn erased_serde::Serialize;
#[cfg(all(feature = "serde", feature = "send"))]
type DynSerialize = dyn erased_serde::Serialize + Send;
pub(crate) enum UserDataStorage<T> {
Owned(UserDataVariant<T>),
Scoped(ScopedUserDataVariant<T>),
}
// A enum for storing userdata values.
// It's stored inside a Lua VM and protected by the outer `ReentrantMutex`.
pub(crate) enum UserDataVariant<T> {
Default(XRc<UserDataCell<T>>),
#[cfg(feature = "serde")]
Serializable(XRc<UserDataCell<Box<DynSerialize>>>, bool), // bool is `is_sync`
}
impl<T> Clone for UserDataVariant<T> {
#[inline]
fn clone(&self) -> Self {
match self {
Self::Default(inner) => Self::Default(XRc::clone(inner)),
#[cfg(feature = "serde")]
Self::Serializable(inner, is_sync) => Self::Serializable(XRc::clone(inner), *is_sync),
}
}
}
impl<T> UserDataVariant<T> {
#[inline(always)]
pub(super) fn try_borrow_scoped<R>(&self, f: impl FnOnce(&T) -> R) -> Result<R> {
// We don't need to check for `T: Sync` because when this method is used (internally),
// Lua mutex is already locked.
// If non-`Sync` userdata is already borrowed by another thread (via `UserDataRef`), it will be
// exclusively locked.
let _guard = (self.raw_lock().try_lock_shared_guarded()).map_err(|_| Error::UserDataBorrowError)?;
Ok(f(unsafe { &*self.as_ptr() }))
}
// Mutably borrows the wrapped value in-place.
#[inline(always)]
fn try_borrow_scoped_mut<R>(&self, f: impl FnOnce(&mut T) -> R) -> Result<R> {
let _guard =
(self.raw_lock().try_lock_exclusive_guarded()).map_err(|_| Error::UserDataBorrowMutError)?;
Ok(f(unsafe { &mut *self.as_ptr() }))
}
// Immutably borrows the wrapped value and returns an owned reference.
#[inline(always)]
fn try_borrow_owned(&self) -> Result<UserDataRef<T>> {
UserDataRef::try_from(self.clone())
}
// Mutably borrows the wrapped value and returns an owned reference.
#[inline(always)]
fn try_borrow_owned_mut(&self) -> Result<UserDataRefMut<T>> {
UserDataRefMut::try_from(self.clone())
}
// Returns the wrapped value.
//
// This method checks that we have exclusive access to the value.
fn into_inner(self) -> Result<T> {
if !self.raw_lock().try_lock_exclusive() {
return Err(Error::UserDataBorrowMutError);
}
Ok(match self {
Self::Default(inner) => XRc::into_inner(inner).unwrap().value.into_inner(),
#[cfg(feature = "serde")]
Self::Serializable(inner, _) => unsafe {
let raw = Box::into_raw(XRc::into_inner(inner).unwrap().value.into_inner());
*Box::from_raw(raw as *mut T)
},
})
}
#[inline(always)]
fn strong_count(&self) -> usize {
match self {
Self::Default(inner) => XRc::strong_count(inner),
#[cfg(feature = "serde")]
Self::Serializable(inner, _) => XRc::strong_count(inner),
}
}
#[inline(always)]
pub(super) fn raw_lock(&self) -> &RawLock {
match self {
Self::Default(inner) => &inner.raw_lock,
#[cfg(feature = "serde")]
Self::Serializable(inner, _) => &inner.raw_lock,
}
}
#[inline(always)]
pub(super) fn as_ptr(&self) -> *mut T {
match self {
Self::Default(inner) => inner.value.get(),
#[cfg(feature = "serde")]
Self::Serializable(inner, _) => unsafe { &mut **(inner.value.get() as *mut Box<T>) },
}
}
}
#[cfg(feature = "serde")]
impl Serialize for UserDataStorage<()> {
fn serialize<S: Serializer>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error> {
match self {
Self::Owned(variant @ UserDataVariant::Serializable(inner, is_sync)) => unsafe {
#[cfg(feature = "send")]
if *is_sync {
let _guard = (variant.raw_lock().try_lock_shared_guarded())
.map_err(|_| serde::ser::Error::custom(Error::UserDataBorrowError))?;
(*inner.value.get()).serialize(serializer)
} else {
let _guard = (variant.raw_lock().try_lock_exclusive_guarded())
.map_err(|_| serde::ser::Error::custom(Error::UserDataBorrowError))?;
(*inner.value.get()).serialize(serializer)
}
#[cfg(not(feature = "send"))]
{
let _ = is_sync;
let _guard = (variant.raw_lock().try_lock_shared_guarded())
.map_err(|_| serde::ser::Error::custom(Error::UserDataBorrowError))?;
(*inner.value.get()).serialize(serializer)
}
},
_ => Err(serde::ser::Error::custom("cannot serialize <userdata>")),
}
}
}
/// A type that provides interior mutability for a userdata value (thread-safe).
pub(crate) struct UserDataCell<T> {
raw_lock: RawLock,
value: UnsafeCell<T>,
}
#[cfg(feature = "send")]
unsafe impl<T: Send> Send for UserDataCell<T> {}
#[cfg(feature = "send")]
unsafe impl<T: Send> Sync for UserDataCell<T> {}
impl<T> UserDataCell<T> {
#[inline(always)]
fn new(value: T) -> Self {
UserDataCell {
raw_lock: RawLock::INIT,
value: UnsafeCell::new(value),
}
}
}
pub(crate) enum ScopedUserDataVariant<T> {
Ref(*const T),
RefMut(RefCell<*mut T>),
Boxed(RefCell<*mut T>),
}
impl<T> Drop for ScopedUserDataVariant<T> {
#[inline]
fn drop(&mut self) {
if let Self::Boxed(value) = self {
if let Ok(value) = value.try_borrow_mut() {
unsafe { drop(Box::from_raw(*value)) };
}
}
}
}
impl<T: 'static> UserDataStorage<T> {
#[inline(always)]
pub(crate) fn new(data: T) -> Self {
Self::Owned(UserDataVariant::Default(XRc::new(UserDataCell::new(data))))
}
#[inline(always)]
pub(crate) fn new_ref(data: &T) -> Self {
Self::Scoped(ScopedUserDataVariant::Ref(data))
}
#[inline(always)]
pub(crate) fn new_ref_mut(data: &mut T) -> Self {
Self::Scoped(ScopedUserDataVariant::RefMut(RefCell::new(data)))
}
#[cfg(feature = "serde")]
#[inline(always)]
pub(crate) fn new_ser(data: T) -> Self
where
T: Serialize + crate::types::MaybeSend,
{
let data = Box::new(data) as Box<DynSerialize>;
let is_sync = super::util::is_sync::<T>();
let variant = UserDataVariant::Serializable(XRc::new(UserDataCell::new(data)), is_sync);
Self::Owned(variant)
}
#[cfg(feature = "serde")]
#[inline(always)]
pub(crate) fn is_serializable(&self) -> bool {
matches!(self, Self::Owned(UserDataVariant::Serializable(..)))
}
// Immutably borrows the wrapped value and returns an owned reference.
#[inline(always)]
pub(crate) fn try_borrow_owned(&self) -> Result<UserDataRef<T>> {
match self {
Self::Owned(data) => data.try_borrow_owned(),
Self::Scoped(_) => Err(Error::UserDataTypeMismatch),
}
}
// Mutably borrows the wrapped value and returns an owned reference.
#[inline(always)]
pub(crate) fn try_borrow_owned_mut(&self) -> Result<UserDataRefMut<T>> {
match self {
Self::Owned(data) => data.try_borrow_owned_mut(),
Self::Scoped(_) => Err(Error::UserDataTypeMismatch),
}
}
#[inline(always)]
pub(crate) fn into_inner(self) -> Result<T> {
match self {
Self::Owned(data) => data.into_inner(),
Self::Scoped(_) => Err(Error::UserDataTypeMismatch),
}
}
}
impl<T> UserDataStorage<T> {
#[inline(always)]
pub(crate) fn new_scoped(data: T) -> Self {
let data = Box::into_raw(Box::new(data));
Self::Scoped(ScopedUserDataVariant::Boxed(RefCell::new(data)))
}
/// Returns `true` if it's safe to destroy the container.
///
/// It's safe to destroy the container if the reference count is greater than 1 or the lock is
/// not acquired.
#[inline(always)]
pub(crate) fn is_safe_to_destroy(&self) -> bool {
match self {
Self::Owned(variant) => variant.strong_count() > 1 || !variant.raw_lock().is_locked(),
Self::Scoped(_) => false,
}
}
/// Returns `true` if the container has exclusive access to the value.
#[inline(always)]
pub(crate) fn has_exclusive_access(&self) -> bool {
match self {
Self::Owned(variant) => !variant.raw_lock().is_locked(),
Self::Scoped(_) => false,
}
}
#[inline]
pub(crate) fn try_borrow_scoped<R>(&self, f: impl FnOnce(&T) -> R) -> Result<R> {
match self {
Self::Owned(data) => data.try_borrow_scoped(f),
Self::Scoped(ScopedUserDataVariant::Ref(value)) => Ok(f(unsafe { &**value })),
Self::Scoped(ScopedUserDataVariant::RefMut(value) | ScopedUserDataVariant::Boxed(value)) => {
let t = value.try_borrow().map_err(|_| Error::UserDataBorrowError)?;
Ok(f(unsafe { &**t }))
}
}
}
#[inline]
pub(crate) fn try_borrow_scoped_mut<R>(&self, f: impl FnOnce(&mut T) -> R) -> Result<R> {
match self {
Self::Owned(data) => data.try_borrow_scoped_mut(f),
Self::Scoped(ScopedUserDataVariant::Ref(_)) => Err(Error::UserDataBorrowMutError),
Self::Scoped(ScopedUserDataVariant::RefMut(value) | ScopedUserDataVariant::Boxed(value)) => {
let mut t = value
.try_borrow_mut()
.map_err(|_| Error::UserDataBorrowMutError)?;
Ok(f(unsafe { &mut **t }))
}
}
}
}
+144
View File
@@ -0,0 +1,144 @@
pub(crate) trait UserDataLock {
const INIT: Self;
fn is_locked(&self) -> bool;
fn try_lock_shared(&self) -> bool;
fn try_lock_exclusive(&self) -> bool;
unsafe fn unlock_shared(&self);
unsafe fn unlock_exclusive(&self);
fn try_lock_shared_guarded(&self) -> Result<LockGuard<'_, Self>, ()> {
if self.try_lock_shared() {
Ok(LockGuard {
lock: self,
exclusive: false,
})
} else {
Err(())
}
}
fn try_lock_exclusive_guarded(&self) -> Result<LockGuard<'_, Self>, ()> {
if self.try_lock_exclusive() {
Ok(LockGuard {
lock: self,
exclusive: true,
})
} else {
Err(())
}
}
}
pub(crate) struct LockGuard<'a, L: UserDataLock + ?Sized> {
lock: &'a L,
exclusive: bool,
}
impl<L: UserDataLock + ?Sized> Drop for LockGuard<'_, L> {
fn drop(&mut self) {
unsafe {
if self.exclusive {
self.lock.unlock_exclusive();
} else {
self.lock.unlock_shared();
}
}
}
}
pub(crate) use lock_impl::RawLock;
#[cfg(not(feature = "send"))]
#[cfg(not(tarpaulin_include))]
mod lock_impl {
use std::cell::Cell;
// Positive values represent the number of read references.
// Negative values represent the number of write references (only one allowed).
pub(crate) type RawLock = Cell<isize>;
const UNUSED: isize = 0;
impl super::UserDataLock for RawLock {
#[allow(clippy::declare_interior_mutable_const)]
const INIT: Self = Cell::new(UNUSED);
#[inline(always)]
fn is_locked(&self) -> bool {
self.get() != UNUSED
}
#[inline(always)]
fn try_lock_shared(&self) -> bool {
let flag = self.get().wrapping_add(1);
if flag <= UNUSED {
return false;
}
self.set(flag);
true
}
#[inline(always)]
fn try_lock_exclusive(&self) -> bool {
let flag = self.get();
if flag != UNUSED {
return false;
}
self.set(UNUSED - 1);
true
}
#[inline(always)]
unsafe fn unlock_shared(&self) {
let flag = self.get();
debug_assert!(flag > UNUSED);
self.set(flag - 1);
}
#[inline(always)]
unsafe fn unlock_exclusive(&self) {
let flag = self.get();
debug_assert!(flag < UNUSED);
self.set(flag + 1);
}
}
}
#[cfg(feature = "send")]
mod lock_impl {
use parking_lot::lock_api::RawRwLock;
pub(crate) type RawLock = parking_lot::RawRwLock;
impl super::UserDataLock for RawLock {
#[allow(clippy::declare_interior_mutable_const)]
const INIT: Self = <Self as parking_lot::lock_api::RawRwLock>::INIT;
#[inline(always)]
fn is_locked(&self) -> bool {
RawRwLock::is_locked(self)
}
#[inline(always)]
fn try_lock_shared(&self) -> bool {
RawRwLock::try_lock_shared(self)
}
#[inline(always)]
fn try_lock_exclusive(&self) -> bool {
RawRwLock::try_lock_exclusive(self)
}
#[inline(always)]
unsafe fn unlock_shared(&self) {
RawRwLock::unlock_shared(self)
}
#[inline(always)]
unsafe fn unlock_exclusive(&self) {
RawRwLock::unlock_exclusive(self)
}
}
}
+93
View File
@@ -0,0 +1,93 @@
use std::string::String as StdString;
use crate::error::{Error, Result};
use crate::table::Table;
use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, ObjectLike};
use crate::userdata::AnyUserData;
use crate::value::Value;
use crate::Function;
#[cfg(feature = "async")]
use crate::function::AsyncCallFuture;
impl ObjectLike for AnyUserData {
#[inline]
fn get<V: FromLua>(&self, key: impl IntoLua) -> Result<V> {
// `lua_gettable` method used under the hood can work with any Lua value
// that has `__index` metamethod
Table(self.0.copy()).get_protected(key)
}
#[inline]
fn set(&self, key: impl IntoLua, value: impl IntoLua) -> Result<()> {
// `lua_settable` method used under the hood can work with any Lua value
// that has `__newindex` metamethod
Table(self.0.copy()).set_protected(key, value)
}
#[inline]
fn call<R>(&self, args: impl IntoLuaMulti) -> Result<R>
where
R: FromLuaMulti,
{
Function(self.0.copy()).call(args)
}
#[cfg(feature = "async")]
#[inline]
fn call_async<R>(&self, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
where
R: FromLuaMulti,
{
Function(self.0.copy()).call_async(args)
}
#[inline]
fn call_method<R>(&self, name: &str, args: impl IntoLuaMulti) -> Result<R>
where
R: FromLuaMulti,
{
self.call_function(name, (self, args))
}
#[cfg(feature = "async")]
fn call_async_method<R>(&self, name: &str, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
where
R: FromLuaMulti,
{
self.call_async_function(name, (self, args))
}
fn call_function<R>(&self, name: &str, args: impl IntoLuaMulti) -> Result<R>
where
R: FromLuaMulti,
{
match self.get(name)? {
Value::Function(func) => func.call(args),
val => {
let msg = format!("attempt to call a {} value (function '{name}')", val.type_name());
Err(Error::RuntimeError(msg))
}
}
}
#[cfg(feature = "async")]
fn call_async_function<R>(&self, name: &str, args: impl IntoLuaMulti) -> AsyncCallFuture<R>
where
R: FromLuaMulti,
{
match self.get(name) {
Ok(Value::Function(func)) => func.call_async(args),
Ok(val) => {
let msg = format!("attempt to call a {} value (function '{name}')", val.type_name());
AsyncCallFuture::error(Error::RuntimeError(msg))
}
Err(err) => AsyncCallFuture::error(err),
}
}
#[inline]
fn to_string(&self) -> Result<StdString> {
Value::UserData(AnyUserData(self.0.copy())).to_string()
}
}
+474
View File
@@ -0,0 +1,474 @@
use std::any::{type_name, TypeId};
use std::ops::{Deref, DerefMut};
use std::os::raw::c_int;
use std::{fmt, mem};
use crate::error::{Error, Result};
use crate::state::{Lua, RawLua};
use crate::traits::FromLua;
use crate::userdata::AnyUserData;
use crate::util::get_userdata;
use crate::value::Value;
use super::cell::{UserDataStorage, UserDataVariant};
use super::lock::{LockGuard, RawLock, UserDataLock};
use super::util::is_sync;
#[cfg(feature = "userdata-wrappers")]
use {
parking_lot::{
Mutex as MutexPL, MutexGuard as MutexGuardPL, RwLock as RwLockPL,
RwLockReadGuard as RwLockReadGuardPL, RwLockWriteGuard as RwLockWriteGuardPL,
},
std::sync::Arc,
};
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
use {
std::cell::{Ref, RefCell, RefMut},
std::rc::Rc,
};
/// A wrapper type for a userdata value that provides read access.
///
/// It implements [`FromLua`] and can be used to receive a typed userdata from Lua.
pub struct UserDataRef<T: 'static> {
// It's important to drop the guard first, as it refers to the `inner` data.
_guard: LockGuard<'static, RawLock>,
inner: UserDataRefInner<T>,
}
impl<T> Deref for UserDataRef<T> {
type Target = T;
#[inline]
fn deref(&self) -> &T {
&self.inner
}
}
impl<T: fmt::Debug> fmt::Debug for UserDataRef<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
impl<T: fmt::Display> fmt::Display for UserDataRef<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
impl<T> TryFrom<UserDataVariant<T>> for UserDataRef<T> {
type Error = Error;
#[inline]
fn try_from(variant: UserDataVariant<T>) -> Result<Self> {
let guard = if cfg!(not(feature = "send")) || is_sync::<T>() {
variant.raw_lock().try_lock_shared_guarded()
} else {
variant.raw_lock().try_lock_exclusive_guarded()
};
let guard = guard.map_err(|_| Error::UserDataBorrowError)?;
let guard = unsafe { mem::transmute::<LockGuard<_>, LockGuard<'static, _>>(guard) };
Ok(UserDataRef::from_parts(UserDataRefInner::Default(variant), guard))
}
}
impl<T: 'static> FromLua for UserDataRef<T> {
fn from_lua(value: Value, _: &Lua) -> Result<Self> {
try_value_to_userdata::<T>(value)?.borrow()
}
#[inline]
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
Self::borrow_from_stack(lua, lua.state(), idx)
}
}
impl<T: 'static> UserDataRef<T> {
#[inline(always)]
fn from_parts(inner: UserDataRefInner<T>, guard: LockGuard<'static, RawLock>) -> Self {
Self { _guard: guard, inner }
}
#[cfg(feature = "userdata-wrappers")]
fn remap<U>(
self,
f: impl FnOnce(UserDataVariant<T>) -> Result<UserDataRefInner<U>>,
) -> Result<UserDataRef<U>> {
match &self.inner {
UserDataRefInner::Default(variant) => {
let inner = f(variant.clone())?;
Ok(UserDataRef::from_parts(inner, self._guard))
}
_ => Err(Error::UserDataTypeMismatch),
}
}
pub(crate) unsafe fn borrow_from_stack(
lua: &RawLua,
state: *mut ffi::lua_State,
idx: c_int,
) -> Result<Self> {
let type_id = lua.get_userdata_type_id::<T>(state, idx)?;
match type_id {
Some(type_id) if type_id == TypeId::of::<T>() => {
let ud = get_userdata::<UserDataStorage<T>>(state, idx);
(*ud).try_borrow_owned()
}
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
Some(type_id) if type_id == TypeId::of::<Rc<T>>() => {
let ud = get_userdata::<UserDataStorage<Rc<T>>>(state, idx);
((*ud).try_borrow_owned()).and_then(|ud| ud.transform_rc())
}
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
Some(type_id) if type_id == TypeId::of::<Rc<RefCell<T>>>() => {
let ud = get_userdata::<UserDataStorage<Rc<RefCell<T>>>>(state, idx);
((*ud).try_borrow_owned()).and_then(|ud| ud.transform_rc_refcell())
}
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == TypeId::of::<Arc<T>>() => {
let ud = get_userdata::<UserDataStorage<Arc<T>>>(state, idx);
((*ud).try_borrow_owned()).and_then(|ud| ud.transform_arc())
}
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == TypeId::of::<Arc<MutexPL<T>>>() => {
let ud = get_userdata::<UserDataStorage<Arc<MutexPL<T>>>>(state, idx);
((*ud).try_borrow_owned()).and_then(|ud| ud.transform_arc_mutex_pl())
}
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == TypeId::of::<Arc<RwLockPL<T>>>() => {
let ud = get_userdata::<UserDataStorage<Arc<RwLockPL<T>>>>(state, idx);
((*ud).try_borrow_owned()).and_then(|ud| ud.transform_arc_rwlock_pl())
}
_ => Err(Error::UserDataTypeMismatch),
}
}
}
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
impl<T> UserDataRef<Rc<T>> {
fn transform_rc(self) -> Result<UserDataRef<T>> {
self.remap(|variant| Ok(UserDataRefInner::Rc(variant)))
}
}
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
impl<T> UserDataRef<Rc<RefCell<T>>> {
fn transform_rc_refcell(self) -> Result<UserDataRef<T>> {
self.remap(|variant| unsafe {
let obj = &*variant.as_ptr();
let r#ref = obj.try_borrow().map_err(|_| Error::UserDataBorrowError)?;
let borrow = std::mem::transmute::<Ref<T>, Ref<'static, T>>(r#ref);
Ok(UserDataRefInner::RcRefCell(borrow, variant))
})
}
}
#[cfg(feature = "userdata-wrappers")]
impl<T> UserDataRef<Arc<T>> {
fn transform_arc(self) -> Result<UserDataRef<T>> {
self.remap(|variant| Ok(UserDataRefInner::Arc(variant)))
}
}
#[cfg(feature = "userdata-wrappers")]
impl<T> UserDataRef<Arc<MutexPL<T>>> {
fn transform_arc_mutex_pl(self) -> Result<UserDataRef<T>> {
self.remap(|variant| unsafe {
let obj = &*variant.as_ptr();
let guard = obj.try_lock().ok_or(Error::UserDataBorrowError)?;
let borrow = std::mem::transmute::<MutexGuardPL<T>, MutexGuardPL<'static, T>>(guard);
Ok(UserDataRefInner::ArcMutexPL(borrow, variant))
})
}
}
#[cfg(feature = "userdata-wrappers")]
impl<T> UserDataRef<Arc<RwLockPL<T>>> {
fn transform_arc_rwlock_pl(self) -> Result<UserDataRef<T>> {
self.remap(|variant| unsafe {
let obj = &*variant.as_ptr();
let guard = obj.try_read().ok_or(Error::UserDataBorrowError)?;
let borrow = std::mem::transmute::<RwLockReadGuardPL<T>, RwLockReadGuardPL<'static, T>>(guard);
Ok(UserDataRefInner::ArcRwLockPL(borrow, variant))
})
}
}
#[allow(unused)]
enum UserDataRefInner<T: 'static> {
Default(UserDataVariant<T>),
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
Rc(UserDataVariant<Rc<T>>),
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
RcRefCell(Ref<'static, T>, UserDataVariant<Rc<RefCell<T>>>),
#[cfg(feature = "userdata-wrappers")]
Arc(UserDataVariant<Arc<T>>),
#[cfg(feature = "userdata-wrappers")]
ArcMutexPL(MutexGuardPL<'static, T>, UserDataVariant<Arc<MutexPL<T>>>),
#[cfg(feature = "userdata-wrappers")]
ArcRwLockPL(RwLockReadGuardPL<'static, T>, UserDataVariant<Arc<RwLockPL<T>>>),
}
impl<T> Deref for UserDataRefInner<T> {
type Target = T;
#[inline]
fn deref(&self) -> &T {
match self {
Self::Default(inner) => unsafe { &*inner.as_ptr() },
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
Self::Rc(inner) => unsafe { &*Rc::as_ptr(&*inner.as_ptr()) },
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
Self::RcRefCell(x, ..) => x,
#[cfg(feature = "userdata-wrappers")]
Self::Arc(inner) => unsafe { &*Arc::as_ptr(&*inner.as_ptr()) },
#[cfg(feature = "userdata-wrappers")]
Self::ArcMutexPL(x, ..) => x,
#[cfg(feature = "userdata-wrappers")]
Self::ArcRwLockPL(x, ..) => x,
}
}
}
/// A wrapper type for a userdata value that provides read and write access.
///
/// It implements [`FromLua`] and can be used to receive a typed userdata from Lua.
pub struct UserDataRefMut<T: 'static> {
// It's important to drop the guard first, as it refers to the `inner` data.
_guard: LockGuard<'static, RawLock>,
inner: UserDataRefMutInner<T>,
}
impl<T> Deref for UserDataRefMut<T> {
type Target = T;
#[inline]
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<T> DerefMut for UserDataRefMut<T> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
impl<T: fmt::Debug> fmt::Debug for UserDataRefMut<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
impl<T: fmt::Display> fmt::Display for UserDataRefMut<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
impl<T> TryFrom<UserDataVariant<T>> for UserDataRefMut<T> {
type Error = Error;
#[inline]
fn try_from(variant: UserDataVariant<T>) -> Result<Self> {
let guard = variant.raw_lock().try_lock_exclusive_guarded();
let guard = guard.map_err(|_| Error::UserDataBorrowMutError)?;
let guard = unsafe { mem::transmute::<LockGuard<_>, LockGuard<'static, _>>(guard) };
Ok(UserDataRefMut::from_parts(
UserDataRefMutInner::Default(variant),
guard,
))
}
}
impl<T: 'static> FromLua for UserDataRefMut<T> {
fn from_lua(value: Value, _: &Lua) -> Result<Self> {
try_value_to_userdata::<T>(value)?.borrow_mut()
}
unsafe fn from_stack(idx: c_int, lua: &RawLua) -> Result<Self> {
Self::borrow_from_stack(lua, lua.state(), idx)
}
}
impl<T: 'static> UserDataRefMut<T> {
#[inline(always)]
fn from_parts(inner: UserDataRefMutInner<T>, guard: LockGuard<'static, RawLock>) -> Self {
Self { _guard: guard, inner }
}
#[cfg(feature = "userdata-wrappers")]
fn remap<U>(
self,
f: impl FnOnce(UserDataVariant<T>) -> Result<UserDataRefMutInner<U>>,
) -> Result<UserDataRefMut<U>> {
match &self.inner {
UserDataRefMutInner::Default(variant) => {
let inner = f(variant.clone())?;
Ok(UserDataRefMut::from_parts(inner, self._guard))
}
_ => Err(Error::UserDataTypeMismatch),
}
}
pub(crate) unsafe fn borrow_from_stack(
lua: &RawLua,
state: *mut ffi::lua_State,
idx: c_int,
) -> Result<Self> {
let type_id = lua.get_userdata_type_id::<T>(state, idx)?;
match type_id {
Some(type_id) if type_id == TypeId::of::<T>() => {
let ud = get_userdata::<UserDataStorage<T>>(state, idx);
(*ud).try_borrow_owned_mut()
}
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
Some(type_id) if type_id == TypeId::of::<Rc<T>>() => Err(Error::UserDataBorrowMutError),
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
Some(type_id) if type_id == TypeId::of::<Rc<RefCell<T>>>() => {
let ud = get_userdata::<UserDataStorage<Rc<RefCell<T>>>>(state, idx);
((*ud).try_borrow_owned_mut()).and_then(|ud| ud.transform_rc_refcell())
}
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == TypeId::of::<Arc<T>>() => Err(Error::UserDataBorrowMutError),
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == TypeId::of::<Arc<MutexPL<T>>>() => {
let ud = get_userdata::<UserDataStorage<Arc<MutexPL<T>>>>(state, idx);
((*ud).try_borrow_owned_mut()).and_then(|ud| ud.transform_arc_mutex_pl())
}
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == TypeId::of::<Arc<RwLockPL<T>>>() => {
let ud = get_userdata::<UserDataStorage<Arc<RwLockPL<T>>>>(state, idx);
((*ud).try_borrow_owned_mut()).and_then(|ud| ud.transform_arc_rwlock_pl())
}
_ => Err(Error::UserDataTypeMismatch),
}
}
}
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
impl<T> UserDataRefMut<Rc<RefCell<T>>> {
fn transform_rc_refcell(self) -> Result<UserDataRefMut<T>> {
self.remap(|variant| unsafe {
let obj = &*variant.as_ptr();
let refmut = obj.try_borrow_mut().map_err(|_| Error::UserDataBorrowMutError)?;
let borrow = std::mem::transmute::<RefMut<T>, RefMut<'static, T>>(refmut);
Ok(UserDataRefMutInner::RcRefCell(borrow, variant))
})
}
}
#[cfg(feature = "userdata-wrappers")]
impl<T> UserDataRefMut<Arc<MutexPL<T>>> {
fn transform_arc_mutex_pl(self) -> Result<UserDataRefMut<T>> {
self.remap(|variant| unsafe {
let obj = &*variant.as_ptr();
let guard = obj.try_lock().ok_or(Error::UserDataBorrowMutError)?;
let borrow = std::mem::transmute::<MutexGuardPL<T>, MutexGuardPL<'static, T>>(guard);
Ok(UserDataRefMutInner::ArcMutexPL(borrow, variant))
})
}
}
#[cfg(feature = "userdata-wrappers")]
impl<T> UserDataRefMut<Arc<RwLockPL<T>>> {
fn transform_arc_rwlock_pl(self) -> Result<UserDataRefMut<T>> {
self.remap(|variant| unsafe {
let obj = &*variant.as_ptr();
let guard = obj.try_write().ok_or(Error::UserDataBorrowMutError)?;
let borrow = std::mem::transmute::<RwLockWriteGuardPL<T>, RwLockWriteGuardPL<'static, T>>(guard);
Ok(UserDataRefMutInner::ArcRwLockPL(borrow, variant))
})
}
}
#[allow(unused)]
enum UserDataRefMutInner<T: 'static> {
Default(UserDataVariant<T>),
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
RcRefCell(RefMut<'static, T>, UserDataVariant<Rc<RefCell<T>>>),
#[cfg(feature = "userdata-wrappers")]
ArcMutexPL(MutexGuardPL<'static, T>, UserDataVariant<Arc<MutexPL<T>>>),
#[cfg(feature = "userdata-wrappers")]
ArcRwLockPL(RwLockWriteGuardPL<'static, T>, UserDataVariant<Arc<RwLockPL<T>>>),
}
impl<T> Deref for UserDataRefMutInner<T> {
type Target = T;
#[inline]
fn deref(&self) -> &T {
match self {
Self::Default(inner) => unsafe { &*inner.as_ptr() },
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
Self::RcRefCell(x, ..) => x,
#[cfg(feature = "userdata-wrappers")]
Self::ArcMutexPL(x, ..) => x,
#[cfg(feature = "userdata-wrappers")]
Self::ArcRwLockPL(x, ..) => x,
}
}
}
impl<T> DerefMut for UserDataRefMutInner<T> {
#[inline]
fn deref_mut(&mut self) -> &mut T {
match self {
Self::Default(inner) => unsafe { &mut *inner.as_ptr() },
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
Self::RcRefCell(x, ..) => x,
#[cfg(feature = "userdata-wrappers")]
Self::ArcMutexPL(x, ..) => x,
#[cfg(feature = "userdata-wrappers")]
Self::ArcRwLockPL(x, ..) => x,
}
}
}
#[inline]
fn try_value_to_userdata<T>(value: Value) -> Result<AnyUserData> {
match value {
Value::UserData(ud) => Ok(ud),
_ => Err(Error::FromLuaConversionError {
from: value.type_name(),
to: "userdata".to_string(),
message: Some(format!("expected userdata of type {}", type_name::<T>())),
}),
}
}
#[cfg(test)]
mod assertions {
use super::*;
#[cfg(feature = "send")]
static_assertions::assert_impl_all!(UserDataRef<()>: Send, Sync);
#[cfg(feature = "send")]
static_assertions::assert_not_impl_all!(UserDataRef<std::rc::Rc<()>>: Send, Sync);
#[cfg(feature = "send")]
static_assertions::assert_impl_all!(UserDataRefMut<()>: Sync, Send);
#[cfg(feature = "send")]
static_assertions::assert_not_impl_all!(UserDataRefMut<std::rc::Rc<()>>: Send, Sync);
#[cfg(not(feature = "send"))]
static_assertions::assert_not_impl_all!(UserDataRef<()>: Send, Sync);
#[cfg(not(feature = "send"))]
static_assertions::assert_not_impl_all!(UserDataRefMut<()>: Send, Sync);
}
+657
View File
@@ -0,0 +1,657 @@
#![allow(clippy::await_holding_refcell_ref, clippy::await_holding_lock)]
use std::any::TypeId;
use std::cell::RefCell;
use std::marker::PhantomData;
use std::os::raw::c_void;
use std::string::String as StdString;
use crate::error::{Error, Result};
use crate::state::{Lua, LuaGuard};
use crate::traits::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti};
use crate::types::{Callback, MaybeSend};
use crate::userdata::{
borrow_userdata_scoped, borrow_userdata_scoped_mut, AnyUserData, MetaMethod, TypeIdHints, UserData,
UserDataFields, UserDataMethods, UserDataStorage,
};
use crate::util::short_type_name;
use crate::value::Value;
#[cfg(feature = "async")]
use {
crate::types::AsyncCallback,
crate::userdata::{UserDataRef, UserDataRefMut},
std::future::{self, Future},
};
#[derive(Clone, Copy)]
enum UserDataType {
Shared(TypeIdHints),
Unique(*mut c_void),
}
/// Handle to registry for userdata methods and metamethods.
pub struct UserDataRegistry<T> {
lua: LuaGuard,
raw: RawUserDataRegistry,
r#type: UserDataType,
_phantom: PhantomData<T>,
}
pub(crate) struct RawUserDataRegistry {
// Fields
pub(crate) fields: Vec<(String, Result<Value>)>,
pub(crate) field_getters: Vec<(String, Callback)>,
pub(crate) field_setters: Vec<(String, Callback)>,
pub(crate) meta_fields: Vec<(String, Result<Value>)>,
// Methods
pub(crate) methods: Vec<(String, Callback)>,
#[cfg(feature = "async")]
pub(crate) async_methods: Vec<(String, AsyncCallback)>,
pub(crate) meta_methods: Vec<(String, Callback)>,
#[cfg(feature = "async")]
pub(crate) async_meta_methods: Vec<(String, AsyncCallback)>,
pub(crate) destructor: ffi::lua_CFunction,
pub(crate) type_id: Option<TypeId>,
pub(crate) type_name: StdString,
}
impl UserDataType {
#[inline]
pub(crate) fn type_id(&self) -> Option<TypeId> {
match self {
UserDataType::Shared(hints) => Some(hints.type_id()),
UserDataType::Unique(_) => None,
}
}
}
#[cfg(feature = "send")]
unsafe impl Send for UserDataType {}
impl<T: 'static> UserDataRegistry<T> {
#[inline(always)]
pub(crate) fn new(lua: &Lua) -> Self {
Self::with_type(lua, UserDataType::Shared(TypeIdHints::new::<T>()))
}
}
impl<T> UserDataRegistry<T> {
#[inline(always)]
pub(crate) fn new_unique(lua: &Lua, ud_ptr: *mut c_void) -> Self {
Self::with_type(lua, UserDataType::Unique(ud_ptr))
}
#[inline(always)]
fn with_type(lua: &Lua, r#type: UserDataType) -> Self {
let raw = RawUserDataRegistry {
fields: Vec::new(),
field_getters: Vec::new(),
field_setters: Vec::new(),
meta_fields: Vec::new(),
methods: Vec::new(),
#[cfg(feature = "async")]
async_methods: Vec::new(),
meta_methods: Vec::new(),
#[cfg(feature = "async")]
async_meta_methods: Vec::new(),
destructor: super::util::destroy_userdata_storage::<T>,
type_id: r#type.type_id(),
type_name: short_type_name::<T>(),
};
UserDataRegistry {
lua: lua.lock_arc(),
raw,
r#type,
_phantom: PhantomData,
}
}
fn box_method<M, A, R>(&self, name: &str, method: M) -> Callback
where
M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
{
let name = get_function_name::<T>(name);
macro_rules! try_self_arg {
($res:expr) => {
$res.map_err(|err| Error::bad_self_argument(&name, err))?
};
}
let target_type = self.r#type;
Box::new(move |rawlua, nargs| unsafe {
if nargs == 0 {
let err = Error::from_lua_conversion("missing argument", "userdata", None);
try_self_arg!(Err(err));
}
let state = rawlua.state();
// Find absolute "self" index before processing args
let self_index = ffi::lua_absindex(state, -nargs);
// Self was at position 1, so we pass 2 here
let args = A::from_stack_args(nargs - 1, 2, Some(&name), rawlua);
match target_type {
#[rustfmt::skip]
UserDataType::Shared(type_hints) => {
let type_id = try_self_arg!(rawlua.get_userdata_type_id::<T>(state, self_index));
try_self_arg!(borrow_userdata_scoped(state, self_index, type_id, type_hints, |ud| {
method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua)
}))
}
UserDataType::Unique(target_ptr) if ffi::lua_touserdata(state, self_index) == target_ptr => {
let ud = target_ptr as *mut UserDataStorage<T>;
try_self_arg!((*ud).try_borrow_scoped(|ud| {
method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua)
}))
}
UserDataType::Unique(_) => {
try_self_arg!(rawlua.get_userdata_type_id::<T>(state, self_index));
Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch))
}
}
})
}
fn box_method_mut<M, A, R>(&self, name: &str, method: M) -> Callback
where
M: FnMut(&Lua, &mut T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
{
let name = get_function_name::<T>(name);
macro_rules! try_self_arg {
($res:expr) => {
$res.map_err(|err| Error::bad_self_argument(&name, err))?
};
}
let method = RefCell::new(method);
let target_type = self.r#type;
Box::new(move |rawlua, nargs| unsafe {
let mut method = method.try_borrow_mut().map_err(|_| Error::RecursiveMutCallback)?;
if nargs == 0 {
let err = Error::from_lua_conversion("missing argument", "userdata", None);
try_self_arg!(Err(err));
}
let state = rawlua.state();
// Find absolute "self" index before processing args
let self_index = ffi::lua_absindex(state, -nargs);
// Self was at position 1, so we pass 2 here
let args = A::from_stack_args(nargs - 1, 2, Some(&name), rawlua);
match target_type {
#[rustfmt::skip]
UserDataType::Shared(type_hints) => {
let type_id = try_self_arg!(rawlua.get_userdata_type_id::<T>(state, self_index));
try_self_arg!(borrow_userdata_scoped_mut(state, self_index, type_id, type_hints, |ud| {
method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua)
}))
}
UserDataType::Unique(target_ptr) if ffi::lua_touserdata(state, self_index) == target_ptr => {
let ud = target_ptr as *mut UserDataStorage<T>;
try_self_arg!((*ud).try_borrow_scoped_mut(|ud| {
method(rawlua.lua(), ud, args?)?.push_into_stack_multi(rawlua)
}))
}
UserDataType::Unique(_) => {
try_self_arg!(rawlua.get_userdata_type_id::<T>(state, self_index));
Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch))
}
}
})
}
#[cfg(feature = "async")]
fn box_async_method<M, A, MR, R>(&self, name: &str, method: M) -> AsyncCallback
where
T: 'static,
M: Fn(Lua, UserDataRef<T>, A) -> MR + MaybeSend + 'static,
A: FromLuaMulti,
MR: Future<Output = Result<R>> + MaybeSend + 'static,
R: IntoLuaMulti,
{
let name = get_function_name::<T>(name);
macro_rules! try_self_arg {
($res:expr) => {
match $res {
Ok(res) => res,
Err(err) => return Box::pin(future::ready(Err(Error::bad_self_argument(&name, err)))),
}
};
}
Box::new(move |rawlua, nargs| unsafe {
if nargs == 0 {
let err = Error::from_lua_conversion("missing argument", "userdata", None);
try_self_arg!(Err(err));
}
// Stack will be empty when polling the future, keep `self` on the ref thread
let self_ud = try_self_arg!(AnyUserData::from_stack(-nargs, rawlua));
let args = A::from_stack_args(nargs - 1, 2, Some(&name), rawlua);
let self_ud = try_self_arg!(self_ud.borrow());
let args = match args {
Ok(args) => args,
Err(e) => return Box::pin(future::ready(Err(e))),
};
let lua = rawlua.lua();
let fut = method(lua.clone(), self_ud, args);
// Lua is locked when the future is polled
Box::pin(async move { fut.await?.push_into_stack_multi(lua.raw_lua()) })
})
}
#[cfg(feature = "async")]
fn box_async_method_mut<M, A, MR, R>(&self, name: &str, method: M) -> AsyncCallback
where
T: 'static,
M: Fn(Lua, UserDataRefMut<T>, A) -> MR + MaybeSend + 'static,
A: FromLuaMulti,
MR: Future<Output = Result<R>> + MaybeSend + 'static,
R: IntoLuaMulti,
{
let name = get_function_name::<T>(name);
macro_rules! try_self_arg {
($res:expr) => {
match $res {
Ok(res) => res,
Err(err) => return Box::pin(future::ready(Err(Error::bad_self_argument(&name, err)))),
}
};
}
Box::new(move |rawlua, nargs| unsafe {
if nargs == 0 {
let err = Error::from_lua_conversion("missing argument", "userdata", None);
try_self_arg!(Err(err));
}
// Stack will be empty when polling the future, keep `self` on the ref thread
let self_ud = try_self_arg!(AnyUserData::from_stack(-nargs, rawlua));
let args = A::from_stack_args(nargs - 1, 2, Some(&name), rawlua);
let self_ud = try_self_arg!(self_ud.borrow_mut());
let args = match args {
Ok(args) => args,
Err(e) => return Box::pin(future::ready(Err(e))),
};
let lua = rawlua.lua();
let fut = method(lua.clone(), self_ud, args);
// Lua is locked when the future is polled
Box::pin(async move { fut.await?.push_into_stack_multi(lua.raw_lua()) })
})
}
fn box_function<F, A, R>(&self, name: &str, function: F) -> Callback
where
F: Fn(&Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
{
let name = get_function_name::<T>(name);
Box::new(move |lua, nargs| unsafe {
let args = A::from_stack_args(nargs, 1, Some(&name), lua)?;
function(lua.lua(), args)?.push_into_stack_multi(lua)
})
}
fn box_function_mut<F, A, R>(&self, name: &str, function: F) -> Callback
where
F: FnMut(&Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
{
let name = get_function_name::<T>(name);
let function = RefCell::new(function);
Box::new(move |lua, nargs| unsafe {
let function = &mut *function
.try_borrow_mut()
.map_err(|_| Error::RecursiveMutCallback)?;
let args = A::from_stack_args(nargs, 1, Some(&name), lua)?;
function(lua.lua(), args)?.push_into_stack_multi(lua)
})
}
#[cfg(feature = "async")]
fn box_async_function<F, A, FR, R>(&self, name: &str, function: F) -> AsyncCallback
where
F: Fn(Lua, A) -> FR + MaybeSend + 'static,
A: FromLuaMulti,
FR: Future<Output = Result<R>> + MaybeSend + 'static,
R: IntoLuaMulti,
{
let name = get_function_name::<T>(name);
Box::new(move |rawlua, nargs| unsafe {
let args = match A::from_stack_args(nargs, 1, Some(&name), rawlua) {
Ok(args) => args,
Err(e) => return Box::pin(future::ready(Err(e))),
};
let lua = rawlua.lua();
let fut = function(lua.clone(), args);
Box::pin(async move { fut.await?.push_into_stack_multi(lua.raw_lua()) })
})
}
pub(crate) fn check_meta_field(lua: &Lua, name: &str, value: impl IntoLua) -> Result<Value> {
let value = value.into_lua(lua)?;
if name == MetaMethod::Index || name == MetaMethod::NewIndex {
match value {
Value::Nil | Value::Table(_) | Value::Function(_) => {}
_ => {
return Err(Error::MetaMethodTypeError {
method: name.to_string(),
type_name: value.type_name(),
message: Some("expected nil, table or function".to_string()),
})
}
}
}
value.into_lua(lua)
}
#[inline(always)]
pub(crate) fn into_raw(self) -> RawUserDataRegistry {
self.raw
}
}
// Returns function name for the type `T`, without the module path
fn get_function_name<T>(name: &str) -> StdString {
format!("{}.{name}", short_type_name::<T>())
}
impl<T> UserDataFields<T> for UserDataRegistry<T> {
fn add_field<V>(&mut self, name: impl Into<StdString>, value: V)
where
V: IntoLua + 'static,
{
let name = name.into();
self.raw.fields.push((name, value.into_lua(self.lua.lua())));
}
fn add_field_method_get<M, R>(&mut self, name: impl Into<StdString>, method: M)
where
M: Fn(&Lua, &T) -> Result<R> + MaybeSend + 'static,
R: IntoLua,
{
let name = name.into();
let callback = self.box_method(&name, move |lua, data, ()| method(lua, data));
self.raw.field_getters.push((name, callback));
}
fn add_field_method_set<M, A>(&mut self, name: impl Into<StdString>, method: M)
where
M: FnMut(&Lua, &mut T, A) -> Result<()> + MaybeSend + 'static,
A: FromLua,
{
let name = name.into();
let callback = self.box_method_mut(&name, method);
self.raw.field_setters.push((name, callback));
}
fn add_field_function_get<F, R>(&mut self, name: impl Into<StdString>, function: F)
where
F: Fn(&Lua, AnyUserData) -> Result<R> + MaybeSend + 'static,
R: IntoLua,
{
let name = name.into();
let callback = self.box_function(&name, function);
self.raw.field_getters.push((name, callback));
}
fn add_field_function_set<F, A>(&mut self, name: impl Into<StdString>, mut function: F)
where
F: FnMut(&Lua, AnyUserData, A) -> Result<()> + MaybeSend + 'static,
A: FromLua,
{
let name = name.into();
let callback = self.box_function_mut(&name, move |lua, (data, val)| function(lua, data, val));
self.raw.field_setters.push((name, callback));
}
fn add_meta_field<V>(&mut self, name: impl Into<StdString>, value: V)
where
V: IntoLua + 'static,
{
let lua = self.lua.lua();
let name = name.into();
let field = Self::check_meta_field(lua, &name, value).and_then(|v| v.into_lua(lua));
self.raw.meta_fields.push((name, field));
}
fn add_meta_field_with<F, R>(&mut self, name: impl Into<StdString>, f: F)
where
F: FnOnce(&Lua) -> Result<R> + 'static,
R: IntoLua,
{
let lua = self.lua.lua();
let name = name.into();
let field = f(lua).and_then(|v| Self::check_meta_field(lua, &name, v).and_then(|v| v.into_lua(lua)));
self.raw.meta_fields.push((name, field));
}
}
impl<T> UserDataMethods<T> for UserDataRegistry<T> {
fn add_method<M, A, R>(&mut self, name: impl Into<StdString>, method: M)
where
M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
{
let name = name.into();
let callback = self.box_method(&name, method);
self.raw.methods.push((name, callback));
}
fn add_method_mut<M, A, R>(&mut self, name: impl Into<StdString>, method: M)
where
M: FnMut(&Lua, &mut T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
{
let name = name.into();
let callback = self.box_method_mut(&name, method);
self.raw.methods.push((name, callback));
}
#[cfg(feature = "async")]
fn add_async_method<M, A, MR, R>(&mut self, name: impl Into<StdString>, method: M)
where
T: 'static,
M: Fn(Lua, UserDataRef<T>, A) -> MR + MaybeSend + 'static,
A: FromLuaMulti,
MR: Future<Output = Result<R>> + MaybeSend + 'static,
R: IntoLuaMulti,
{
let name = name.into();
let callback = self.box_async_method(&name, method);
self.raw.async_methods.push((name, callback));
}
#[cfg(feature = "async")]
fn add_async_method_mut<M, A, MR, R>(&mut self, name: impl Into<StdString>, method: M)
where
T: 'static,
M: Fn(Lua, UserDataRefMut<T>, A) -> MR + MaybeSend + 'static,
A: FromLuaMulti,
MR: Future<Output = Result<R>> + MaybeSend + 'static,
R: IntoLuaMulti,
{
let name = name.into();
let callback = self.box_async_method_mut(&name, method);
self.raw.async_methods.push((name, callback));
}
fn add_function<F, A, R>(&mut self, name: impl Into<StdString>, function: F)
where
F: Fn(&Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
{
let name = name.into();
let callback = self.box_function(&name, function);
self.raw.methods.push((name, callback));
}
fn add_function_mut<F, A, R>(&mut self, name: impl Into<StdString>, function: F)
where
F: FnMut(&Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
{
let name = name.into();
let callback = self.box_function_mut(&name, function);
self.raw.methods.push((name, callback));
}
#[cfg(feature = "async")]
fn add_async_function<F, A, FR, R>(&mut self, name: impl Into<StdString>, function: F)
where
F: Fn(Lua, A) -> FR + MaybeSend + 'static,
A: FromLuaMulti,
FR: Future<Output = Result<R>> + MaybeSend + 'static,
R: IntoLuaMulti,
{
let name = name.into();
let callback = self.box_async_function(&name, function);
self.raw.async_methods.push((name, callback));
}
fn add_meta_method<M, A, R>(&mut self, name: impl Into<StdString>, method: M)
where
M: Fn(&Lua, &T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
{
let name = name.into();
let callback = self.box_method(&name, method);
self.raw.meta_methods.push((name, callback));
}
fn add_meta_method_mut<M, A, R>(&mut self, name: impl Into<StdString>, method: M)
where
M: FnMut(&Lua, &mut T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
{
let name = name.into();
let callback = self.box_method_mut(&name, method);
self.raw.meta_methods.push((name, callback));
}
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
fn add_async_meta_method<M, A, MR, R>(&mut self, name: impl Into<StdString>, method: M)
where
T: 'static,
M: Fn(Lua, UserDataRef<T>, A) -> MR + MaybeSend + 'static,
A: FromLuaMulti,
MR: Future<Output = Result<R>> + MaybeSend + 'static,
R: IntoLuaMulti,
{
let name = name.into();
let callback = self.box_async_method(&name, method);
self.raw.async_meta_methods.push((name, callback));
}
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
fn add_async_meta_method_mut<M, A, MR, R>(&mut self, name: impl Into<StdString>, method: M)
where
T: 'static,
M: Fn(Lua, UserDataRefMut<T>, A) -> MR + MaybeSend + 'static,
A: FromLuaMulti,
MR: Future<Output = Result<R>> + MaybeSend + 'static,
R: IntoLuaMulti,
{
let name = name.into();
let callback = self.box_async_method_mut(&name, method);
self.raw.async_meta_methods.push((name, callback));
}
fn add_meta_function<F, A, R>(&mut self, name: impl Into<StdString>, function: F)
where
F: Fn(&Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
{
let name = name.into();
let callback = self.box_function(&name, function);
self.raw.meta_methods.push((name, callback));
}
fn add_meta_function_mut<F, A, R>(&mut self, name: impl Into<StdString>, function: F)
where
F: FnMut(&Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti,
R: IntoLuaMulti,
{
let name = name.into();
let callback = self.box_function_mut(&name, function);
self.raw.meta_methods.push((name, callback));
}
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
fn add_async_meta_function<F, A, FR, R>(&mut self, name: impl Into<StdString>, function: F)
where
F: Fn(Lua, A) -> FR + MaybeSend + 'static,
A: FromLuaMulti,
FR: Future<Output = Result<R>> + MaybeSend + 'static,
R: IntoLuaMulti,
{
let name = name.into();
let callback = self.box_async_function(&name, function);
self.raw.async_meta_methods.push((name, callback));
}
}
macro_rules! lua_userdata_impl {
($type:ty) => {
impl<T: UserData + 'static> UserData for $type {
fn register(registry: &mut UserDataRegistry<Self>) {
let mut orig_registry = UserDataRegistry::new(registry.lua.lua());
T::register(&mut orig_registry);
// Copy all fields, methods, etc. from the original registry
(registry.raw.fields).extend(orig_registry.raw.fields);
(registry.raw.field_getters).extend(orig_registry.raw.field_getters);
(registry.raw.field_setters).extend(orig_registry.raw.field_setters);
(registry.raw.meta_fields).extend(orig_registry.raw.meta_fields);
(registry.raw.methods).extend(orig_registry.raw.methods);
#[cfg(feature = "async")]
(registry.raw.async_methods).extend(orig_registry.raw.async_methods);
(registry.raw.meta_methods).extend(orig_registry.raw.meta_methods);
#[cfg(feature = "async")]
(registry.raw.async_meta_methods).extend(orig_registry.raw.async_meta_methods);
}
}
};
}
// A special proxy object for UserData
pub(crate) struct UserDataProxy<T>(pub(crate) PhantomData<T>);
lua_userdata_impl!(UserDataProxy<T>);
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
lua_userdata_impl!(std::rc::Rc<T>);
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
lua_userdata_impl!(std::rc::Rc<std::cell::RefCell<T>>);
#[cfg(feature = "userdata-wrappers")]
lua_userdata_impl!(std::sync::Arc<T>);
#[cfg(feature = "userdata-wrappers")]
lua_userdata_impl!(std::sync::Arc<std::sync::Mutex<T>>);
#[cfg(feature = "userdata-wrappers")]
lua_userdata_impl!(std::sync::Arc<std::sync::RwLock<T>>);
#[cfg(feature = "userdata-wrappers")]
lua_userdata_impl!(std::sync::Arc<parking_lot::Mutex<T>>);
#[cfg(feature = "userdata-wrappers")]
lua_userdata_impl!(std::sync::Arc<parking_lot::RwLock<T>>);
#[cfg(test)]
mod assertions {
#[cfg(feature = "send")]
static_assertions::assert_impl_all!(super::RawUserDataRegistry: Send);
}
+469
View File
@@ -0,0 +1,469 @@
use std::any::TypeId;
use std::cell::Cell;
use std::marker::PhantomData;
use std::os::raw::c_int;
use std::ptr;
use super::UserDataStorage;
use crate::error::{Error, Result};
use crate::util::{get_userdata, rawget_field, rawset_field, take_userdata};
// This is a trick to check if a type is `Sync` or not.
// It uses leaked specialization feature from stdlib.
struct IsSync<'a, T> {
is_sync: &'a Cell<bool>,
_marker: PhantomData<T>,
}
impl<T> Clone for IsSync<'_, T> {
fn clone(&self) -> Self {
self.is_sync.set(false);
IsSync {
is_sync: self.is_sync,
_marker: PhantomData,
}
}
}
impl<T: Sync> Copy for IsSync<'_, T> {}
pub(crate) fn is_sync<T>() -> bool {
let is_sync = Cell::new(true);
let _ = [IsSync::<T> {
is_sync: &is_sync,
_marker: PhantomData,
}]
.clone();
is_sync.get()
}
// Userdata type hints, used to match types of wrapped userdata
#[derive(Clone, Copy)]
pub(crate) struct TypeIdHints {
t: TypeId,
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
rc: TypeId,
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
rc_refcell: TypeId,
#[cfg(feature = "userdata-wrappers")]
arc: TypeId,
#[cfg(feature = "userdata-wrappers")]
arc_mutex: TypeId,
#[cfg(feature = "userdata-wrappers")]
arc_rwlock: TypeId,
#[cfg(feature = "userdata-wrappers")]
arc_pl_mutex: TypeId,
#[cfg(feature = "userdata-wrappers")]
arc_pl_rwlock: TypeId,
}
impl TypeIdHints {
pub(crate) fn new<T: 'static>() -> Self {
Self {
t: TypeId::of::<T>(),
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
rc: TypeId::of::<std::rc::Rc<T>>(),
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
rc_refcell: TypeId::of::<std::rc::Rc<std::cell::RefCell<T>>>(),
#[cfg(feature = "userdata-wrappers")]
arc: TypeId::of::<std::sync::Arc<T>>(),
#[cfg(feature = "userdata-wrappers")]
arc_mutex: TypeId::of::<std::sync::Arc<std::sync::Mutex<T>>>(),
#[cfg(feature = "userdata-wrappers")]
arc_rwlock: TypeId::of::<std::sync::Arc<std::sync::RwLock<T>>>(),
#[cfg(feature = "userdata-wrappers")]
arc_pl_mutex: TypeId::of::<std::sync::Arc<parking_lot::Mutex<T>>>(),
#[cfg(feature = "userdata-wrappers")]
arc_pl_rwlock: TypeId::of::<std::sync::Arc<parking_lot::RwLock<T>>>(),
}
}
#[inline(always)]
pub(crate) fn type_id(&self) -> TypeId {
self.t
}
}
pub(crate) unsafe fn borrow_userdata_scoped<T, R>(
state: *mut ffi::lua_State,
idx: c_int,
type_id: Option<TypeId>,
type_hints: TypeIdHints,
f: impl FnOnce(&T) -> R,
) -> Result<R> {
match type_id {
Some(type_id) if type_id == type_hints.t => {
let ud = get_userdata::<UserDataStorage<T>>(state, idx);
(*ud).try_borrow_scoped(|ud| f(ud))
}
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
Some(type_id) if type_id == type_hints.rc => {
let ud = get_userdata::<UserDataStorage<std::rc::Rc<T>>>(state, idx);
(*ud).try_borrow_scoped(|ud| f(ud))
}
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
Some(type_id) if type_id == type_hints.rc_refcell => {
let ud = get_userdata::<UserDataStorage<std::rc::Rc<std::cell::RefCell<T>>>>(state, idx);
(*ud).try_borrow_scoped(|ud| {
let ud = ud.try_borrow().map_err(|_| Error::UserDataBorrowError)?;
Ok(f(&ud))
})?
}
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == type_hints.arc => {
let ud = get_userdata::<UserDataStorage<std::sync::Arc<T>>>(state, idx);
(*ud).try_borrow_scoped(|ud| f(ud))
}
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == type_hints.arc_mutex => {
let ud = get_userdata::<UserDataStorage<std::sync::Arc<std::sync::Mutex<T>>>>(state, idx);
(*ud).try_borrow_scoped(|ud| {
let ud = ud.try_lock().map_err(|_| Error::UserDataBorrowError)?;
Ok(f(&ud))
})?
}
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == type_hints.arc_rwlock => {
let ud = get_userdata::<UserDataStorage<std::sync::Arc<std::sync::RwLock<T>>>>(state, idx);
(*ud).try_borrow_scoped(|ud| {
let ud = ud.try_read().map_err(|_| Error::UserDataBorrowError)?;
Ok(f(&ud))
})?
}
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == type_hints.arc_pl_mutex => {
let ud = get_userdata::<UserDataStorage<std::sync::Arc<parking_lot::Mutex<T>>>>(state, idx);
(*ud).try_borrow_scoped(|ud| {
let ud = ud.try_lock().ok_or(Error::UserDataBorrowError)?;
Ok(f(&ud))
})?
}
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == type_hints.arc_pl_rwlock => {
let ud = get_userdata::<UserDataStorage<std::sync::Arc<parking_lot::RwLock<T>>>>(state, idx);
(*ud).try_borrow_scoped(|ud| {
let ud = ud.try_read().ok_or(Error::UserDataBorrowError)?;
Ok(f(&ud))
})?
}
_ => Err(Error::UserDataTypeMismatch),
}
}
pub(crate) unsafe fn borrow_userdata_scoped_mut<T, R>(
state: *mut ffi::lua_State,
idx: c_int,
type_id: Option<TypeId>,
type_hints: TypeIdHints,
f: impl FnOnce(&mut T) -> R,
) -> Result<R> {
match type_id {
Some(type_id) if type_id == type_hints.t => {
let ud = get_userdata::<UserDataStorage<T>>(state, idx);
(*ud).try_borrow_scoped_mut(|ud| f(ud))
}
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
Some(type_id) if type_id == type_hints.rc => {
let ud = get_userdata::<UserDataStorage<std::rc::Rc<T>>>(state, idx);
(*ud).try_borrow_scoped_mut(|ud| match std::rc::Rc::get_mut(ud) {
Some(ud) => Ok(f(ud)),
None => Err(Error::UserDataBorrowMutError),
})?
}
#[cfg(all(feature = "userdata-wrappers", not(feature = "send")))]
Some(type_id) if type_id == type_hints.rc_refcell => {
let ud = get_userdata::<UserDataStorage<std::rc::Rc<std::cell::RefCell<T>>>>(state, idx);
(*ud).try_borrow_scoped(|ud| {
let mut ud = ud.try_borrow_mut().map_err(|_| Error::UserDataBorrowMutError)?;
Ok(f(&mut ud))
})?
}
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == type_hints.arc => {
let ud = get_userdata::<UserDataStorage<std::sync::Arc<T>>>(state, idx);
(*ud).try_borrow_scoped_mut(|ud| match std::sync::Arc::get_mut(ud) {
Some(ud) => Ok(f(ud)),
None => Err(Error::UserDataBorrowMutError),
})?
}
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == type_hints.arc_mutex => {
let ud = get_userdata::<UserDataStorage<std::sync::Arc<std::sync::Mutex<T>>>>(state, idx);
(*ud).try_borrow_scoped_mut(|ud| {
let mut ud = ud.try_lock().map_err(|_| Error::UserDataBorrowMutError)?;
Ok(f(&mut ud))
})?
}
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == type_hints.arc_rwlock => {
let ud = get_userdata::<UserDataStorage<std::sync::Arc<std::sync::RwLock<T>>>>(state, idx);
(*ud).try_borrow_scoped_mut(|ud| {
let mut ud = ud.try_write().map_err(|_| Error::UserDataBorrowMutError)?;
Ok(f(&mut ud))
})?
}
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == type_hints.arc_pl_mutex => {
let ud = get_userdata::<UserDataStorage<std::sync::Arc<parking_lot::Mutex<T>>>>(state, idx);
(*ud).try_borrow_scoped_mut(|ud| {
let mut ud = ud.try_lock().ok_or(Error::UserDataBorrowMutError)?;
Ok(f(&mut ud))
})?
}
#[cfg(feature = "userdata-wrappers")]
Some(type_id) if type_id == type_hints.arc_pl_rwlock => {
let ud = get_userdata::<UserDataStorage<std::sync::Arc<parking_lot::RwLock<T>>>>(state, idx);
(*ud).try_borrow_scoped_mut(|ud| {
let mut ud = ud.try_write().ok_or(Error::UserDataBorrowMutError)?;
Ok(f(&mut ud))
})?
}
_ => Err(Error::UserDataTypeMismatch),
}
}
// Populates the given table with the appropriate members to be a userdata metatable for the given
// type. This function takes the given table at the `metatable` index, and adds an appropriate
// `__gc` member to it for the given type and a `__metatable` entry to protect the table from script
// access. The function also, if given a `field_getters` or `methods` tables, will create an
// `__index` metamethod (capturing previous one) to lookup in `field_getters` first, then `methods`
// and falling back to the captured `__index` if no matches found.
// The same is also applicable for `__newindex` metamethod and `field_setters` table.
// Internally uses 9 stack spaces and does not call checkstack.
pub(crate) unsafe fn init_userdata_metatable(
state: *mut ffi::lua_State,
metatable: c_int,
field_getters: Option<c_int>,
field_setters: Option<c_int>,
methods: Option<c_int>,
) -> Result<()> {
if field_getters.is_some() || methods.is_some() {
// Push `__index` generator function
init_userdata_metatable_index(state)?;
let index_type = rawget_field(state, metatable, "__index")?;
match index_type {
ffi::LUA_TNIL | ffi::LUA_TTABLE | ffi::LUA_TFUNCTION => {
for &idx in &[field_getters, methods] {
if let Some(idx) = idx {
ffi::lua_pushvalue(state, idx);
} else {
ffi::lua_pushnil(state);
}
}
// Generate `__index`
protect_lua!(state, 4, 1, fn(state) ffi::lua_call(state, 3, 1))?;
}
_ => mlua_panic!("improper `__index` type: {}", index_type),
}
rawset_field(state, metatable, "__index")?;
}
if let Some(field_setters) = field_setters {
// Push `__newindex` generator function
init_userdata_metatable_newindex(state)?;
let newindex_type = rawget_field(state, metatable, "__newindex")?;
match newindex_type {
ffi::LUA_TNIL | ffi::LUA_TTABLE | ffi::LUA_TFUNCTION => {
ffi::lua_pushvalue(state, field_setters);
// Generate `__newindex`
protect_lua!(state, 3, 1, fn(state) ffi::lua_call(state, 2, 1))?;
}
_ => mlua_panic!("improper `__newindex` type: {}", newindex_type),
}
rawset_field(state, metatable, "__newindex")?;
}
ffi::lua_pushboolean(state, 0);
rawset_field(state, metatable, "__metatable")?;
Ok(())
}
unsafe extern "C-unwind" fn lua_error_impl(state: *mut ffi::lua_State) -> c_int {
ffi::lua_error(state);
}
unsafe extern "C-unwind" fn lua_isfunction_impl(state: *mut ffi::lua_State) -> c_int {
ffi::lua_pushboolean(state, ffi::lua_isfunction(state, -1));
1
}
unsafe extern "C-unwind" fn lua_istable_impl(state: *mut ffi::lua_State) -> c_int {
ffi::lua_pushboolean(state, ffi::lua_istable(state, -1));
1
}
unsafe fn init_userdata_metatable_index(state: *mut ffi::lua_State) -> Result<()> {
let index_key = &USERDATA_METATABLE_INDEX as *const u8 as *const _;
if ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, index_key) == ffi::LUA_TFUNCTION {
return Ok(());
}
ffi::lua_pop(state, 1);
// Create and cache `__index` generator
let code = cr#"
local error, isfunction, istable = ...
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)
if field_getters ~= nil then
local field_getter = field_getters[key]
if field_getter ~= nil then
return field_getter(self)
end
end
if methods ~= nil then
local method = methods[key]
if method ~= nil then
return method
end
end
if isfunction(__index) then
return __index(self, key)
elseif __index == nil then
error("attempt to get an unknown field '"..key.."'")
else
return __index[key]
end
end
end
"#;
protect_lua!(state, 0, 1, |state| {
let ret = ffi::luaL_loadbuffer(state, code.as_ptr(), code.count_bytes(), cstr!("=__mlua_index"));
if ret != ffi::LUA_OK {
ffi::lua_error(state);
}
ffi::lua_pushcfunction(state, lua_error_impl);
ffi::lua_pushcfunction(state, lua_isfunction_impl);
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
ffi::lua_pushvalue(state, -1);
ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, index_key);
})
}
unsafe fn init_userdata_metatable_newindex(state: *mut ffi::lua_State) -> Result<()> {
let newindex_key = &USERDATA_METATABLE_NEWINDEX as *const u8 as *const _;
if ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, newindex_key) == ffi::LUA_TFUNCTION {
return Ok(());
}
ffi::lua_pop(state, 1);
// Create and cache `__newindex` generator
let code = cr#"
local error, isfunction = ...
return function (__newindex, field_setters)
return function (self, key, value)
if field_setters ~= nil then
local field_setter = field_setters[key]
if field_setter ~= nil then
field_setter(self, value)
return
end
end
if isfunction(__newindex) then
__newindex(self, key, value)
elseif __newindex == nil then
error("attempt to set an unknown field '"..key.."'")
else
__newindex[key] = value
end
end
end
"#;
protect_lua!(state, 0, 1, |state| {
let code_len = code.count_bytes();
let ret = ffi::luaL_loadbuffer(state, code.as_ptr(), code_len, cstr!("=__mlua_newindex"));
if ret != ffi::LUA_OK {
ffi::lua_error(state);
}
ffi::lua_pushcfunction(state, lua_error_impl);
ffi::lua_pushcfunction(state, lua_isfunction_impl);
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
ffi::lua_pushvalue(state, -1);
ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, newindex_key);
})
}
// This method is called by Lua GC when it's time to collect the userdata.
//
// This method is usually used to collect internal userdata.
#[cfg(not(feature = "luau"))]
pub(crate) unsafe extern "C-unwind" fn collect_userdata<T>(state: *mut ffi::lua_State) -> c_int {
let ud = get_userdata::<T>(state, -1);
ptr::drop_in_place(ud);
0
}
// This method is called by Luau GC when it's time to collect the userdata.
#[cfg(feature = "luau")]
pub(crate) unsafe extern "C" fn collect_userdata<T>(
state: *mut ffi::lua_State,
ud: *mut std::os::raw::c_void,
) {
// Almost none Lua operations are allowed when destructor is running,
// so we need to set a flag to prevent calling any Lua functions
let extra = (*ffi::lua_callbacks(state)).userdata as *mut crate::state::ExtraData;
(*extra).running_gc = true;
// Luau does not support _any_ panics in destructors (they are declared as "C", NOT as "C-unwind"),
// so any panics will trigger `abort()`.
ptr::drop_in_place(ud as *mut T);
(*extra).running_gc = false;
}
// This method can be called by user or Lua GC to destroy the userdata.
// It checks if the userdata is safe to destroy and sets the "destroyed" metatable
// to prevent further GC collection.
pub(super) unsafe extern "C-unwind" fn destroy_userdata_storage<T>(state: *mut ffi::lua_State) -> c_int {
let ud = get_userdata::<UserDataStorage<T>>(state, 1);
if (*ud).is_safe_to_destroy() {
take_userdata::<UserDataStorage<T>>(state, 1);
ffi::lua_pushboolean(state, 1);
} else {
ffi::lua_pushboolean(state, 0);
}
1
}
static USERDATA_METATABLE_INDEX: u8 = 0;
static USERDATA_METATABLE_NEWINDEX: u8 = 0;
-190
View File
@@ -1,190 +0,0 @@
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)),
}
}
}
-797
View File
@@ -1,797 +0,0 @@
#![allow(clippy::await_holding_refcell_ref, clippy::await_holding_lock)]
use std::any::TypeId;
use std::cell::{Ref, RefCell, RefMut};
use std::marker::PhantomData;
use std::os::raw::c_int;
use std::string::String as StdString;
use std::sync::{Arc, Mutex, RwLock};
use crate::error::{Error, Result};
use crate::lua::Lua;
use crate::types::{Callback, MaybeSend};
use crate::userdata::{
AnyUserData, MetaMethod, UserData, UserDataCell, UserDataFields, UserDataMethods,
};
use crate::util::{get_userdata, short_type_name};
use crate::value::{FromLua, FromLuaMulti, IntoLua, IntoLuaMulti, Value};
#[cfg(not(feature = "send"))]
use std::rc::Rc;
#[cfg(feature = "async")]
use {crate::types::AsyncCallback, futures_util::future, std::future::Future};
/// Handle to registry for userdata methods and metamethods.
pub struct UserDataRegistry<'lua, T: 'static> {
// Fields
pub(crate) fields: Vec<(String, Callback<'lua, 'static>)>,
pub(crate) field_getters: Vec<(String, Callback<'lua, 'static>)>,
pub(crate) field_setters: Vec<(String, Callback<'lua, 'static>)>,
pub(crate) meta_fields: Vec<(String, Callback<'lua, 'static>)>,
// Methods
pub(crate) methods: Vec<(String, Callback<'lua, 'static>)>,
#[cfg(feature = "async")]
pub(crate) async_methods: Vec<(String, AsyncCallback<'lua, 'static>)>,
pub(crate) meta_methods: Vec<(String, Callback<'lua, 'static>)>,
#[cfg(feature = "async")]
pub(crate) async_meta_methods: Vec<(String, AsyncCallback<'lua, 'static>)>,
_type: PhantomData<T>,
}
impl<'lua, T: 'static> UserDataRegistry<'lua, T> {
pub(crate) const fn new() -> Self {
UserDataRegistry {
fields: Vec::new(),
field_getters: Vec::new(),
field_setters: Vec::new(),
meta_fields: Vec::new(),
methods: Vec::new(),
#[cfg(feature = "async")]
async_methods: Vec::new(),
meta_methods: Vec::new(),
#[cfg(feature = "async")]
async_meta_methods: Vec::new(),
_type: PhantomData,
}
}
fn box_method<M, A, R>(name: &str, method: M) -> Callback<'lua, 'static>
where
M: Fn(&'lua Lua, &T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
{
let name = get_function_name::<T>(name);
macro_rules! try_self_arg {
($res:expr) => {
$res.map_err(|err| Error::bad_self_argument(&name, err))?
};
($res:expr, $err:expr) => {
$res.map_err(|_| Error::bad_self_argument(&name, $err))?
};
}
Box::new(move |lua, nargs| unsafe {
if nargs == 0 {
let err = Error::from_lua_conversion("missing argument", "userdata", None);
try_self_arg!(Err(err));
}
let state = lua.state();
// Find absolute "self" index before processing args
let index = ffi::lua_absindex(state, -nargs);
// Self was at position 1, so we pass 2 here
let args = A::from_stack_args(nargs - 1, 2, Some(&name), lua);
match try_self_arg!(lua.get_userdata_type_id(index)) {
Some(id) if id == TypeId::of::<T>() => {
let ud = try_self_arg!(get_userdata_ref::<T>(state, index));
method(lua, &ud, args?)?.push_into_stack_multi(lua)
}
#[cfg(not(feature = "send"))]
Some(id) if id == TypeId::of::<Rc<T>>() => {
let ud = try_self_arg!(get_userdata_ref::<Rc<T>>(state, index));
method(lua, &ud, args?)?.push_into_stack_multi(lua)
}
#[cfg(not(feature = "send"))]
Some(id) if id == TypeId::of::<Rc<RefCell<T>>>() => {
let ud = try_self_arg!(get_userdata_ref::<Rc<RefCell<T>>>(state, index));
let ud = try_self_arg!(ud.try_borrow(), Error::UserDataBorrowError);
method(lua, &ud, args?)?.push_into_stack_multi(lua)
}
Some(id) if id == TypeId::of::<Arc<T>>() => {
let ud = try_self_arg!(get_userdata_ref::<Arc<T>>(state, index));
method(lua, &ud, args?)?.push_into_stack_multi(lua)
}
Some(id) if id == TypeId::of::<Arc<Mutex<T>>>() => {
let ud = try_self_arg!(get_userdata_ref::<Arc<Mutex<T>>>(state, index));
let ud = try_self_arg!(ud.try_lock(), Error::UserDataBorrowError);
method(lua, &ud, args?)?.push_into_stack_multi(lua)
}
#[cfg(feature = "parking_lot")]
Some(id) if id == TypeId::of::<Arc<parking_lot::Mutex<T>>>() => {
let ud = get_userdata_ref::<Arc<parking_lot::Mutex<T>>>(state, index);
let ud = try_self_arg!(ud);
let ud = try_self_arg!(ud.try_lock().ok_or(Error::UserDataBorrowError));
method(lua, &ud, args?)?.push_into_stack_multi(lua)
}
Some(id) if id == TypeId::of::<Arc<RwLock<T>>>() => {
let ud = try_self_arg!(get_userdata_ref::<Arc<RwLock<T>>>(state, index));
let ud = try_self_arg!(ud.try_read(), Error::UserDataBorrowError);
method(lua, &ud, args?)?.push_into_stack_multi(lua)
}
#[cfg(feature = "parking_lot")]
Some(id) if id == TypeId::of::<Arc<parking_lot::RwLock<T>>>() => {
let ud = get_userdata_ref::<Arc<parking_lot::RwLock<T>>>(state, index);
let ud = try_self_arg!(ud);
let ud = try_self_arg!(ud.try_read().ok_or(Error::UserDataBorrowError));
method(lua, &ud, args?)?.push_into_stack_multi(lua)
}
_ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)),
}
})
}
fn box_method_mut<M, A, R>(name: &str, method: M) -> Callback<'lua, 'static>
where
M: FnMut(&'lua Lua, &mut T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
{
let name = get_function_name::<T>(name);
macro_rules! try_self_arg {
($res:expr) => {
$res.map_err(|err| Error::bad_self_argument(&name, err))?
};
($res:expr, $err:expr) => {
$res.map_err(|_| Error::bad_self_argument(&name, $err))?
};
}
let method = RefCell::new(method);
Box::new(move |lua, nargs| unsafe {
let mut method = method
.try_borrow_mut()
.map_err(|_| Error::RecursiveMutCallback)?;
if nargs == 0 {
let err = Error::from_lua_conversion("missing argument", "userdata", None);
try_self_arg!(Err(err));
}
let state = lua.state();
// Find absolute "self" index before processing args
let index = ffi::lua_absindex(state, -nargs);
// Self was at position 1, so we pass 2 here
let args = A::from_stack_args(nargs - 1, 2, Some(&name), lua);
match try_self_arg!(lua.get_userdata_type_id(index)) {
Some(id) if id == TypeId::of::<T>() => {
let mut ud = try_self_arg!(get_userdata_mut::<T>(state, index));
method(lua, &mut ud, args?)?.push_into_stack_multi(lua)
}
#[cfg(not(feature = "send"))]
Some(id) if id == TypeId::of::<Rc<T>>() => Err(Error::UserDataBorrowMutError),
#[cfg(not(feature = "send"))]
Some(id) if id == TypeId::of::<Rc<RefCell<T>>>() => {
let ud = try_self_arg!(get_userdata_mut::<Rc<RefCell<T>>>(state, index));
let mut ud = try_self_arg!(ud.try_borrow_mut(), Error::UserDataBorrowMutError);
method(lua, &mut ud, args?)?.push_into_stack_multi(lua)
}
Some(id) if id == TypeId::of::<Arc<T>>() => Err(Error::UserDataBorrowMutError),
Some(id) if id == TypeId::of::<Arc<Mutex<T>>>() => {
let ud = try_self_arg!(get_userdata_mut::<Arc<Mutex<T>>>(state, index));
let mut ud = try_self_arg!(ud.try_lock(), Error::UserDataBorrowMutError);
method(lua, &mut ud, args?)?.push_into_stack_multi(lua)
}
#[cfg(feature = "parking_lot")]
Some(id) if id == TypeId::of::<Arc<parking_lot::Mutex<T>>>() => {
let ud = get_userdata_mut::<Arc<parking_lot::Mutex<T>>>(state, index);
let ud = try_self_arg!(ud);
let mut ud = try_self_arg!(ud.try_lock().ok_or(Error::UserDataBorrowMutError));
method(lua, &mut ud, args?)?.push_into_stack_multi(lua)
}
Some(id) if id == TypeId::of::<Arc<RwLock<T>>>() => {
let ud = try_self_arg!(get_userdata_mut::<Arc<RwLock<T>>>(state, index));
let mut ud = try_self_arg!(ud.try_write(), Error::UserDataBorrowMutError);
method(lua, &mut ud, args?)?.push_into_stack_multi(lua)
}
#[cfg(feature = "parking_lot")]
Some(id) if id == TypeId::of::<Arc<parking_lot::RwLock<T>>>() => {
let ud = get_userdata_mut::<Arc<parking_lot::RwLock<T>>>(state, index);
let ud = try_self_arg!(ud);
let mut ud = try_self_arg!(ud.try_write().ok_or(Error::UserDataBorrowMutError));
method(lua, &mut ud, args?)?.push_into_stack_multi(lua)
}
_ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)),
}
})
}
#[cfg(feature = "async")]
fn box_async_method<'s, M, A, MR, R>(name: &str, method: M) -> AsyncCallback<'lua, 'static>
where
'lua: 's,
T: 'static,
M: Fn(&'lua Lua, &'s T, A) -> MR + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
MR: Future<Output = Result<R>> + 's,
R: IntoLuaMulti<'lua>,
{
let name = Arc::new(get_function_name::<T>(name));
let method = Arc::new(method);
Box::new(move |lua, mut args| unsafe {
let name = name.clone();
let method = method.clone();
macro_rules! try_self_arg {
($res:expr) => {
$res.map_err(|err| Error::bad_self_argument(&name, err))?
};
($res:expr, $err:expr) => {
$res.map_err(|_| Error::bad_self_argument(&name, $err))?
};
}
Box::pin(async move {
let this = args.pop_front().ok_or_else(|| {
Error::from_lua_conversion("missing argument", "userdata", None)
});
let this = try_self_arg!(AnyUserData::from_lua(try_self_arg!(this), lua));
let args = A::from_lua_args(args, 2, Some(&name), lua);
let (ref_thread, index) = (lua.ref_thread(), this.0.index);
match try_self_arg!(this.type_id()) {
Some(id) if id == TypeId::of::<T>() => {
let ud = try_self_arg!(get_userdata_ref::<T>(ref_thread, index));
let ud = std::mem::transmute::<&T, &T>(&ud);
method(lua, ud, args?).await?.push_into_stack_multi(lua)
}
#[cfg(not(feature = "send"))]
Some(id) if id == TypeId::of::<Rc<T>>() => {
let ud = try_self_arg!(get_userdata_ref::<Rc<T>>(ref_thread, index));
let ud = std::mem::transmute::<&T, &T>(&ud);
method(lua, ud, args?).await?.push_into_stack_multi(lua)
}
#[cfg(not(feature = "send"))]
Some(id) if id == TypeId::of::<Rc<RefCell<T>>>() => {
let ud =
try_self_arg!(get_userdata_ref::<Rc<RefCell<T>>>(ref_thread, index));
let ud = try_self_arg!(ud.try_borrow(), Error::UserDataBorrowError);
let ud = std::mem::transmute::<&T, &T>(&ud);
method(lua, ud, args?).await?.push_into_stack_multi(lua)
}
Some(id) if id == TypeId::of::<Arc<T>>() => {
let ud = try_self_arg!(get_userdata_ref::<Arc<T>>(ref_thread, index));
let ud = std::mem::transmute::<&T, &T>(&ud);
method(lua, ud, args?).await?.push_into_stack_multi(lua)
}
Some(id) if id == TypeId::of::<Arc<Mutex<T>>>() => {
let ud =
try_self_arg!(get_userdata_ref::<Arc<Mutex<T>>>(ref_thread, index));
let ud = try_self_arg!(ud.try_lock(), Error::UserDataBorrowError);
let ud = std::mem::transmute::<&T, &T>(&ud);
method(lua, ud, args?).await?.push_into_stack_multi(lua)
}
#[cfg(feature = "parking_lot")]
Some(id) if id == TypeId::of::<Arc<parking_lot::Mutex<T>>>() => {
let ud = get_userdata_ref::<Arc<parking_lot::Mutex<T>>>(ref_thread, index);
let ud = try_self_arg!(ud);
let ud = try_self_arg!(ud.try_lock().ok_or(Error::UserDataBorrowError));
let ud = std::mem::transmute::<&T, &T>(&ud);
method(lua, ud, args?).await?.push_into_stack_multi(lua)
}
Some(id) if id == TypeId::of::<Arc<RwLock<T>>>() => {
let ud =
try_self_arg!(get_userdata_ref::<Arc<RwLock<T>>>(ref_thread, index));
let ud = try_self_arg!(ud.try_read(), Error::UserDataBorrowError);
let ud = std::mem::transmute::<&T, &T>(&ud);
method(lua, ud, args?).await?.push_into_stack_multi(lua)
}
#[cfg(feature = "parking_lot")]
Some(id) if id == TypeId::of::<Arc<parking_lot::RwLock<T>>>() => {
let ud = get_userdata_ref::<Arc<parking_lot::RwLock<T>>>(ref_thread, index);
let ud = try_self_arg!(ud);
let ud = try_self_arg!(ud.try_read().ok_or(Error::UserDataBorrowError));
let ud = std::mem::transmute::<&T, &T>(&ud);
method(lua, ud, args?).await?.push_into_stack_multi(lua)
}
_ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)),
}
})
})
}
#[cfg(feature = "async")]
fn box_async_method_mut<'s, M, A, MR, R>(name: &str, method: M) -> AsyncCallback<'lua, 'static>
where
'lua: 's,
T: 'static,
M: Fn(&'lua Lua, &'s mut T, A) -> MR + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
MR: Future<Output = Result<R>> + 's,
R: IntoLuaMulti<'lua>,
{
let name = Arc::new(get_function_name::<T>(name));
let method = Arc::new(method);
Box::new(move |lua, mut args| unsafe {
let name = name.clone();
let method = method.clone();
macro_rules! try_self_arg {
($res:expr) => {
$res.map_err(|err| Error::bad_self_argument(&name, err))?
};
($res:expr, $err:expr) => {
$res.map_err(|_| Error::bad_self_argument(&name, $err))?
};
}
Box::pin(async move {
let this = args.pop_front().ok_or_else(|| {
Error::from_lua_conversion("missing argument", "userdata", None)
});
let this = try_self_arg!(AnyUserData::from_lua(try_self_arg!(this), lua));
let args = A::from_lua_args(args, 2, Some(&name), lua);
let (ref_thread, index) = (lua.ref_thread(), this.0.index);
match try_self_arg!(this.type_id()) {
Some(id) if id == TypeId::of::<T>() => {
let mut ud = try_self_arg!(get_userdata_mut::<T>(ref_thread, index));
let ud = std::mem::transmute::<&mut T, &mut T>(&mut ud);
method(lua, ud, args?).await?.push_into_stack_multi(lua)
}
#[cfg(not(feature = "send"))]
Some(id) if id == TypeId::of::<Rc<RefCell<T>>>() => {
Err(Error::UserDataBorrowMutError)
}
#[cfg(not(feature = "send"))]
Some(id) if id == TypeId::of::<Rc<RefCell<T>>>() => {
let ud =
try_self_arg!(get_userdata_mut::<Rc<RefCell<T>>>(ref_thread, index));
let mut ud =
try_self_arg!(ud.try_borrow_mut(), Error::UserDataBorrowMutError);
let ud = std::mem::transmute::<&mut T, &mut T>(&mut ud);
method(lua, ud, args?).await?.push_into_stack_multi(lua)
}
#[cfg(not(feature = "send"))]
Some(id) if id == TypeId::of::<Arc<T>>() => Err(Error::UserDataBorrowMutError),
Some(id) if id == TypeId::of::<Arc<Mutex<T>>>() => {
let ud =
try_self_arg!(get_userdata_mut::<Arc<Mutex<T>>>(ref_thread, index));
let mut ud = try_self_arg!(ud.try_lock(), Error::UserDataBorrowMutError);
let ud = std::mem::transmute::<&mut T, &mut T>(&mut ud);
method(lua, ud, args?).await?.push_into_stack_multi(lua)
}
#[cfg(feature = "parking_lot")]
Some(id) if id == TypeId::of::<Arc<parking_lot::Mutex<T>>>() => {
let ud = get_userdata_mut::<Arc<parking_lot::Mutex<T>>>(ref_thread, index);
let ud = try_self_arg!(ud);
let mut ud =
try_self_arg!(ud.try_lock().ok_or(Error::UserDataBorrowMutError));
let ud = std::mem::transmute::<&mut T, &mut T>(&mut ud);
method(lua, ud, args?).await?.push_into_stack_multi(lua)
}
Some(id) if id == TypeId::of::<Arc<RwLock<T>>>() => {
let ud =
try_self_arg!(get_userdata_mut::<Arc<RwLock<T>>>(ref_thread, index));
let mut ud = try_self_arg!(ud.try_write(), Error::UserDataBorrowMutError);
let ud = std::mem::transmute::<&mut T, &mut T>(&mut ud);
method(lua, ud, args?).await?.push_into_stack_multi(lua)
}
#[cfg(feature = "parking_lot")]
Some(id) if id == TypeId::of::<Arc<parking_lot::RwLock<T>>>() => {
let ud = get_userdata_mut::<Arc<parking_lot::RwLock<T>>>(ref_thread, index);
let ud = try_self_arg!(ud);
let mut ud =
try_self_arg!(ud.try_write().ok_or(Error::UserDataBorrowMutError));
let ud = std::mem::transmute::<&mut T, &mut T>(&mut ud);
method(lua, ud, args?).await?.push_into_stack_multi(lua)
}
_ => Err(Error::bad_self_argument(&name, Error::UserDataTypeMismatch)),
}
})
})
}
fn box_function<F, A, R>(name: &str, function: F) -> Callback<'lua, 'static>
where
F: Fn(&'lua Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
{
let name = get_function_name::<T>(name);
Box::new(move |lua, nargs| unsafe {
let args = A::from_stack_args(nargs, 1, Some(&name), lua)?;
function(lua, args)?.push_into_stack_multi(lua)
})
}
fn box_function_mut<F, A, R>(name: &str, function: F) -> Callback<'lua, 'static>
where
F: FnMut(&'lua Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
{
let name = get_function_name::<T>(name);
let function = RefCell::new(function);
Box::new(move |lua, nargs| unsafe {
let function = &mut *function
.try_borrow_mut()
.map_err(|_| Error::RecursiveMutCallback)?;
let args = A::from_stack_args(nargs, 1, Some(&name), lua)?;
function(lua, args)?.push_into_stack_multi(lua)
})
}
#[cfg(feature = "async")]
fn box_async_function<F, A, FR, R>(name: &str, function: F) -> AsyncCallback<'lua, 'static>
where
F: Fn(&'lua Lua, A) -> FR + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
FR: Future<Output = Result<R>> + 'lua,
R: IntoLuaMulti<'lua>,
{
let name = get_function_name::<T>(name);
Box::new(move |lua, args| unsafe {
let args = match A::from_lua_args(args, 1, Some(&name), lua) {
Ok(args) => args,
Err(e) => return Box::pin(future::err(e)),
};
let fut = function(lua, args);
Box::pin(async move { fut.await?.push_into_stack_multi(lua) })
})
}
pub(crate) fn check_meta_field<V>(lua: &'lua Lua, name: &str, value: V) -> Result<Value<'lua>>
where
V: IntoLua<'lua>,
{
let value = value.into_lua(lua)?;
if name == MetaMethod::Index || name == MetaMethod::NewIndex {
match value {
Value::Nil | Value::Table(_) | Value::Function(_) => {}
_ => {
return Err(Error::MetaMethodTypeError {
method: name.to_string(),
type_name: value.type_name(),
message: Some("expected nil, table or function".to_string()),
})
}
}
}
value.into_lua(lua)
}
}
// Returns function name for the type `T`, without the module path
fn get_function_name<T>(name: &str) -> StdString {
format!("{}.{name}", short_type_name::<T>())
}
impl<'lua, T: 'static> UserDataFields<'lua, T> for UserDataRegistry<'lua, T> {
fn add_field<V>(&mut self, name: impl AsRef<str>, value: V)
where
V: IntoLua<'lua> + Clone + 'static,
{
let name = name.as_ref().to_string();
self.fields.push((
name,
Box::new(move |lua, _| unsafe { value.clone().push_into_stack_multi(lua) }),
));
}
fn add_field_method_get<M, R>(&mut self, name: impl AsRef<str>, method: M)
where
M: Fn(&'lua Lua, &T) -> Result<R> + MaybeSend + 'static,
R: IntoLua<'lua>,
{
let name = name.as_ref();
let method = Self::box_method(name, move |lua, data, ()| method(lua, data));
self.field_getters.push((name.into(), method));
}
fn add_field_method_set<M, A>(&mut self, name: impl AsRef<str>, method: M)
where
M: FnMut(&'lua Lua, &mut T, A) -> Result<()> + MaybeSend + 'static,
A: FromLua<'lua>,
{
let name = name.as_ref();
let method = Self::box_method_mut(name, method);
self.field_setters.push((name.into(), method));
}
fn add_field_function_get<F, R>(&mut self, name: impl AsRef<str>, function: F)
where
F: Fn(&'lua Lua, AnyUserData<'lua>) -> Result<R> + MaybeSend + 'static,
R: IntoLua<'lua>,
{
let name = name.as_ref();
let func = Self::box_function(name, function);
self.field_getters.push((name.into(), func));
}
fn add_field_function_set<F, A>(&mut self, name: impl AsRef<str>, mut function: F)
where
F: FnMut(&'lua Lua, AnyUserData<'lua>, A) -> Result<()> + MaybeSend + 'static,
A: FromLua<'lua>,
{
let name = name.as_ref();
let func = Self::box_function_mut(name, move |lua, (data, val)| function(lua, data, val));
self.field_setters.push((name.into(), func));
}
fn add_meta_field<V>(&mut self, name: impl AsRef<str>, value: V)
where
V: IntoLua<'lua> + Clone + 'static,
{
let name = name.as_ref().to_string();
let name2 = name.clone();
self.meta_fields.push((
name,
Box::new(move |lua, _| unsafe {
Self::check_meta_field(lua, &name2, value.clone())?.push_into_stack_multi(lua)
}),
));
}
fn add_meta_field_with<F, R>(&mut self, name: impl AsRef<str>, f: F)
where
F: Fn(&'lua Lua) -> Result<R> + MaybeSend + 'static,
R: IntoLua<'lua>,
{
let name = name.as_ref().to_string();
let name2 = name.clone();
self.meta_fields.push((
name,
Box::new(move |lua, _| unsafe {
Self::check_meta_field(lua, &name2, f(lua)?)?.push_into_stack_multi(lua)
}),
));
}
// Below are internal methods
fn append_fields_from<S>(&mut self, other: UserDataRegistry<'lua, S>) {
self.fields.extend(other.fields);
self.field_getters.extend(other.field_getters);
self.field_setters.extend(other.field_setters);
self.meta_fields.extend(other.meta_fields);
}
}
impl<'lua, T: 'static> UserDataMethods<'lua, T> for UserDataRegistry<'lua, T> {
fn add_method<M, A, R>(&mut self, name: impl AsRef<str>, method: M)
where
M: Fn(&'lua Lua, &T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
{
let name = name.as_ref();
self.methods
.push((name.into(), Self::box_method(name, method)));
}
fn add_method_mut<M, A, R>(&mut self, name: impl AsRef<str>, method: M)
where
M: FnMut(&'lua Lua, &mut T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
{
let name = name.as_ref();
self.methods
.push((name.into(), Self::box_method_mut(name, method)));
}
#[cfg(feature = "async")]
fn add_async_method<'s, M, A, MR, R>(&mut self, name: impl AsRef<str>, method: M)
where
'lua: 's,
T: 'static,
M: Fn(&'lua Lua, &'s T, A) -> MR + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
MR: Future<Output = Result<R>> + 's,
R: IntoLuaMulti<'lua>,
{
let name = name.as_ref();
self.async_methods
.push((name.into(), Self::box_async_method(name, method)));
}
#[cfg(feature = "async")]
fn add_async_method_mut<'s, M, A, MR, R>(&mut self, name: impl AsRef<str>, method: M)
where
'lua: 's,
T: 'static,
M: Fn(&'lua Lua, &'s mut T, A) -> MR + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
MR: Future<Output = Result<R>> + 's,
R: IntoLuaMulti<'lua>,
{
let name = name.as_ref();
self.async_methods
.push((name.into(), Self::box_async_method_mut(name, method)));
}
fn add_function<F, A, R>(&mut self, name: impl AsRef<str>, function: F)
where
F: Fn(&'lua Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
{
let name = name.as_ref();
self.methods
.push((name.into(), Self::box_function(name, function)));
}
fn add_function_mut<F, A, R>(&mut self, name: impl AsRef<str>, function: F)
where
F: FnMut(&'lua Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
{
let name = name.as_ref();
self.methods
.push((name.into(), Self::box_function_mut(name, function)));
}
#[cfg(feature = "async")]
fn add_async_function<F, A, FR, R>(&mut self, name: impl AsRef<str>, function: F)
where
F: Fn(&'lua Lua, A) -> FR + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
FR: Future<Output = Result<R>> + 'lua,
R: IntoLuaMulti<'lua>,
{
let name = name.as_ref();
self.async_methods
.push((name.into(), Self::box_async_function(name, function)));
}
fn add_meta_method<M, A, R>(&mut self, name: impl AsRef<str>, method: M)
where
M: Fn(&'lua Lua, &T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
{
let name = name.as_ref();
self.meta_methods
.push((name.into(), Self::box_method(name, method)));
}
fn add_meta_method_mut<M, A, R>(&mut self, name: impl AsRef<str>, method: M)
where
M: FnMut(&'lua Lua, &mut T, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
{
let name = name.as_ref();
self.meta_methods
.push((name.into(), Self::box_method_mut(name, method)));
}
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
fn add_async_meta_method<'s, M, A, MR, R>(&mut self, name: impl AsRef<str>, method: M)
where
'lua: 's,
T: 'static,
M: Fn(&'lua Lua, &'s T, A) -> MR + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
MR: Future<Output = Result<R>> + 's,
R: IntoLuaMulti<'lua>,
{
let name = name.as_ref();
self.async_meta_methods
.push((name.into(), Self::box_async_method(name, method)));
}
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
fn add_async_meta_method_mut<'s, M, A, MR, R>(&mut self, name: impl AsRef<str>, method: M)
where
'lua: 's,
T: 'static,
M: Fn(&'lua Lua, &'s mut T, A) -> MR + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
MR: Future<Output = Result<R>> + 's,
R: IntoLuaMulti<'lua>,
{
let name = name.as_ref();
self.async_meta_methods
.push((name.into(), Self::box_async_method_mut(name, method)));
}
fn add_meta_function<F, A, R>(&mut self, name: impl AsRef<str>, function: F)
where
F: Fn(&'lua Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
{
let name = name.as_ref();
self.meta_methods
.push((name.into(), Self::box_function(name, function)));
}
fn add_meta_function_mut<F, A, R>(&mut self, name: impl AsRef<str>, function: F)
where
F: FnMut(&'lua Lua, A) -> Result<R> + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
{
let name = name.as_ref();
self.meta_methods
.push((name.into(), Self::box_function_mut(name, function)));
}
#[cfg(all(feature = "async", not(any(feature = "lua51", feature = "luau"))))]
fn add_async_meta_function<F, A, FR, R>(&mut self, name: impl AsRef<str>, function: F)
where
F: Fn(&'lua Lua, A) -> FR + MaybeSend + 'static,
A: FromLuaMulti<'lua>,
FR: Future<Output = Result<R>> + 'lua,
R: IntoLuaMulti<'lua>,
{
let name = name.as_ref();
self.async_meta_methods
.push((name.into(), Self::box_async_function(name, function)));
}
// Below are internal methods used in generated code
fn append_methods_from<S>(&mut self, other: UserDataRegistry<'lua, S>) {
self.methods.extend(other.methods);
#[cfg(feature = "async")]
self.async_methods.extend(other.async_methods);
self.meta_methods.extend(other.meta_methods);
#[cfg(feature = "async")]
self.async_meta_methods.extend(other.async_meta_methods);
}
}
#[inline]
unsafe fn get_userdata_ref<'a, T>(state: *mut ffi::lua_State, index: c_int) -> Result<Ref<'a, T>> {
(*get_userdata::<UserDataCell<T>>(state, index)).try_borrow()
}
#[inline]
unsafe fn get_userdata_mut<'a, T>(
state: *mut ffi::lua_State,
index: c_int,
) -> Result<RefMut<'a, T>> {
(*get_userdata::<UserDataCell<T>>(state, index)).try_borrow_mut()
}
macro_rules! lua_userdata_impl {
($type:ty) => {
impl<T: UserData + 'static> UserData for $type {
fn add_fields<'lua, F: UserDataFields<'lua, Self>>(fields: &mut F) {
let mut orig_fields = UserDataRegistry::new();
T::add_fields(&mut orig_fields);
fields.append_fields_from(orig_fields);
}
fn add_methods<'lua, M: UserDataMethods<'lua, Self>>(methods: &mut M) {
let mut orig_methods = UserDataRegistry::new();
T::add_methods(&mut orig_methods);
methods.append_methods_from(orig_methods);
}
}
};
}
#[cfg(not(feature = "send"))]
lua_userdata_impl!(Rc<T>);
#[cfg(not(feature = "send"))]
lua_userdata_impl!(Rc<RefCell<T>>);
lua_userdata_impl!(Arc<T>);
lua_userdata_impl!(Arc<Mutex<T>>);
lua_userdata_impl!(Arc<RwLock<T>>);
#[cfg(feature = "parking_lot")]
lua_userdata_impl!(Arc<parking_lot::Mutex<T>>);
#[cfg(feature = "parking_lot")]
lua_userdata_impl!(Arc<parking_lot::RwLock<T>>);
// A special proxy object for UserData
pub(crate) struct UserDataProxy<T>(pub(crate) PhantomData<T>);
lua_userdata_impl!(UserDataProxy<T>);
+427
View File
@@ -0,0 +1,427 @@
use std::any::Any;
use std::fmt::Write as _;
use std::mem::MaybeUninit;
use std::os::raw::{c_int, c_void};
use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe};
use std::ptr;
use std::sync::Arc;
use crate::error::{Error, Result};
use crate::memory::MemoryState;
use crate::util::{
check_stack, get_internal_userdata, init_internal_metatable, push_internal_userdata, push_string,
push_table, rawset_field, to_string, TypeKey, DESTRUCTED_USERDATA_METATABLE,
};
static WRAPPED_FAILURE_TYPE_KEY: u8 = 0;
pub(crate) enum WrappedFailure {
None,
Error(Error),
Panic(Option<Box<dyn Any + Send + 'static>>),
}
impl TypeKey for WrappedFailure {
#[inline(always)]
fn type_key() -> *const c_void {
&WRAPPED_FAILURE_TYPE_KEY as *const u8 as *const c_void
}
}
impl WrappedFailure {
pub(crate) unsafe fn new_userdata(state: *mut ffi::lua_State) -> *mut Self {
// Unprotected calls always return `Ok`
push_internal_userdata(state, WrappedFailure::None, false).unwrap()
}
}
// In the context of a lua callback, this will call the given function and if the given function
// returns an error, *or if the given function panics*, this will result in a call to `lua_error` (a
// longjmp). The error or panic is wrapped in such a way that when calling `pop_error` back on
// the Rust side, it will resume the panic.
//
// This function assumes the structure of the stack at the beginning of a callback, that the only
// elements on the stack are the arguments to the callback.
//
// This function uses some of the bottom of the stack for error handling, the given callback will be
// given the number of arguments available as an argument, and should return the number of returns
// as normal, but cannot assume that the arguments available start at 0.
unsafe fn callback_error<F, R>(state: *mut ffi::lua_State, f: F) -> R
where
F: FnOnce(c_int) -> Result<R>,
{
let nargs = ffi::lua_gettop(state);
// We need 2 extra stack spaces to store preallocated memory and error/panic metatable
let extra_stack = if nargs < 2 { 2 - nargs } else { 1 };
ffi::luaL_checkstack(
state,
extra_stack,
cstr!("not enough stack space for callback error handling"),
);
// We cannot shadow Rust errors with Lua ones, we pre-allocate enough memory
// to store a wrapped error or panic *before* we proceed.
let ud = WrappedFailure::new_userdata(state);
ffi::lua_rotate(state, 1, 1);
match catch_unwind(AssertUnwindSafe(|| f(nargs))) {
Ok(Ok(r)) => {
ffi::lua_remove(state, 1);
r
}
Ok(Err(err)) => {
ffi::lua_settop(state, 1);
// Build `CallbackError` with traceback
let traceback = if ffi::lua_checkstack(state, ffi::LUA_TRACEBACK_STACK) != 0 {
ffi::luaL_traceback(state, state, ptr::null(), 0);
let traceback = to_string(state, -1);
ffi::lua_pop(state, 1);
traceback
} else {
"<not enough stack space for traceback>".to_string()
};
let cause = Arc::new(err);
let wrapped_error = WrappedFailure::Error(Error::CallbackError { traceback, cause });
ptr::write(ud, wrapped_error);
ffi::lua_error(state)
}
Err(p) => {
ffi::lua_settop(state, 1);
ptr::write(ud, WrappedFailure::Panic(Some(p)));
ffi::lua_error(state)
}
}
}
// Pops an error off of the stack and returns it. The specific behavior depends on the type of the
// error at the top of the stack:
// 1) If the error is actually a panic, this will continue the panic.
// 2) If the error on the top of the stack is actually an error, just returns it.
// 3) Otherwise, interprets the error as the appropriate lua error.
// Uses 2 stack spaces, does not call checkstack.
pub(crate) unsafe fn pop_error(state: *mut ffi::lua_State, err_code: c_int) -> Error {
mlua_debug_assert!(
err_code != ffi::LUA_OK && err_code != ffi::LUA_YIELD,
"pop_error called with non-error return code"
);
match get_internal_userdata::<WrappedFailure>(state, -1, ptr::null()).as_mut() {
Some(WrappedFailure::Error(err)) => {
ffi::lua_pop(state, 1);
err.clone()
}
Some(WrappedFailure::Panic(panic)) => {
if let Some(p) = panic.take() {
resume_unwind(p);
} else {
Error::PreviouslyResumedPanic
}
}
_ => {
let err_string = to_string(state, -1);
ffi::lua_pop(state, 1);
match err_code {
ffi::LUA_ERRRUN => Error::RuntimeError(err_string),
ffi::LUA_ERRSYNTAX => {
Error::SyntaxError {
// This seems terrible, but as far as I can tell, this is exactly what the
// stock Lua REPL does.
incomplete_input: err_string.ends_with("<eof>") || err_string.ends_with("'<eof>'"),
message: err_string,
}
}
ffi::LUA_ERRERR => {
// This error is raised when the error handler raises an error too many times
// recursively, and continuing to trigger the error handler would cause a stack
// overflow. It is not very useful to differentiate between this and "ordinary"
// runtime errors, so we handle them the same way.
Error::RuntimeError(err_string)
}
ffi::LUA_ERRMEM => Error::MemoryError(err_string),
#[cfg(any(feature = "lua53", feature = "lua52"))]
ffi::LUA_ERRGCMM => Error::GarbageCollectorError(err_string),
_ => mlua_panic!("unrecognized lua error code"),
}
}
}
}
// Call a function that calls into the Lua API and may trigger a Lua error (longjmp) in a safe way.
// Wraps the inner function in a call to `lua_pcall`, so the inner function only has access to a
// limited lua stack. `nargs` is the same as the the parameter to `lua_pcall`, and `nresults` is
// always `LUA_MULTRET`. Provided function must *not* panic, and since it will generally be
// longjmping, should not contain any values that implements Drop.
// Internally uses 2 extra stack spaces, and does not call checkstack.
pub(crate) unsafe fn protect_lua_call(
state: *mut ffi::lua_State,
nargs: c_int,
f: unsafe extern "C-unwind" fn(*mut ffi::lua_State) -> c_int,
) -> Result<()> {
let stack_start = ffi::lua_gettop(state) - nargs;
MemoryState::relax_limit_with(state, || {
ffi::lua_pushcfunction(state, error_traceback);
ffi::lua_pushcfunction(state, f);
});
if nargs > 0 {
ffi::lua_rotate(state, stack_start + 1, 2);
}
let ret = ffi::lua_pcall(state, nargs, ffi::LUA_MULTRET, stack_start + 1);
ffi::lua_remove(state, stack_start + 1);
if ret == ffi::LUA_OK {
Ok(())
} else {
Err(pop_error(state, ret))
}
}
// Call a function that calls into the Lua API and may trigger a Lua error (longjmp) in a safe way.
// Wraps the inner function in a call to `lua_pcall`, so the inner function only has access to a
// limited lua stack. `nargs` and `nresults` are similar to the parameters of `lua_pcall`, but the
// given function return type is not the return value count, instead the inner function return
// values are assumed to match the `nresults` param. Provided function must *not* panic, and since
// it will generally be longjmping, should not contain any values that implements Drop.
// Internally uses 3 extra stack spaces, and does not call checkstack.
pub(crate) unsafe fn protect_lua_closure<F, R>(
state: *mut ffi::lua_State,
nargs: c_int,
nresults: c_int,
f: F,
) -> Result<R>
where
F: FnOnce(*mut ffi::lua_State) -> R,
R: Copy,
{
struct Params<F, R: Copy> {
function: Option<F>,
result: MaybeUninit<R>,
nresults: c_int,
}
unsafe extern "C-unwind" fn do_call<F, R>(state: *mut ffi::lua_State) -> c_int
where
F: FnOnce(*mut ffi::lua_State) -> R,
R: Copy,
{
let params = ffi::lua_touserdata(state, -1) as *mut Params<F, R>;
ffi::lua_pop(state, 1);
let f = (*params).function.take().unwrap();
(*params).result.write(f(state));
if (*params).nresults == ffi::LUA_MULTRET {
ffi::lua_gettop(state)
} else {
(*params).nresults
}
}
let stack_start = ffi::lua_gettop(state) - nargs;
MemoryState::relax_limit_with(state, || {
ffi::lua_pushcfunction(state, error_traceback);
ffi::lua_pushcfunction(state, do_call::<F, R>);
});
if nargs > 0 {
ffi::lua_rotate(state, stack_start + 1, 2);
}
let mut params = Params {
function: Some(f),
result: MaybeUninit::uninit(),
nresults,
};
ffi::lua_pushlightuserdata(state, &mut params as *mut Params<F, R> as *mut c_void);
let ret = ffi::lua_pcall(state, nargs + 1, nresults, stack_start + 1);
ffi::lua_remove(state, stack_start + 1);
if ret == ffi::LUA_OK {
// `LUA_OK` is only returned when the `do_call` function has completed successfully, so
// `params.result` is definitely initialized.
Ok(params.result.assume_init())
} else {
Err(pop_error(state, ret))
}
}
pub(crate) 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/luau-lang/luau/issues/880
#[cfg(feature = "luau")]
if MemoryState::limit_reached(state) {
return 0;
}
if ffi::lua_checkstack(state, 2) == 0 {
// 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.
return 1;
}
if get_internal_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, state, s, 0);
ffi::lua_remove(state, -2);
}
}
1
}
// A variant of `error_traceback` that can safely inspect another (yielded) thread stack
pub(crate) 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_internal_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);
}
}
}
// Initialize the error, panic, and destructed userdata metatables.
pub(crate) unsafe fn init_error_registry(state: *mut ffi::lua_State) -> Result<()> {
check_stack(state, 7)?;
// Create error and panic metatables
static ERROR_PRINT_BUFFER_KEY: u8 = 0;
unsafe extern "C-unwind" fn error_tostring(state: *mut ffi::lua_State) -> c_int {
callback_error(state, |_| {
check_stack(state, 3)?;
let err_buf = match get_internal_userdata::<WrappedFailure>(state, -1, ptr::null()).as_ref() {
Some(WrappedFailure::Error(error)) => {
let err_buf_key = &ERROR_PRINT_BUFFER_KEY as *const u8 as *const c_void;
ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, err_buf_key);
let err_buf = ffi::lua_touserdata(state, -1) as *mut String;
ffi::lua_pop(state, 2);
(*err_buf).clear();
// 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
// kind of recursive error structure?)
let _ = write!(&mut (*err_buf), "{error}");
Ok(err_buf)
}
Some(WrappedFailure::Panic(Some(panic))) => {
let err_buf_key = &ERROR_PRINT_BUFFER_KEY as *const u8 as *const c_void;
ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, err_buf_key);
let err_buf = ffi::lua_touserdata(state, -1) as *mut String;
(*err_buf).clear();
ffi::lua_pop(state, 2);
if let Some(msg) = panic.downcast_ref::<&str>() {
let _ = write!(&mut (*err_buf), "{msg}");
} else if let Some(msg) = panic.downcast_ref::<String>() {
let _ = write!(&mut (*err_buf), "{msg}");
} else {
let _ = write!(&mut (*err_buf), "<panic>");
};
Ok(err_buf)
}
Some(WrappedFailure::Panic(None)) => Err(Error::PreviouslyResumedPanic),
_ => {
// I'm not sure whether this is possible to trigger without bugs in mlua?
Err(Error::UserDataTypeMismatch)
}
}?;
push_string(state, (*err_buf).as_bytes(), true)?;
(*err_buf).clear();
Ok(1)
})
}
init_internal_metatable::<WrappedFailure>(
state,
Some(|state| {
ffi::lua_pushcfunction(state, error_tostring);
ffi::lua_setfield(state, -2, cstr!("__tostring"));
// This is mostly for Luau typeof() function
ffi::lua_pushstring(state, cstr!("error"));
ffi::lua_setfield(state, -2, cstr!("__type"));
}),
)?;
// Create destructed userdata metatable
unsafe extern "C-unwind" fn destructed_error(state: *mut ffi::lua_State) -> c_int {
callback_error(state, |_| Err(Error::UserDataDestructed))
}
push_table(state, 0, 26, true)?;
ffi::lua_pushcfunction(state, destructed_error);
for &method in &[
"__add",
"__sub",
"__mul",
"__div",
"__mod",
"__pow",
"__unm",
#[cfg(any(feature = "lua54", feature = "lua53", feature = "luau"))]
"__idiv",
#[cfg(any(feature = "lua54", feature = "lua53"))]
"__band",
#[cfg(any(feature = "lua54", feature = "lua53"))]
"__bor",
#[cfg(any(feature = "lua54", feature = "lua53"))]
"__bxor",
#[cfg(any(feature = "lua54", feature = "lua53"))]
"__bnot",
#[cfg(any(feature = "lua54", feature = "lua53"))]
"__shl",
#[cfg(any(feature = "lua54", feature = "lua53"))]
"__shr",
"__concat",
"__len",
"__eq",
"__lt",
"__le",
"__index",
"__newindex",
"__call",
"__tostring",
#[cfg(any(feature = "lua54", feature = "lua53", feature = "lua52", feature = "luajit52"))]
"__pairs",
#[cfg(any(feature = "lua53", feature = "lua52", feature = "luajit52"))]
"__ipairs",
#[cfg(feature = "luau")]
"__iter",
#[cfg(feature = "lua54")]
"__close",
] {
ffi::lua_pushvalue(state, -1);
rawset_field(state, -3, method)?;
}
ffi::lua_pop(state, 1);
protect_lua!(state, 1, 0, fn(state) {
let destructed_mt_key = &DESTRUCTED_USERDATA_METATABLE as *const u8 as *const c_void;
ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, destructed_mt_key);
})?;
// Create error print buffer
init_internal_metatable::<String>(state, None)?;
push_internal_userdata(state, String::new(), true)?;
protect_lua!(state, 1, 0, fn(state) {
let err_buf_key = &ERROR_PRINT_BUFFER_KEY as *const u8 as *const c_void;
ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, err_buf_key);
})?;
Ok(())
}
+82 -831
View File
File diff suppressed because it is too large Load Diff
+6 -14
View File
@@ -25,22 +25,17 @@ pub(crate) fn short_type_name<T: ?Sized>() -> String {
// 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))
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();
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];
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("::") =>
{
">" | ")" | "]" if rest_of_string[special_character_index + 1..].starts_with("::") => {
parsed_name.push_str("::");
// Move the index past the "::"
index += special_character_index + 3;
@@ -77,9 +72,6 @@ mod tests {
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"
);
assert_eq!(short_type_name::<dyn Fn(i32) -> i32>(), "dyn Fn(i32) -> i32");
}
}
+80
View File
@@ -0,0 +1,80 @@
use std::any::Any;
use std::os::raw::c_void;
use crate::types::{Callback, CallbackUpvalue};
#[cfg(feature = "async")]
use crate::types::{AsyncCallback, AsyncCallbackUpvalue, AsyncPollUpvalue};
pub(crate) trait TypeKey: Any {
fn type_key() -> *const c_void;
}
impl TypeKey for String {
#[inline(always)]
fn type_key() -> *const c_void {
static STRING_TYPE_KEY: u8 = 0;
&STRING_TYPE_KEY as *const u8 as *const c_void
}
}
impl TypeKey for Callback {
#[inline(always)]
fn type_key() -> *const c_void {
static CALLBACK_TYPE_KEY: u8 = 0;
&CALLBACK_TYPE_KEY as *const u8 as *const c_void
}
}
impl TypeKey for CallbackUpvalue {
#[inline(always)]
fn type_key() -> *const c_void {
static CALLBACK_UPVALUE_TYPE_KEY: u8 = 0;
&CALLBACK_UPVALUE_TYPE_KEY as *const u8 as *const c_void
}
}
#[cfg(not(feature = "luau"))]
impl TypeKey for crate::types::HookCallback {
#[inline(always)]
fn type_key() -> *const c_void {
static HOOK_CALLBACK_TYPE_KEY: u8 = 0;
&HOOK_CALLBACK_TYPE_KEY as *const u8 as *const c_void
}
}
#[cfg(feature = "async")]
impl TypeKey for AsyncCallback {
#[inline(always)]
fn type_key() -> *const c_void {
static ASYNC_CALLBACK_TYPE_KEY: u8 = 0;
&ASYNC_CALLBACK_TYPE_KEY as *const u8 as *const c_void
}
}
#[cfg(feature = "async")]
impl TypeKey for AsyncCallbackUpvalue {
#[inline(always)]
fn type_key() -> *const c_void {
static ASYNC_CALLBACK_UPVALUE_TYPE_KEY: u8 = 0;
&ASYNC_CALLBACK_UPVALUE_TYPE_KEY as *const u8 as *const c_void
}
}
#[cfg(feature = "async")]
impl TypeKey for AsyncPollUpvalue {
#[inline(always)]
fn type_key() -> *const c_void {
static ASYNC_POLL_UPVALUE_TYPE_KEY: u8 = 0;
&ASYNC_POLL_UPVALUE_TYPE_KEY as *const u8 as *const c_void
}
}
#[cfg(feature = "async")]
impl TypeKey for Option<std::task::Waker> {
#[inline(always)]
fn type_key() -> *const c_void {
static WAKER_TYPE_KEY: u8 = 0;
&WAKER_TYPE_KEY as *const u8 as *const c_void
}
}
+173
View File
@@ -0,0 +1,173 @@
use std::os::raw::{c_int, c_void};
use std::{mem, ptr};
use crate::error::Result;
use crate::userdata::collect_userdata;
use crate::util::{check_stack, get_metatable_ptr, push_table, rawset_field, TypeKey};
// Pushes the userdata and attaches a metatable with __gc method.
// Internally uses 3 stack spaces, does not call checkstack.
pub(crate) unsafe fn push_internal_userdata<T: TypeKey>(
state: *mut ffi::lua_State,
t: T,
protect: bool,
) -> Result<*mut T> {
#[cfg(not(feature = "luau"))]
let ud_ptr = if protect {
protect_lua!(state, 0, 1, move |state| {
let ud_ptr = ffi::lua_newuserdata(state, const { mem::size_of::<T>() }) as *mut T;
ptr::write(ud_ptr, t);
ud_ptr
})?
} else {
let ud_ptr = ffi::lua_newuserdata(state, const { mem::size_of::<T>() }) as *mut T;
ptr::write(ud_ptr, t);
ud_ptr
};
#[cfg(feature = "luau")]
let ud_ptr = if protect {
protect_lua!(state, 0, 1, move |state| ffi::lua_newuserdata_t::<T>(state, t))?
} else {
ffi::lua_newuserdata_t::<T>(state, t)
};
get_internal_metatable::<T>(state);
ffi::lua_setmetatable(state, -2);
Ok(ud_ptr)
}
#[track_caller]
pub(crate) unsafe fn get_internal_metatable<T: TypeKey>(state: *mut ffi::lua_State) {
ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, T::type_key());
debug_assert!(ffi::lua_isnil(state, -1) == 0, "internal metatable not found");
}
// Initialize the internal metatable for a type T (with __gc method).
// Uses 6 stack spaces and calls checkstack.
pub(crate) unsafe fn init_internal_metatable<T: TypeKey>(
state: *mut ffi::lua_State,
customize_fn: Option<fn(*mut ffi::lua_State)>,
) -> Result<()> {
check_stack(state, 6)?;
push_table(state, 0, 3, true)?;
#[cfg(not(feature = "luau"))]
{
ffi::lua_pushcfunction(state, collect_userdata::<T>);
rawset_field(state, -2, "__gc")?;
}
ffi::lua_pushboolean(state, 0);
rawset_field(state, -2, "__metatable")?;
protect_lua!(state, 1, 0, |state| {
if let Some(f) = customize_fn {
f(state);
}
ffi::lua_rawsetp(state, ffi::LUA_REGISTRYINDEX, T::type_key());
})?;
Ok(())
}
// Uses up to 1 stack space, does not call `checkstack`
pub(crate) unsafe fn get_internal_userdata<T: TypeKey>(
state: *mut ffi::lua_State,
index: c_int,
mut type_mt_ptr: *const c_void,
) -> *mut T {
let ud = ffi::lua_touserdata(state, index) as *mut T;
if ud.is_null() {
return ptr::null_mut();
}
let mt_ptr = get_metatable_ptr(state, index);
if type_mt_ptr.is_null() {
get_internal_metatable::<T>(state);
type_mt_ptr = ffi::lua_topointer(state, -1);
ffi::lua_pop(state, 1);
}
if mt_ptr != type_mt_ptr {
return ptr::null_mut();
}
ud
}
// Internally uses 3 stack spaces, does not call checkstack.
#[inline]
#[cfg(not(feature = "luau"))]
pub(crate) unsafe fn push_uninit_userdata<T>(state: *mut ffi::lua_State, protect: bool) -> Result<*mut T> {
if protect {
protect_lua!(state, 0, 1, |state| {
ffi::lua_newuserdata(state, const { mem::size_of::<T>() }) as *mut T
})
} else {
Ok(ffi::lua_newuserdata(state, const { mem::size_of::<T>() }) as *mut T)
}
}
// Internally uses 3 stack spaces, does not call checkstack.
#[inline]
pub(crate) unsafe fn push_userdata<T>(state: *mut ffi::lua_State, t: T, protect: bool) -> Result<*mut T> {
let size = const { mem::size_of::<T>() };
#[cfg(not(feature = "luau"))]
let ud_ptr = if protect {
protect_lua!(state, 0, 1, move |state| ffi::lua_newuserdata(state, size))?
} else {
ffi::lua_newuserdata(state, size)
} as *mut T;
#[cfg(feature = "luau")]
let ud_ptr = if protect {
protect_lua!(state, 0, 1, |state| {
ffi::lua_newuserdatadtor(state, size, collect_userdata::<T>)
})?
} else {
ffi::lua_newuserdatadtor(state, size, collect_userdata::<T>)
} as *mut T;
ptr::write(ud_ptr, t);
Ok(ud_ptr)
}
#[inline]
#[track_caller]
pub(crate) unsafe fn get_userdata<T>(state: *mut ffi::lua_State, index: c_int) -> *mut T {
let ud = ffi::lua_touserdata(state, index) as *mut T;
mlua_debug_assert!(!ud.is_null(), "userdata pointer is null");
ud
}
/// Unwraps `T` from the Lua userdata and invalidating it by setting the special "destructed"
/// metatable.
///
/// This method does not check that userdata is of type `T` and was not previously invalidated.
///
/// Uses 1 extra stack space, does not call checkstack.
pub(crate) unsafe fn take_userdata<T>(state: *mut ffi::lua_State, idx: c_int) -> T {
#[rustfmt::skip]
let idx = if idx < 0 { ffi::lua_absindex(state, idx) } else { idx };
// Update the metatable of this userdata to a special one with no `__gc` method and with
// metamethods that trigger an error on access.
// We do this so that it will not be double dropped or used after being dropped.
get_destructed_userdata_metatable(state);
ffi::lua_setmetatable(state, idx);
let ud = get_userdata::<T>(state, idx);
// Update userdata tag to disable destructor and mark as destructed
#[cfg(feature = "luau")]
ffi::lua_setuserdatatag(state, idx, 1);
ptr::read(ud)
}
pub(crate) unsafe fn get_destructed_userdata_metatable(state: *mut ffi::lua_State) {
let key = &DESTRUCTED_USERDATA_METATABLE as *const u8 as *const c_void;
ffi::lua_rawgetp(state, ffi::LUA_REGISTRYINDEX, key);
}
pub(crate) static DESTRUCTED_USERDATA_METATABLE: u8 = 0;
+191 -429
View File
@@ -1,38 +1,35 @@
use std::borrow::Cow;
use std::cmp::Ordering;
use std::collections::HashSet;
use std::iter::{self, FromIterator};
use std::ops::Index;
use std::os::raw::{c_int, c_void};
use std::os::raw::c_void;
use std::string::String as StdString;
use std::sync::Arc;
use std::{fmt, mem, ptr, slice, str, vec};
use std::{fmt, ptr, str};
use num_traits::FromPrimitive;
#[cfg(feature = "serialize")]
use crate::error::{Error, Result};
use crate::function::Function;
use crate::string::{BorrowedStr, String};
use crate::table::Table;
use crate::thread::Thread;
use crate::types::{Integer, LightUserData, Number, ValueRef};
use crate::userdata::AnyUserData;
use crate::util::{check_stack, StackGuard};
#[cfg(feature = "serde")]
use {
crate::table::SerializableTable,
rustc_hash::FxHashSet,
serde::ser::{self, Serialize, Serializer},
std::{cell::RefCell, convert::TryInto, rc::Rc, result::Result as StdResult},
std::{cell::RefCell, rc::Rc, result::Result as StdResult},
};
use crate::error::{Error, Result};
use crate::function::Function;
use crate::lua::Lua;
use crate::string::String;
use crate::table::Table;
use crate::thread::Thread;
use crate::types::{Integer, LightUserData, Number, SubtypeId};
use crate::userdata::AnyUserData;
use crate::util::{check_stack, StackGuard};
/// 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
/// types between separate `Lua` instances, and doing so will result in a panic.
/// A dynamically typed Lua value.
///
/// The non-primitive variants (eg. string/table/function/thread/userdata) 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.
#[derive(Clone)]
pub enum Value<'lua> {
pub enum Value {
/// The Lua value `nil`.
Nil,
/// The Lua value `true` or `false`.
@@ -48,34 +45,41 @@ pub enum Value<'lua> {
/// A Luau vector.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
Vector(crate::types::Vector),
Vector(crate::Vector),
/// An interned string, managed by Lua.
///
/// Unlike Rust strings, Lua strings may not be valid UTF-8.
String(String<'lua>),
String(String),
/// Reference to a Lua table.
Table(Table<'lua>),
Table(Table),
/// Reference to a Lua function (or closure).
Function(Function<'lua>),
Function(Function),
/// Reference to a Lua thread (or coroutine).
Thread(Thread<'lua>),
Thread(Thread),
/// Reference to a userdata object that holds a custom type which implements `UserData`.
///
/// Special builtin userdata types will be represented as other `Value` variants.
UserData(AnyUserData<'lua>),
UserData(AnyUserData),
/// A Luau buffer.
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
Buffer(crate::Buffer),
/// `Error` is a special builtin userdata type. When received from Lua it is implicitly cloned.
Error(Error),
Error(Box<Error>),
/// Any other value not known to mlua (eg. LuaJIT CData).
Other(#[doc(hidden)] ValueRef),
}
pub use self::Value::Nil;
impl<'lua> Value<'lua> {
impl Value {
/// 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()));
pub const NULL: Value = Value::LightUserData(LightUserData(ptr::null_mut()));
/// Returns type name of this value.
pub const fn type_name(&self) -> &'static str {
pub fn type_name(&self) -> &'static str {
match *self {
Value::Nil => "nil",
Value::Boolean(_) => "boolean",
@@ -88,27 +92,26 @@ impl<'lua> Value<'lua> {
Value::Table(_) => "table",
Value::Function(_) => "function",
Value::Thread(_) => "thread",
Value::UserData(AnyUserData(_, SubtypeId::None)) => "userdata",
Value::UserData(_) => "userdata",
#[cfg(feature = "luau")]
Value::UserData(AnyUserData(_, SubtypeId::Buffer)) => "buffer",
#[cfg(feature = "luajit")]
Value::UserData(AnyUserData(_, SubtypeId::CData)) => "cdata",
Value::Buffer(_) => "buffer",
Value::Error(_) => "error",
Value::Other(_) => "other",
}
}
/// Compares two values for equality.
///
/// Equality comparisons do not convert strings to numbers or vice versa.
/// Tables, Functions, Threads, and Userdata are compared by reference:
/// Tables, functions, threads, and userdata are compared by reference:
/// two objects are considered equal only if they are the same object.
///
/// If Tables or Userdata have `__eq` metamethod then mlua will try to invoke it.
/// If table or userdata have `__eq` metamethod then mlua will try to invoke it.
/// The first value is checked first. If that value does not define a metamethod
/// for `__eq`, then mlua will check the second value.
/// Then mlua calls the metamethod with the two values as arguments, if found.
pub fn equals<T: AsRef<Self>>(&self, other: T) -> Result<bool> {
match (self, other.as_ref()) {
pub fn equals(&self, other: &Self) -> Result<bool> {
match (self, other) {
(Value::Table(a), Value::Table(b)) => a.equals(b),
(Value::UserData(a), Value::UserData(b)) => a.equals(b),
(a, b) => Ok(a == b),
@@ -117,28 +120,50 @@ impl<'lua> Value<'lua> {
/// Converts the value to a generic C pointer.
///
/// The value can be a userdata, a table, a thread, a string, or a function; otherwise it returns NULL.
/// Different objects will give different pointers.
/// The value can be a userdata, a table, a thread, a string, or a function; otherwise it
/// returns NULL. Different objects will give different pointers.
/// 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 {
match self {
Value::String(String(vref)) => {
// In Lua < 5.4 (excluding Luau), string pointers are NULL
// Use alternative approach
let lua = vref.lua.lock();
unsafe { ffi::lua_tostring(lua.ref_thread(), vref.index) as *const c_void }
}
Value::LightUserData(ud) => ud.0,
Value::String(String(r))
| Value::Table(Table(r))
| Value::Function(Function(r))
| Value::Thread(Thread(r, ..))
| Value::UserData(AnyUserData(r, ..)) => r.to_pointer(),
Value::Table(Table(vref))
| Value::Function(Function(vref))
| Value::Thread(Thread(vref, ..))
| Value::UserData(AnyUserData(vref))
| Value::Other(vref) => vref.to_pointer(),
#[cfg(feature = "luau")]
Value::Buffer(crate::Buffer(vref)) => vref.to_pointer(),
_ => 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.
/// This might invoke the `__tostring` metamethod for non-primitive types (eg. tables,
/// functions).
pub fn to_string(&self) -> Result<StdString> {
unsafe fn invoke_to_string(vref: &ValueRef) -> Result<StdString> {
let lua = vref.lua.lock();
let state = lua.state();
let _guard = StackGuard::new(state);
check_stack(state, 3)?;
lua.push_ref(vref);
protect_lua!(state, 1, 1, fn(state) {
ffi::luaL_tolstring(state, -1, ptr::null_mut());
})?;
Ok(String(lua.pop_ref()).to_str()?.to_string())
}
match self {
Value::Nil => Ok("nil".to_string()),
Value::Boolean(b) => Ok(b.to_string()),
@@ -149,20 +174,13 @@ impl<'lua> Value<'lua> {
#[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::Table(Table(vref))
| Value::Function(Function(vref))
| Value::Thread(Thread(vref, ..))
| Value::UserData(AnyUserData(vref))
| Value::Other(vref) => unsafe { invoke_to_string(vref) },
#[cfg(feature = "luau")]
Value::Buffer(crate::Buffer(vref)) => unsafe { invoke_to_string(vref) },
Value::Error(err) => Ok(err.to_string()),
}
}
@@ -174,6 +192,8 @@ impl<'lua> Value<'lua> {
}
/// Returns `true` if the value is a [`NULL`].
///
/// [`NULL`]: Value::NULL
#[inline]
pub fn is_null(&self) -> bool {
self == &Self::NULL
@@ -252,8 +272,10 @@ impl<'lua> Value<'lua> {
/// If the value is a Lua [`Integer`], try to convert it to `i64` or return `None` otherwise.
#[inline]
pub fn as_i64(&self) -> Option<i64> {
#[allow(clippy::useless_conversion)]
self.as_integer().and_then(|i| i64::try_from(i).ok())
#[cfg(target_pointer_width = "64")]
return self.as_integer();
#[cfg(not(target_pointer_width = "64"))]
return self.as_integer().map(i64::from);
}
/// Cast the value to `u64`.
@@ -330,19 +352,28 @@ impl<'lua> Value<'lua> {
}
}
/// Cast the value to [`str`].
/// Cast the value to [`BorrowedStr`].
///
/// If the value is a Lua [`String`], try to convert it to [`str`] or return `None` otherwise.
/// If the value is a Lua [`String`], try to convert it to [`BorrowedStr`] or return `None`
/// otherwise.
#[deprecated(
since = "0.11.0",
note = "This method does not follow Rust naming convention. Use `as_string().and_then(|s| s.to_str().ok())` instead."
)]
#[inline]
pub fn as_str(&self) -> Option<&str> {
pub fn as_str(&self) -> Option<BorrowedStr<'_>> {
self.as_string().and_then(|s| s.to_str().ok())
}
/// Cast the value to [`Cow<str>`].
/// Cast the value to [`StdString`].
///
/// If the value is a Lua [`String`], converts it to [`Cow<str>`] or returns `None` otherwise.
/// If the value is a Lua [`String`], converts it to [`StdString`] or returns `None` otherwise.
#[deprecated(
since = "0.11.0",
note = "This method does not follow Rust naming convention. Use `as_string().map(|s| s.to_string_lossy())` instead."
)]
#[inline]
pub fn as_string_lossy(&self) -> Option<Cow<str>> {
pub fn as_string_lossy(&self) -> Option<StdString> {
self.as_string().map(|s| s.to_string_lossy())
}
@@ -414,41 +445,60 @@ impl<'lua> Value<'lua> {
}
}
/// Returns `true` if the value is a Buffer wrapped in [`AnyUserData`].
/// Cast the value to a [`Buffer`].
///
/// If the value is [`Buffer`], returns it or `None` otherwise.
///
/// [`Buffer`]: crate::Buffer
#[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()
pub fn as_buffer(&self) -> Option<&crate::Buffer> {
match self {
Value::Buffer(b) => Some(b),
_ => None,
}
}
/// 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)]
/// Returns `true` if the value is a [`Buffer`].
///
/// [`Buffer`]: crate::Buffer
#[cfg(any(feature = "luau", doc))]
#[cfg_attr(docsrs, doc(cfg(feature = "luau")))]
#[inline]
pub fn is_cdata(&self) -> bool {
self.as_userdata()
.map(|ud| ud.1 == SubtypeId::CData)
.unwrap_or_default()
pub fn is_buffer(&self) -> bool {
self.as_buffer().is_some()
}
/// Returns `true` if the value is an [`Error`].
#[inline]
pub fn is_error(&self) -> bool {
self.as_error().is_some()
}
/// Cast the value to [`Error`].
///
/// If the value is an [`Error`], returns it or `None` otherwise.
pub fn as_error(&self) -> Option<&Error> {
match self {
Value::Error(e) => Some(e),
_ => None,
}
}
/// Wrap reference to this Value into [`SerializableValue`].
///
/// This allows customizing serialization behavior using serde.
#[cfg(feature = "serialize")]
#[cfg_attr(docsrs, doc(cfg(feature = "serialize")))]
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
#[doc(hidden)]
pub fn to_serializable(&self) -> SerializableValue<'_, 'lua> {
pub fn to_serializable(&self) -> SerializableValue<'_> {
SerializableValue::new(self, Default::default(), None)
}
// Compares two values.
// Used to sort values for Debug printing.
pub(crate) fn cmp(&self, other: &Self) -> Ordering {
pub(crate) fn sort_cmp(&self, other: &Self) -> Ordering {
fn cmp_num(a: Number, b: Number) -> Ordering {
match (a, b) {
_ if a < b => Ordering::Less,
@@ -472,16 +522,19 @@ impl<'lua> Value<'lua> {
(_, 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(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,
// Vector (Luau)
#[cfg(feature = "luau")]
(Value::Vector(a), Value::Vector(b)) => a.partial_cmp(b).unwrap_or(Ordering::Equal),
// String
(Value::String(a), Value::String(b)) => a.as_bytes().cmp(b.as_bytes()),
(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
// Other variants can be ordered by their pointer
(a, b) => a.to_pointer().cmp(&b.to_pointer()),
}
}
@@ -518,17 +571,27 @@ impl<'lua> Value<'lua> {
.unwrap_or_else(|| format!("userdata: {:?}", u.to_pointer()));
write!(fmt, "{s}")
}
#[cfg(feature = "luau")]
buf @ Value::Buffer(_) => write!(fmt, "buffer: {:?}", buf.to_pointer()),
Value::Error(e) if recursive => write!(fmt, "{e:?}"),
Value::Error(_) => write!(fmt, "error"),
Value::Other(v) => write!(fmt, "other: {:?}", v.to_pointer()),
}
}
}
impl fmt::Debug for Value<'_> {
impl Default for Value {
fn default() -> Self {
Self::Nil
}
}
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})"),
@@ -542,12 +605,15 @@ impl fmt::Debug for Value<'_> {
Value::Function(f) => write!(fmt, "{f:?}"),
Value::Thread(t) => write!(fmt, "{t:?}"),
Value::UserData(ud) => write!(fmt, "{ud:?}"),
#[cfg(feature = "luau")]
Value::Buffer(buf) => write!(fmt, "{buf:?}"),
Value::Error(e) => write!(fmt, "Error({e:?})"),
Value::Other(v) => write!(fmt, "Other({v:?})"),
}
}
}
impl<'lua> PartialEq for Value<'lua> {
impl PartialEq for Value {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Value::Nil, Value::Nil) => true,
@@ -564,41 +630,36 @@ impl<'lua> PartialEq for Value<'lua> {
(Value::Function(a), Value::Function(b)) => a == b,
(Value::Thread(a), Value::Thread(b)) => a == b,
(Value::UserData(a), Value::UserData(b)) => a == b,
#[cfg(feature = "luau")]
(Value::Buffer(a), Value::Buffer(b)) => a == b,
_ => false,
}
}
}
impl<'lua> AsRef<Value<'lua>> for Value<'lua> {
#[inline]
fn as_ref(&self) -> &Self {
self
}
}
/// 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>,
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
pub struct SerializableValue<'a> {
value: &'a Value,
options: crate::serde::de::Options,
// In many cases we don't need `visited` map, so don't allocate memory by default
visited: Option<Rc<RefCell<FxHashSet<*const c_void>>>>,
}
#[cfg(feature = "serialize")]
impl<'lua> Serialize for Value<'lua> {
#[cfg(feature = "serde")]
impl Serialize for Value {
#[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> {
#[cfg(feature = "serde")]
impl<'a> SerializableValue<'a> {
#[inline]
pub(crate) fn new(
value: &'a Value<'lua>,
value: &'a Value,
options: crate::serde::de::Options,
visited: Option<&Rc<RefCell<FxHashSet<*const c_void>>>>,
) -> Self {
@@ -647,10 +708,19 @@ impl<'a, 'lua> SerializableValue<'a, 'lua> {
self.options.sort_keys = enabled;
self
}
/// If true, empty Lua tables will be encoded as array, instead of map.
///
/// Default: **false**
#[must_use]
pub const fn encode_empty_tables_as_array(mut self, enabled: bool) -> Self {
self.options.encode_empty_tables_as_array = enabled;
self
}
}
#[cfg(feature = "serialize")]
impl<'a, 'lua> Serialize for SerializableValue<'a, 'lua> {
#[cfg(feature = "serde")]
impl Serialize for SerializableValue<'_> {
fn serialize<S>(&self, serializer: S) -> StdResult<S::Ok, S::Error>
where
S: Serializer,
@@ -659,8 +729,7 @@ impl<'a, 'lua> Serialize for SerializableValue<'a, 'lua> {
Value::Nil => serializer.serialize_unit(),
Value::Boolean(b) => serializer.serialize_bool(*b),
#[allow(clippy::useless_conversion)]
Value::Integer(i) => serializer
.serialize_i64((*i).try_into().expect("cannot convert Lua Integer to i64")),
Value::Integer(i) => serializer.serialize_i64((*i).into()),
Value::Number(n) => serializer.serialize_f64(*n),
#[cfg(feature = "luau")]
Value::Vector(v) => v.serialize(serializer),
@@ -673,11 +742,14 @@ impl<'a, 'lua> Serialize for SerializableValue<'a, 'lua> {
Value::UserData(ud) if ud.is_serializable() || self.options.deny_unsupported_types => {
ud.serialize(serializer)
}
#[cfg(feature = "luau")]
Value::Buffer(buf) => buf.serialize(serializer),
Value::Function(_)
| Value::Thread(_)
| Value::UserData(_)
| Value::LightUserData(_)
| Value::Error(_) => {
| Value::Error(_)
| Value::Other(_) => {
if self.options.deny_unsupported_types {
let msg = format!("cannot serialize <{}>", self.value.type_name());
Err(ser::Error::custom(msg))
@@ -689,322 +761,12 @@ impl<'a, 'lua> Serialize for SerializableValue<'a, 'lua> {
}
}
/// Trait for types convertible to `Value`.
pub trait IntoLua<'lua>: Sized {
/// Performs the conversion.
fn into_lua(self, lua: &'lua Lua) -> Result<Value<'lua>>;
/// Pushes the value into the Lua stack.
///
/// # Safety
/// This method does not check Lua stack space.
#[doc(hidden)]
#[inline]
unsafe fn push_into_stack(self, lua: &'lua Lua) -> Result<()> {
lua.push_value(self.into_lua(lua)?)
}
}
/// Trait for types convertible from `Value`.
pub trait FromLua<'lua>: Sized {
/// Performs the conversion.
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.
#[derive(Debug, Clone)]
pub struct MultiValue<'lua> {
vec: Vec<Value<'lua>>,
lua: Option<&'lua Lua>,
}
impl Drop for MultiValue<'_> {
fn drop(&mut self) {
if let Some(lua) = self.lua {
let vec = mem::take(&mut self.vec);
lua.push_multivalue_to_pool(vec);
}
}
}
impl<'lua> MultiValue<'lua> {
/// Creates an empty `MultiValue` containing no values.
pub const fn new() -> MultiValue<'lua> {
MultiValue {
vec: Vec::new(),
lua: None,
}
}
/// Similar to `new` but can reuse previously used container with allocated capacity.
#[inline]
pub(crate) fn with_lua_and_capacity(lua: &'lua Lua, capacity: usize) -> MultiValue<'lua> {
let vec = lua
.pop_multivalue_from_pool()
.map(|mut vec| {
vec.reserve(capacity);
vec
})
.unwrap_or_else(|| Vec::with_capacity(capacity));
MultiValue {
vec,
lua: Some(lua),
}
}
}
impl<'lua> Default for MultiValue<'lua> {
#[inline]
fn default() -> MultiValue<'lua> {
MultiValue::new()
}
}
impl<'lua> FromIterator<Value<'lua>> for MultiValue<'lua> {
#[inline]
fn from_iter<I: IntoIterator<Item = Value<'lua>>>(iter: I) -> Self {
MultiValue::from_vec(Vec::from_iter(iter))
}
}
impl<'lua> IntoIterator for MultiValue<'lua> {
type Item = Value<'lua>;
type IntoIter = iter::Rev<vec::IntoIter<Value<'lua>>>;
#[inline]
fn into_iter(mut self) -> Self::IntoIter {
let vec = mem::take(&mut self.vec);
mem::forget(self);
vec.into_iter().rev()
}
}
impl<'a, 'lua> IntoIterator for &'a MultiValue<'lua> {
type Item = &'a Value<'lua>;
type IntoIter = iter::Rev<slice::Iter<'a, Value<'lua>>>;
#[inline]
fn into_iter(self) -> Self::IntoIter {
self.vec.iter().rev()
}
}
impl<'lua> Index<usize> for MultiValue<'lua> {
type Output = Value<'lua>;
#[inline]
fn index(&self, index: usize) -> &Self::Output {
if let Some(result) = self.get(index) {
result
} else {
panic!(
"index out of bounds: the len is {} but the index is {}",
self.len(),
index
)
}
}
}
impl<'lua> MultiValue<'lua> {
#[inline]
pub fn from_vec(mut vec: Vec<Value<'lua>>) -> MultiValue<'lua> {
vec.reverse();
MultiValue { vec, lua: None }
}
#[inline]
pub fn into_vec(mut self) -> Vec<Value<'lua>> {
let mut vec = mem::take(&mut self.vec);
mem::forget(self);
vec.reverse();
vec
}
#[inline]
pub fn get(&self, index: usize) -> Option<&Value<'lua>> {
if index < self.vec.len() {
return self.vec.get(self.vec.len() - index - 1);
}
None
}
#[inline]
pub fn pop_front(&mut self) -> Option<Value<'lua>> {
self.vec.pop()
}
#[inline]
pub fn push_front(&mut self, value: Value<'lua>) {
self.vec.push(value);
}
#[inline]
pub fn clear(&mut self) {
self.vec.clear();
}
#[inline]
pub fn len(&self) -> usize {
self.vec.len()
}
#[inline]
pub fn is_empty(&self) -> bool {
self.vec.is_empty()
}
#[inline]
pub fn iter(&self) -> iter::Rev<slice::Iter<Value<'lua>>> {
self.vec.iter().rev()
}
#[inline]
pub(crate) fn drain_all(&mut self) -> iter::Rev<vec::Drain<Value<'lua>>> {
self.vec.drain(..).rev()
}
#[inline]
pub(crate) fn refill(
&mut self,
iter: impl IntoIterator<Item = Result<Value<'lua>>>,
) -> Result<()> {
self.vec.clear();
for value in iter {
self.vec.push(value?);
}
self.vec.reverse();
Ok(())
}
}
/// Trait for types convertible to any number of Lua values.
///
/// This is a generalization of `IntoLua`, allowing any number of resulting Lua values instead of just
/// one. Any type that implements `IntoLua` will automatically implement this trait.
pub trait IntoLuaMulti<'lua>: Sized {
/// Performs the conversion.
fn into_lua_multi(self, lua: &'lua Lua) -> Result<MultiValue<'lua>>;
/// Pushes the values into the Lua stack.
///
/// Returns number of pushed values.
#[doc(hidden)]
#[inline]
unsafe fn push_into_stack_multi(self, lua: &'lua Lua) -> Result<c_int> {
let mut values = self.into_lua_multi(lua)?;
let len: c_int = values.len().try_into().unwrap();
unsafe {
check_stack(lua.state(), len + 1)?;
for v in values.drain_all() {
lua.push_value(v)?;
}
}
Ok(len)
}
}
/// Trait for types that can be created from an arbitrary number of Lua values.
///
/// This is a generalization of `FromLua`, allowing an arbitrary number of Lua values to participate
/// in the conversion. Any type that implements `FromLua` will automatically implement this trait.
pub trait FromLuaMulti<'lua>: Sized {
/// Performs the conversion.
///
/// In case `values` contains more values than needed to perform the conversion, the excess
/// values should be ignored. This reflects the semantics of Lua when calling a function or
/// assigning values. Similarly, if not enough values are given, conversions should assume that
/// any missing values are nil.
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::*;
#[cfg(not(feature = "send"))]
static_assertions::assert_not_impl_any!(Value: Send);
static_assertions::assert_not_impl_any!(MultiValue: Send);
#[cfg(feature = "send")]
static_assertions::assert_impl_all!(Value: Send, Sync);
}

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