merge: origin/main -> feature/datetime

This commit is contained in:
Erica Marigold 2023-08-24 21:35:19 +05:30
commit a5ed13e62b
No known key found for this signature in database
GPG key ID: 23CD97ABBBCC5ED2
124 changed files with 4577 additions and 4268 deletions

View file

@ -8,10 +8,42 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased ## `0.7.7` - August 23rd, 2023
### Added ### Added
- Added a [REPL](https://en.wikipedia.org/wiki/Readevalprint_loop) to Lune. ([#83])
This allows you to run scripts within Lune without writing files!
Example usage, inside your favorite terminal:
```bash
# 1. Run the Lune executable, without any arguments
lune
# 2. You will be shown the current Lune version and a blank prompt arrow:
Lune v0.7.7
>
# 3. Start typing, and hit enter when you want to run your script!
# Your script will run until completion and output things along the way.
> print(2 + 3)
5
> print("Hello, lune changelog!")
Hello, lune changelog!
# 4. You can also set variables that will get preserved between runs.
# Note that local variables do not get preserved here.
> myVariable = 123
> print(myVariable)
123
# 5. Press either of these key combinations to exit the REPL:
# - Ctrl + D
# - Ctrl + C
```
- Added a new `luau` built-in library for manually compiling and loading Luau source code. ([#82]) - Added a new `luau` built-in library for manually compiling and loading Luau source code. ([#82])
Example usage: Example usage:
@ -31,14 +63,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
callableFn2() callableFn2()
``` ```
- Implemented support for a variable number of arguments for `CFrame` methods in the `roblox` built-in library. ([#85]) ### Changed
- Update to Luau version `0.591`
- Lune's internal task scheduler and `require` functionality has been completely rewritten. <br/>
The new scheduler is much more stable, conforms to a larger test suite, and has a few additional benefits:
- Built-in libraries are now lazily loaded, meaning nothing gets allocated until the built-in library gets loaded using `require("@lune/builtin-name")`. This also improves startup times slightly.
- Spawned processes using `process.spawn` now run on different thread(s), freeing up resources for the main thread where luau runs.
- Serving requests using `net.serve` now processes requests on background threads, also freeing up resources. In the future, this will also allow us to offload heavy tasks such as compression/decompression to background threads.
- Groundwork for custom / user-defined require aliases has been implemented, as well as absolute / cwd-relative requires. These will both be exposed as options and be made available to use some time in the future.
- When using the `serde` built-in library, keys are now sorted during serialization. This means that the output of `encode` is now completely deterministic, and wont cause issues when committing generated files to git etc.
### Fixed ### Fixed
- Fixed not being able to pass arguments to the thread using `coroutine.resume`. ([#86]) - Fixed not being able to pass arguments to the thread using `coroutine.resume`. ([#86])
- Fixed a large number of long-standing issues, from the task scheduler rewrite:
- Fixed `require` hanging indefinitely when the module being require-d uses an async function in its main body.
- Fixed background tasks (such as `net.serve`) not keeping Lune alive even if there are no lua threads to run.
- Fixed spurious panics and error messages such as `Tried to resume next queued future but none are queued`.
- Fixed not being able to catch non-string errors properly, errors were accidentally being wrapped in an opaque `userdata` type.
[#82]: https://github.com/filiptibell/lune/pull/82 [#82]: https://github.com/filiptibell/lune/pull/82
[#85]: https://github.com/filiptibell/lune/pull/85 [#83]: https://github.com/filiptibell/lune/pull/83
[#86]: https://github.com/filiptibell/lune/pull/86 [#86]: https://github.com/filiptibell/lune/pull/86
## `0.7.6` - August 9th, 2023 ## `0.7.6` - August 9th, 2023

406
Cargo.lock generated
View file

@ -4,9 +4,9 @@ version = 3
[[package]] [[package]]
name = "addr2line" name = "addr2line"
version = "0.20.0" version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4fa78e18c64fce05e902adecd7a5eed15a5e0a3439f7b0e169f0252214865e3" checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
dependencies = [ dependencies = [
"gimli", "gimli",
] ]
@ -19,9 +19,9 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "1.0.2" version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@ -73,9 +73,9 @@ dependencies = [
[[package]] [[package]]
name = "anstyle" name = "anstyle"
version = "1.0.1" version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" checksum = "15c4c2c83f81532e5845a733998b6971faca23490340a418e9b72a3ec9de12ea"
[[package]] [[package]]
name = "anstyle-parse" name = "anstyle-parse"
@ -107,9 +107,9 @@ dependencies = [
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.72" version = "1.0.75"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
[[package]] [[package]]
name = "arrayref" name = "arrayref"
@ -145,13 +145,13 @@ dependencies = [
[[package]] [[package]]
name = "async-trait" name = "async-trait"
version = "0.1.72" version = "0.1.73"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc6dde6e4ed435a4c1ee4e73592f5ba9da2151af10076cc04858746af9352d09" checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.28", "syn 2.0.29",
] ]
[[package]] [[package]]
@ -162,9 +162,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]] [[package]]
name = "backtrace" name = "backtrace"
version = "0.3.68" version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4319208da049c43661739c5fade2ba182f09d1dc2299b32298d3a31692b17e12" checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837"
dependencies = [ dependencies = [
"addr2line", "addr2line",
"cc", "cc",
@ -201,9 +201,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.3.3" version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "630be753d4e58660abd17930c71b647fe46c27ea6b63cc59e1e3851406972e42" checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
[[package]] [[package]]
name = "blake2b_simd" name = "blake2b_simd"
@ -296,9 +296,9 @@ checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.82" version = "1.0.83"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01" checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@ -340,9 +340,9 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.3.21" version = "4.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c27cdf28c0f604ba3f512b0c9a409f8de8513e4816705deb0498b627e7c3a3fd" checksum = "fb690e81c7840c0d7aade59f242ea3b41b9bc27bcd5997890e7702ae4b32e487"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive", "clap_derive",
@ -351,9 +351,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.3.21" version = "4.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08a9f1ab5e9f01a9b81f202e8562eb9a10de70abf9eaeac1be465c28b75aa4aa" checksum = "5ed2e96bc16d8d740f6f48d663eddf4b8a0983e79210fd55479b7bcd0a69860e"
dependencies = [ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
@ -370,7 +370,7 @@ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.28", "syn 2.0.29",
] ]
[[package]] [[package]]
@ -488,9 +488,9 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
[[package]] [[package]]
name = "deranged" name = "deranged"
version = "0.3.7" version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7684a49fb1af197853ef7b2ee694bc1f5b4179556f1e5710e1760c5db6f5e929" checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946"
[[package]] [[package]]
name = "dialoguer" name = "dialoguer"
@ -573,9 +573,9 @@ checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]] [[package]]
name = "encoding_rs" name = "encoding_rs"
version = "0.8.32" version = "0.8.33"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
] ]
@ -607,9 +607,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]] [[package]]
name = "erased-serde" name = "erased-serde"
version = "0.3.28" version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da96524cc884f6558f1769b6c46686af2fe8e8b4cd253bd5a3cdba8181b8e070" checksum = "837c0466252947ada828b975e12daf82e18bb5444e4df87be6038d4469e2a3d2"
dependencies = [ dependencies = [
"serde", "serde",
] ]
@ -664,9 +664,9 @@ dependencies = [
[[package]] [[package]]
name = "flate2" name = "flate2"
version = "1.0.26" version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" checksum = "c6c98ee8095e9d1dcbf2fcc6d95acccb90d1c81db1e44725c6a984b1dbdfb010"
dependencies = [ dependencies = [
"crc32fast", "crc32fast",
"miniz_oxide", "miniz_oxide",
@ -710,7 +710,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.28", "syn 2.0.29",
] ]
[[package]] [[package]]
@ -774,9 +774,9 @@ dependencies = [
[[package]] [[package]]
name = "gimli" name = "gimli"
version = "0.27.3" version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c80984affa11d98d1b88b66ac8853f143217b399d3c74116778ff8fdb4ed2e" checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"
[[package]] [[package]]
name = "glam" name = "glam"
@ -792,9 +792,9 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]] [[package]]
name = "h2" name = "h2"
version = "0.3.20" version = "0.3.21"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97ec8491ebaf99c8eaa73058b045fe58073cd6be7f596ac993ced0b0a0c01049" checksum = "91fc23aa11be92976ef4729127f1a74adf36d8436f7816b185d18df956790833"
dependencies = [ dependencies = [
"bytes", "bytes",
"fnv", "fnv",
@ -872,9 +872,9 @@ checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
[[package]] [[package]]
name = "httpdate" name = "httpdate"
version = "1.0.2" version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]] [[package]]
name = "humantime" name = "humantime"
@ -1086,22 +1086,22 @@ dependencies = [
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.19" version = "0.4.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
[[package]] [[package]]
name = "luau0-src" name = "luau0-src"
version = "0.6.0+luau588" version = "0.7.1+luau591"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c628f5525cc62a89a2d478b2ee619c77b35da55c8e3231752f3b8fe528a6c49" checksum = "2fb600eccdbc0bb69e746fddb756559a67b5fcfc01c8a142c6853fec76b6bfc7"
dependencies = [ dependencies = [
"cc", "cc",
] ]
[[package]] [[package]]
name = "lune" name = "lune"
version = "0.7.6" version = "0.7.7"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-compression", "async-compression",
@ -1124,6 +1124,7 @@ dependencies = [
"mlua", "mlua",
"once_cell", "once_cell",
"os_str_bytes", "os_str_bytes",
"path-clean",
"pin-project", "pin-project",
"rand", "rand",
"rbx_binary", "rbx_binary",
@ -1142,6 +1143,8 @@ dependencies = [
"tokio", "tokio",
"tokio-tungstenite", "tokio-tungstenite",
"toml", "toml",
"tracing",
"tracing-subscriber",
"urlencoding", "urlencoding",
] ]
@ -1174,6 +1177,15 @@ dependencies = [
"twox-hash", "twox-hash",
] ]
[[package]]
name = "matchers"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
dependencies = [
"regex-automata 0.1.10",
]
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.5.0" version = "2.5.0"
@ -1208,9 +1220,9 @@ dependencies = [
[[package]] [[package]]
name = "mlua" name = "mlua"
version = "0.9.0-rc.3" version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01a6500a9fb74b519a85ac206cd57f9f91b270ce39d6cb12ab06a8ed29c3563d" checksum = "6c3a7a7ff4481ec91b951a733390211a8ace1caba57266ccb5f4d4966704e560"
dependencies = [ dependencies = [
"bstr", "bstr",
"erased-serde", "erased-serde",
@ -1224,9 +1236,9 @@ dependencies = [
[[package]] [[package]]
name = "mlua-sys" name = "mlua-sys"
version = "0.3.1" version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa5b61f6c943d77dd6ab5f670865670f65b978400127c8bf31c2df7d6e76289a" checksum = "3ec8b54eddb76093069cce9eeffb4c7b3a1a0fe66962d7bd44c4867928149ca3"
dependencies = [ dependencies = [
"cc", "cc",
"cfg-if", "cfg-if",
@ -1255,6 +1267,16 @@ dependencies = [
"static_assertions", "static_assertions",
] ]
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
dependencies = [
"overload",
"winapi",
]
[[package]] [[package]]
name = "num-integer" name = "num-integer"
version = "0.1.45" version = "0.1.45"
@ -1286,9 +1308,9 @@ dependencies = [
[[package]] [[package]]
name = "object" name = "object"
version = "0.31.1" version = "0.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bda667d9f2b5051b8833f59f3bf748b28ef54f850f4fcb389a252aa383866d1" checksum = "77ac5bbd07aea88c60a577a1ce218075ffd59208b2d7ca97adf9bfc5aeb21ebe"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@ -1323,6 +1345,12 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "overload"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
version = "0.12.1" version = "0.12.1"
@ -1343,7 +1371,7 @@ dependencies = [
"libc", "libc",
"redox_syscall 0.3.5", "redox_syscall 0.3.5",
"smallvec", "smallvec",
"windows-targets 0.48.1", "windows-targets 0.48.5",
] ]
[[package]] [[package]]
@ -1352,6 +1380,12 @@ version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c"
[[package]]
name = "path-clean"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17359afc20d7ab31fdb42bb844c8b3bb1dabd7dcf7e68428492da7f16966fcef"
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.3.0" version = "2.3.0"
@ -1375,7 +1409,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.28", "syn 2.0.29",
] ]
[[package]] [[package]]
@ -1407,7 +1441,7 @@ dependencies = [
"line-wrap", "line-wrap",
"quick-xml", "quick-xml",
"serde", "serde",
"time 0.3.25", "time 0.3.27",
] ]
[[package]] [[package]]
@ -1447,7 +1481,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "097bf8b99121dfb8c75eed54dfbdbdb1d53e372c53d2353e8a15aad2a479249d" checksum = "097bf8b99121dfb8c75eed54dfbdbdb1d53e372c53d2353e8a15aad2a479249d"
dependencies = [ dependencies = [
"quote", "quote",
"syn 2.0.28", "syn 2.0.29",
] ]
[[package]] [[package]]
@ -1461,9 +1495,9 @@ dependencies = [
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.32" version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@ -1535,7 +1569,7 @@ dependencies = [
"log", "log",
"plist", "plist",
"winapi", "winapi",
"winreg", "winreg 0.10.1",
] ]
[[package]] [[package]]
@ -1654,8 +1688,17 @@ checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
"regex-automata", "regex-automata 0.3.6",
"regex-syntax", "regex-syntax 0.7.4",
]
[[package]]
name = "regex-automata"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
dependencies = [
"regex-syntax 0.6.29",
] ]
[[package]] [[package]]
@ -1666,9 +1709,15 @@ checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
"regex-syntax", "regex-syntax 0.7.4",
] ]
[[package]]
name = "regex-syntax"
version = "0.6.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
[[package]] [[package]]
name = "regex-syntax" name = "regex-syntax"
version = "0.7.4" version = "0.7.4"
@ -1677,9 +1726,9 @@ checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
[[package]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.11.18" version = "0.11.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55" checksum = "3e9ad3fe7488d7e34558a2033d45a0c90b72d97b4f80705666fea71472e2e6a1"
dependencies = [ dependencies = [
"base64 0.21.2", "base64 0.21.2",
"bytes", "bytes",
@ -1710,8 +1759,8 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-futures", "wasm-bindgen-futures",
"web-sys", "web-sys",
"webpki-roots 0.22.6", "webpki-roots 0.25.2",
"winreg", "winreg 0.50.0",
] ]
[[package]] [[package]]
@ -1786,11 +1835,11 @@ dependencies = [
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.38.7" version = "0.38.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "172891ebdceb05aa0005f533a6cbfca599ddd7d966f6f5d4d9b2e70478e70399" checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f"
dependencies = [ dependencies = [
"bitflags 2.3.3", "bitflags 2.4.0",
"errno", "errno",
"libc", "libc",
"linux-raw-sys", "linux-raw-sys",
@ -1805,7 +1854,7 @@ checksum = "1d1feddffcfcc0b33f5c6ce9a29e341e4cd59c3f78e7ee45f4a40c038b1d6cbb"
dependencies = [ dependencies = [
"log", "log",
"ring", "ring",
"rustls-webpki 0.101.3", "rustls-webpki 0.101.4",
"sct", "sct",
] ]
@ -1820,9 +1869,9 @@ dependencies = [
[[package]] [[package]]
name = "rustls-webpki" name = "rustls-webpki"
version = "0.100.1" version = "0.100.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b" checksum = "e98ff011474fa39949b7e5c0428f9b4937eda7da7848bbb947786b7be0b27dab"
dependencies = [ dependencies = [
"ring", "ring",
"untrusted", "untrusted",
@ -1830,9 +1879,9 @@ dependencies = [
[[package]] [[package]]
name = "rustls-webpki" name = "rustls-webpki"
version = "0.101.3" version = "0.101.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "261e9e0888cba427c3316e6322805653c9425240b6fd96cee7cb671ab70ab8d0" checksum = "7d93931baf2d282fff8d3a532bbfd7653f734643161b87e3e01e59a04439bf0d"
dependencies = [ dependencies = [
"ring", "ring",
"untrusted", "untrusted",
@ -1844,7 +1893,7 @@ version = "12.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "994eca4bca05c87e86e15d90fc7a91d1be64b4482b38cb2d27474568fe7c9db9" checksum = "994eca4bca05c87e86e15d90fc7a91d1be64b4482b38cb2d27474568fe7c9db9"
dependencies = [ dependencies = [
"bitflags 2.3.3", "bitflags 2.4.0",
"cfg-if", "cfg-if",
"clipboard-win", "clipboard-win",
"fd-lock", "fd-lock",
@ -1915,9 +1964,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.183" version = "1.0.186"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c" checksum = "9f5db24220c009de9bd45e69fb2938f4b6d2df856aa9304ce377b3180f83b7c1"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
@ -1934,20 +1983,20 @@ dependencies = [
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.183" version = "1.0.186"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816" checksum = "5ad697f7e0b65af4983a4ce8f56ed5b357e8d3c36651bf6a7e13639c17b8e670"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.28", "syn 2.0.29",
] ]
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.104" version = "1.0.105"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360"
dependencies = [ dependencies = [
"indexmap 2.0.0", "indexmap 2.0.0",
"itoa", "itoa",
@ -2015,6 +2064,15 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012"
[[package]]
name = "sharded-slab"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
dependencies = [
"lazy_static",
]
[[package]] [[package]]
name = "shell-words" name = "shell-words"
version = "1.1.0" version = "1.1.0"
@ -2032,9 +2090,9 @@ dependencies = [
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.8" version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
dependencies = [ dependencies = [
"autocfg", "autocfg",
] ]
@ -2166,9 +2224,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.28" version = "2.0.29"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2177,9 +2235,9 @@ dependencies = [
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.7.1" version = "3.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc02fddf48964c42031a0b3fe0428320ecf3a73c401040fc0096f97794310651" checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"fastrand", "fastrand",
@ -2199,22 +2257,32 @@ dependencies = [
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.44" version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90" checksum = "97a802ec30afc17eee47b2855fc72e0c4cd62be9b4efe6591edde0ec5bd68d8f"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.44" version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" checksum = "6bb623b56e39ab7dcd4b1b98bb6c8f8d907ed255b18de254088016b27a8ee19b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.28", "syn 2.0.29",
]
[[package]]
name = "thread_local"
version = "1.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
dependencies = [
"cfg-if",
"once_cell",
] ]
[[package]] [[package]]
@ -2245,15 +2313,15 @@ dependencies = [
[[package]] [[package]]
name = "time" name = "time"
version = "0.3.25" version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fdd63d58b18d663fbdf70e049f00a22c8e42be082203be7f26589213cd75ea" checksum = "0bb39ee79a6d8de55f48f2293a830e040392f1c5f16e336bdd1788cd0aadce07"
dependencies = [ dependencies = [
"deranged", "deranged",
"itoa", "itoa",
"serde", "serde",
"time-core", "time-core",
"time-macros 0.2.11", "time-macros 0.2.13",
] ]
[[package]] [[package]]
@ -2274,9 +2342,9 @@ dependencies = [
[[package]] [[package]]
name = "time-macros" name = "time-macros"
version = "0.2.11" version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb71511c991639bb078fd5bf97757e03914361c48100d52878b8e52b46fb92cd" checksum = "733d258752e9303d392b94b75230d07b0b9c489350c69b851fc6c065fde3e8f9"
dependencies = [ dependencies = [
"time-core", "time-core",
] ]
@ -2311,9 +2379,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.30.0" version = "1.32.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d3ce25f50619af8b0aec2eb23deebe84249e19e2ddd393a6e16e3300a6dadfd" checksum = "17ed6077ed6cd6c74735e21f37eb16dc3935f96878b1fe961074089cc80893f9"
dependencies = [ dependencies = [
"backtrace", "backtrace",
"bytes", "bytes",
@ -2325,6 +2393,7 @@ dependencies = [
"signal-hook-registry", "signal-hook-registry",
"socket2 0.5.3", "socket2 0.5.3",
"tokio-macros", "tokio-macros",
"tracing",
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
@ -2336,7 +2405,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.28", "syn 2.0.29",
] ]
[[package]] [[package]]
@ -2427,9 +2496,21 @@ checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"pin-project-lite", "pin-project-lite",
"tracing-attributes",
"tracing-core", "tracing-core",
] ]
[[package]]
name = "tracing-attributes"
version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.29",
]
[[package]] [[package]]
name = "tracing-core" name = "tracing-core"
version = "0.1.31" version = "0.1.31"
@ -2437,6 +2518,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a"
dependencies = [ dependencies = [
"once_cell", "once_cell",
"valuable",
]
[[package]]
name = "tracing-log"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922"
dependencies = [
"lazy_static",
"log",
"tracing-core",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77"
dependencies = [
"matchers",
"nu-ansi-term",
"once_cell",
"regex",
"sharded-slab",
"smallvec",
"thread_local",
"tracing",
"tracing-core",
"tracing-log",
] ]
[[package]] [[package]]
@ -2555,6 +2666,12 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "valuable"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]] [[package]]
name = "version_check" name = "version_check"
version = "0.9.4" version = "0.9.4"
@ -2619,7 +2736,7 @@ dependencies = [
"once_cell", "once_cell",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.28", "syn 2.0.29",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@ -2653,7 +2770,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.28", "syn 2.0.29",
"wasm-bindgen-backend", "wasm-bindgen-backend",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@ -2674,34 +2791,21 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "webpki"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd"
dependencies = [
"ring",
"untrusted",
]
[[package]]
name = "webpki-roots"
version = "0.22.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87"
dependencies = [
"webpki",
]
[[package]] [[package]]
name = "webpki-roots" name = "webpki-roots"
version = "0.23.1" version = "0.23.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b03058f88386e5ff5310d9111d53f48b17d732b401aeb83a8d5190f2ac459338" checksum = "b03058f88386e5ff5310d9111d53f48b17d732b401aeb83a8d5190f2ac459338"
dependencies = [ dependencies = [
"rustls-webpki 0.100.1", "rustls-webpki 0.100.2",
] ]
[[package]]
name = "webpki-roots"
version = "0.25.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc"
[[package]] [[package]]
name = "winapi" name = "winapi"
version = "0.3.9" version = "0.3.9"
@ -2739,7 +2843,7 @@ version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f"
dependencies = [ dependencies = [
"windows-targets 0.48.1", "windows-targets 0.48.5",
] ]
[[package]] [[package]]
@ -2757,7 +2861,7 @@ version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [ dependencies = [
"windows-targets 0.48.1", "windows-targets 0.48.5",
] ]
[[package]] [[package]]
@ -2777,17 +2881,17 @@ dependencies = [
[[package]] [[package]]
name = "windows-targets" name = "windows-targets"
version = "0.48.1" version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [ dependencies = [
"windows_aarch64_gnullvm 0.48.0", "windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_msvc 0.48.0", "windows_aarch64_msvc 0.48.5",
"windows_i686_gnu 0.48.0", "windows_i686_gnu 0.48.5",
"windows_i686_msvc 0.48.0", "windows_i686_msvc 0.48.5",
"windows_x86_64_gnu 0.48.0", "windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm 0.48.0", "windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_msvc 0.48.0", "windows_x86_64_msvc 0.48.5",
] ]
[[package]] [[package]]
@ -2798,9 +2902,9 @@ checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]] [[package]]
name = "windows_aarch64_gnullvm" name = "windows_aarch64_gnullvm"
version = "0.48.0" version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
@ -2810,9 +2914,9 @@ checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.48.0" version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
@ -2822,9 +2926,9 @@ checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.48.0" version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
@ -2834,9 +2938,9 @@ checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.48.0" version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
@ -2846,9 +2950,9 @@ checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.48.0" version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
@ -2858,9 +2962,9 @@ checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
version = "0.48.0" version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
@ -2870,15 +2974,15 @@ checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.48.0" version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "0.5.4" version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acaaa1190073b2b101e15083c38ee8ec891b5e05cbee516521e94ec008f61e64" checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@ -2892,6 +2996,16 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "winreg"
version = "0.50.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
dependencies = [
"cfg-if",
"windows-sys 0.48.0",
]
[[package]] [[package]]
name = "xml-rs" name = "xml-rs"
version = "0.8.16" version = "0.8.16"

View file

@ -1,6 +1,6 @@
[package] [package]
name = "lune" name = "lune"
version = "0.7.6" version = "0.7.7"
edition = "2021" edition = "2021"
license = "MPL-2.0" license = "MPL-2.0"
repository = "https://github.com/filiptibell/lune" repository = "https://github.com/filiptibell/lune"
@ -71,18 +71,17 @@ async-trait = "0.1"
dialoguer = "0.10" dialoguer = "0.10"
dunce = "1.0" dunce = "1.0"
lz4_flex = "0.11" lz4_flex = "0.11"
path-clean = "1.0"
pin-project = "1.0" pin-project = "1.0"
os_str_bytes = "6.4" os_str_bytes = "6.4"
urlencoding = "2.1" urlencoding = "2.1"
### RUNTIME ### RUNTIME
mlua = { version = "0.9.0-beta.3", features = [ tracing = "0.1"
"luau", tracing-subscriber = { version = "0.3", features = ["env-filter"] }
"luau-jit", mlua = { version = "0.9.0", features = ["luau", "luau-jit", "serialize"] }
"serialize", tokio = { version = "1.24", features = ["full", "tracing"] }
] }
tokio = { version = "1.24", features = ["full"] }
### SERDE ### SERDE

View file

@ -32,7 +32,7 @@ pub async fn show_interface() -> Result<ExitCode> {
let mut prompt_state = PromptState::Regular; let mut prompt_state = PromptState::Regular;
let mut source_code = String::new(); let mut source_code = String::new();
let lune_instance = Lune::new(); let mut lune_instance = Lune::new();
loop { loop {
let prompt = match prompt_state { let prompt = match prompt_state {

View file

@ -5,7 +5,7 @@ use std::path::{Path, PathBuf};
use mlua::prelude::*; use mlua::prelude::*;
use tokio::fs; use tokio::fs;
use super::FsWriteOptions; use super::options::FsWriteOptions;
pub struct CopyContents { pub struct CopyContents {
// Vec<(relative depth, path)> // Vec<(relative depth, path)>

View file

@ -4,10 +4,15 @@ use std::path::{PathBuf, MAIN_SEPARATOR};
use mlua::prelude::*; use mlua::prelude::*;
use tokio::fs; use tokio::fs;
use crate::lune::lua::{ use crate::lune::util::TableBuilder;
fs::{copy, FsMetadata, FsWriteOptions},
table::TableBuilder, mod copy;
}; mod metadata;
mod options;
use copy::copy;
use metadata::FsMetadata;
use options::FsWriteOptions;
pub fn create(lua: &'static Lua) -> LuaResult<LuaTable> { pub fn create(lua: &'static Lua) -> LuaResult<LuaTable> {
TableBuilder::new(lua)? TableBuilder::new(lua)?

View file

@ -1,13 +1,13 @@
use mlua::prelude::*; use mlua::prelude::*;
use crate::lune::lua::{ use crate::lune::util::TableBuilder;
luau::{LuauCompileOptions, LuauLoadOptions},
table::TableBuilder, mod options;
}; use options::{LuauCompileOptions, LuauLoadOptions};
const BYTECODE_ERROR_BYTE: u8 = 0; const BYTECODE_ERROR_BYTE: u8 = 0;
pub fn create(lua: &'static Lua) -> LuaResult<LuaTable> { pub fn create(lua: &Lua) -> LuaResult<LuaTable> {
TableBuilder::new(lua)? TableBuilder::new(lua)?
.with_function("compile", compile_source)? .with_function("compile", compile_source)?
.with_function("load", load_source)? .with_function("load", load_source)?

View file

@ -1,12 +1,85 @@
pub mod fs; use std::str::FromStr;
pub mod luau;
pub mod net; use mlua::prelude::*;
pub mod process;
pub mod serde; mod fs;
pub mod stdio; mod luau;
pub mod task; mod net;
pub mod top_level; mod process;
pub mod date_time; mod serde;
mod stdio;
mod task;
#[cfg(feature = "roblox")] #[cfg(feature = "roblox")]
pub mod roblox; mod roblox;
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub enum LuneBuiltin {
Fs,
Luau,
Net,
Task,
Process,
Serde,
Stdio,
#[cfg(feature = "roblox")]
Roblox,
}
impl<'lua> LuneBuiltin
where
'lua: 'static, // FIXME: Remove static lifetime bound here when builtin libraries no longer need it
{
pub fn name(&self) -> &'static str {
match self {
Self::Fs => "fs",
Self::Luau => "luau",
Self::Net => "net",
Self::Task => "task",
Self::Process => "process",
Self::Serde => "serde",
Self::Stdio => "stdio",
#[cfg(feature = "roblox")]
Self::Roblox => "roblox",
}
}
pub fn create(&self, lua: &'lua Lua) -> LuaResult<LuaMultiValue<'lua>> {
let res = match self {
Self::Fs => fs::create(lua),
Self::Luau => luau::create(lua),
Self::Net => net::create(lua),
Self::Task => task::create(lua),
Self::Process => process::create(lua),
Self::Serde => serde::create(lua),
Self::Stdio => stdio::create(lua),
#[cfg(feature = "roblox")]
Self::Roblox => roblox::create(lua),
};
match res {
Ok(v) => v.into_lua_multi(lua),
Err(e) => Err(e.context(format!(
"Failed to create builtin library '{}'",
self.name()
))),
}
}
}
impl FromStr for LuneBuiltin {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.trim().to_ascii_lowercase().as_str() {
"fs" => Ok(Self::Fs),
"luau" => Ok(Self::Luau),
"net" => Ok(Self::Net),
"task" => Ok(Self::Task),
"process" => Ok(Self::Process),
"serde" => Ok(Self::Serde),
"stdio" => Ok(Self::Stdio),
#[cfg(feature = "roblox")]
"roblox" => Ok(Self::Roblox),
_ => Err(format!("Unknown builtin library '{s}'")),
}
}
}

View file

@ -5,6 +5,8 @@ use mlua::prelude::*;
use hyper::{header::HeaderName, http::HeaderValue, HeaderMap}; use hyper::{header::HeaderName, http::HeaderValue, HeaderMap};
use reqwest::{IntoUrl, Method, RequestBuilder}; use reqwest::{IntoUrl, Method, RequestBuilder};
const REGISTRY_KEY: &str = "NetClient";
pub struct NetClientBuilder { pub struct NetClientBuilder {
builder: reqwest::ClientBuilder, builder: reqwest::ClientBuilder,
} }
@ -44,6 +46,35 @@ impl NetClient {
pub fn request<U: IntoUrl>(&self, method: Method, url: U) -> RequestBuilder { pub fn request<U: IntoUrl>(&self, method: Method, url: U) -> RequestBuilder {
self.0.request(method, url) self.0.request(method, url)
} }
pub fn into_registry(self, lua: &Lua) {
lua.set_named_registry_value(REGISTRY_KEY, self)
.expect("Failed to store NetClient in lua registry");
}
pub fn from_registry(lua: &Lua) -> Self {
lua.named_registry_value(REGISTRY_KEY)
.expect("Failed to get NetClient from lua registry")
}
} }
impl LuaUserData for NetClient {} impl LuaUserData for NetClient {}
impl<'lua> FromLua<'lua> for NetClient {
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
if let LuaValue::UserData(ud) = value {
if let Ok(ctx) = ud.borrow::<NetClient>() {
return Ok(ctx.clone());
}
}
unreachable!("NetClient should only be used from registry")
}
}
impl<'lua> From<&'lua Lua> for NetClient {
fn from(value: &'lua Lua) -> Self {
value
.named_registry_value(REGISTRY_KEY)
.expect("Missing require context in lua registry")
}
}

View file

@ -2,32 +2,34 @@ use std::collections::HashMap;
use mlua::prelude::*; use mlua::prelude::*;
use console::style; use hyper::header::{CONTENT_ENCODING, CONTENT_LENGTH};
use hyper::{
header::{CONTENT_ENCODING, CONTENT_LENGTH},
Server,
};
use tokio::{sync::mpsc, task};
use crate::lune::lua::{ use crate::lune::{scheduler::Scheduler, util::TableBuilder};
net::{
NetClient, NetClientBuilder, NetLocalExec, NetService, NetWebSocket, RequestConfig, use self::server::create_server;
ServeConfig,
}, use super::serde::{
serde::{decompress, CompressDecompressFormat, EncodeDecodeConfig, EncodeDecodeFormat}, compress_decompress::{decompress, CompressDecompressFormat},
table::TableBuilder, encode_decode::{EncodeDecodeConfig, EncodeDecodeFormat},
task::{TaskScheduler, TaskSchedulerAsyncExt},
}; };
mod client;
mod config;
mod processing;
mod response;
mod server;
mod websocket;
use client::{NetClient, NetClientBuilder};
use config::{RequestConfig, ServeConfig};
use server::bind_to_localhost;
use websocket::NetWebSocket;
pub fn create(lua: &'static Lua) -> LuaResult<LuaTable> { pub fn create(lua: &'static Lua) -> LuaResult<LuaTable> {
// Create a reusable client for performing our NetClientBuilder::new()
// web requests and store it in the lua registry,
// allowing us to reuse headers and internal structs
let client = NetClientBuilder::new()
.headers(&[("User-Agent", create_user_agent_header())])? .headers(&[("User-Agent", create_user_agent_header())])?
.build()?; .build()?
lua.set_named_registry_value("net.client", client)?; .into_registry(lua);
// Create the global table for net
TableBuilder::new(lua)? TableBuilder::new(lua)?
.with_function("jsonEncode", net_json_encode)? .with_function("jsonEncode", net_json_encode)?
.with_function("jsonDecode", net_json_decode)? .with_function("jsonDecode", net_json_decode)?
@ -59,12 +61,12 @@ fn net_json_decode<'lua>(lua: &'lua Lua, json: LuaString<'lua>) -> LuaResult<Lua
EncodeDecodeConfig::from(EncodeDecodeFormat::Json).deserialize_from_string(lua, json) EncodeDecodeConfig::from(EncodeDecodeFormat::Json).deserialize_from_string(lua, json)
} }
async fn net_request<'lua>( async fn net_request<'lua>(lua: &'lua Lua, config: RequestConfig<'lua>) -> LuaResult<LuaTable<'lua>>
lua: &'static Lua, where
config: RequestConfig<'lua>, 'lua: 'static, // FIXME: Get rid of static lifetime bound here
) -> LuaResult<LuaTable<'lua>> { {
// Create and send the request // Create and send the request
let client: LuaUserDataRef<NetClient> = lua.named_registry_value("net.client")?; let client = NetClient::from_registry(lua);
let mut request = client.request(config.method, &config.url); let mut request = client.request(config.method, &config.url);
for (query, value) in config.query { for (query, value) in config.query {
request = request.query(&[(query.to_str()?, value.to_str()?)]); request = request.query(&[(query.to_str()?, value.to_str()?)]);
@ -122,73 +124,28 @@ async fn net_request<'lua>(
.build_readonly() .build_readonly()
} }
async fn net_socket<'lua>(lua: &'static Lua, url: String) -> LuaResult<LuaTable> { async fn net_socket<'lua>(lua: &'lua Lua, url: String) -> LuaResult<LuaTable>
where
'lua: 'static, // FIXME: Get rid of static lifetime bound here
{
let (ws, _) = tokio_tungstenite::connect_async(url).await.into_lua_err()?; let (ws, _) = tokio_tungstenite::connect_async(url).await.into_lua_err()?;
NetWebSocket::new(ws).into_lua_table(lua) NetWebSocket::new(ws).into_lua_table(lua)
} }
async fn net_serve<'lua>( async fn net_serve<'lua>(
lua: &'static Lua, lua: &'lua Lua,
(port, config): (u16, ServeConfig<'lua>), (port, config): (u16, ServeConfig<'lua>),
) -> LuaResult<LuaTable<'lua>> { ) -> LuaResult<LuaTable<'lua>>
// Note that we need to use a mpsc here and not where
// a oneshot channel since we move the sender 'lua: 'static, // FIXME: Get rid of static lifetime bound here
// into our table with the stop function {
let (shutdown_tx, mut shutdown_rx) = mpsc::channel::<()>(1);
let server_request_callback = lua.create_registry_value(config.handle_request)?;
let server_websocket_callback = config.handle_web_socket.map(|handler| {
lua.create_registry_value(handler)
.expect("Failed to store websocket handler")
});
let sched = lua let sched = lua
.app_data_ref::<&TaskScheduler>() .app_data_ref::<&Scheduler>()
.expect("Missing task scheduler - make sure it is added as a lua app data before the first scheduler resumption"); .expect("Lua struct is missing scheduler");
// Bind first to make sure that we can bind to this address
let bound = match Server::try_bind(&([127, 0, 0, 1], port).into()) { let builder = bind_to_localhost(port)?;
Err(e) => {
return Err(LuaError::external(format!( create_server(lua, &sched, config, builder)
"Failed to bind to localhost on port {port}\n{}",
format!("{e}").replace(
"error creating server listener: ",
&format!("{}", style("> ").dim())
)
)));
}
Ok(bound) => bound,
};
// Register a background task to prevent the task scheduler from
// exiting early and start up our web server on the bound address
let task = sched.register_background_task();
let server = bound
.http1_only(true) // Web sockets can only use http1
.http1_keepalive(true) // Web sockets must be kept alive
.executor(NetLocalExec)
.serve(NetService::new(
lua,
server_request_callback,
server_websocket_callback,
))
.with_graceful_shutdown(async move {
task.unregister(Ok(()));
shutdown_rx
.recv()
.await
.expect("Server was stopped instantly");
shutdown_rx.close();
});
// Spawn a new tokio task so we don't block
task::spawn_local(server);
// Create a new read-only table that contains methods
// for manipulating server behavior and shutting it down
let handle_stop = move |_, _: ()| match shutdown_tx.try_send(()) {
Ok(_) => Ok(()),
Err(_) => Err(LuaError::RuntimeError(
"Server has already been stopped".to_string(),
)),
};
TableBuilder::new(lua)?
.with_function("stop", handle_stop)?
.build_readonly()
} }
fn net_url_encode<'lua>( fn net_url_encode<'lua>(

View file

@ -0,0 +1,101 @@
use std::sync::atomic::{AtomicUsize, Ordering};
use hyper::{body::to_bytes, Body, Request};
use mlua::prelude::*;
use crate::lune::util::TableBuilder;
static ID_COUNTER: AtomicUsize = AtomicUsize::new(0);
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub(super) struct ProcessedRequestId(usize);
impl ProcessedRequestId {
pub fn new() -> Self {
// NOTE: This may overflow after a couple billion requests,
// but that's completely fine... unless a request is still
// alive after billions more arrive and need to be handled
Self(ID_COUNTER.fetch_add(1, Ordering::Relaxed))
}
}
pub(super) struct ProcessedRequest {
pub id: ProcessedRequestId,
method: String,
path: String,
query: Vec<(String, String)>,
headers: Vec<(String, Vec<u8>)>,
body: Vec<u8>,
}
impl ProcessedRequest {
pub async fn from_request(req: Request<Body>) -> LuaResult<Self> {
let (head, body) = req.into_parts();
// FUTURE: We can do extra processing like async decompression here
let body = match to_bytes(body).await {
Err(_) => return Err(LuaError::runtime("Failed to read request body bytes")),
Ok(b) => b.to_vec(),
};
let method = head.method.to_string().to_ascii_uppercase();
let mut path = head.uri.path().to_string();
if path.is_empty() {
path = "/".to_string();
}
let query = head
.uri
.query()
.unwrap_or_default()
.split('&')
.filter_map(|q| q.split_once('='))
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect();
let mut headers = Vec::new();
let mut header_name = String::new();
for (name_opt, value) in head.headers.into_iter() {
if let Some(name) = name_opt {
header_name = name.to_string();
}
headers.push((header_name.clone(), value.as_bytes().to_vec()))
}
let id = ProcessedRequestId::new();
Ok(Self {
id,
method,
path,
query,
headers,
body,
})
}
pub fn into_lua_table(self, lua: &Lua) -> LuaResult<LuaTable> {
// FUTURE: Make inner tables for query keys that have multiple values?
let query = lua.create_table_with_capacity(0, self.query.len())?;
for (key, value) in self.query.into_iter() {
query.set(key, value)?;
}
let headers = lua.create_table_with_capacity(0, self.headers.len())?;
for (key, value) in self.headers.into_iter() {
headers.set(key, lua.create_string(value)?)?;
}
let body = lua.create_string(self.body)?;
TableBuilder::new(lua)?
.with_value("method", self.method)?
.with_value("path", self.path)?
.with_value("query", query)?
.with_value("headers", headers)?
.with_value("body", body)?
.build_readonly()
}
}

View file

@ -9,7 +9,7 @@ pub enum NetServeResponseKind {
Table, Table,
} }
#[derive(Debug, Clone)] #[derive(Debug)]
pub struct NetServeResponse { pub struct NetServeResponse {
kind: NetServeResponseKind, kind: NetServeResponseKind,
status: u16, status: u16,
@ -81,26 +81,3 @@ impl<'lua> FromLua<'lua> for NetServeResponse {
} }
} }
} }
impl<'lua> IntoLua<'lua> for NetServeResponse {
fn into_lua(self, lua: &'lua Lua) -> LuaResult<LuaValue<'lua>> {
if self.headers.len() > i32::MAX as usize {
return Err(LuaError::ToLuaConversionError {
from: "NetServeResponse",
to: "table",
message: Some("Too many header values".to_string()),
});
}
let body = self.body.map(|b| lua.create_string(b)).transpose()?;
let headers = lua.create_table_with_capacity(0, self.headers.len())?;
for (key, value) in self.headers {
headers.set(key, lua.create_string(&value)?)?;
}
let table = lua.create_table_with_capacity(0, 3)?;
table.set("status", self.status)?;
table.set("headers", headers)?;
table.set("body", body)?;
table.set_readonly(true);
Ok(LuaValue::Table(table))
}
}

View file

@ -0,0 +1,203 @@
use std::{collections::HashMap, convert::Infallible, net::SocketAddr, sync::Arc};
use hyper::{
server::{conn::AddrIncoming, Builder},
service::{make_service_fn, service_fn},
Server,
};
use hyper_tungstenite::{is_upgrade_request, upgrade, HyperWebsocket};
use mlua::prelude::*;
use tokio::sync::{mpsc, oneshot, Mutex};
use crate::lune::{
scheduler::Scheduler,
util::{traits::LuaEmitErrorExt, TableBuilder},
};
use super::{
config::ServeConfig, processing::ProcessedRequest, response::NetServeResponse,
websocket::NetWebSocket,
};
pub(super) fn bind_to_localhost(port: u16) -> LuaResult<Builder<AddrIncoming>> {
let addr = match SocketAddr::try_from(([127, 0, 0, 1], port)) {
Ok(a) => a,
Err(e) => {
return Err(LuaError::external(format!(
"Failed to bind to localhost on port {port}\n{e}"
)))
}
};
match Server::try_bind(&addr) {
Ok(b) => Ok(b),
Err(e) => Err(LuaError::external(format!(
"Failed to bind to localhost on port {port}\n{}",
e.to_string()
.replace("error creating server listener: ", "> ")
))),
}
}
pub(super) fn create_server<'lua>(
lua: &'lua Lua,
sched: &'lua Scheduler,
config: ServeConfig<'lua>,
builder: Builder<AddrIncoming>,
) -> LuaResult<LuaTable<'lua>>
where
'lua: 'static, // FIXME: Get rid of static lifetime bound here
{
// Note that we need to use a mpsc here and not
// a oneshot channel since we move the sender
// into our table with the stop function
let (shutdown_tx, mut shutdown_rx) = mpsc::channel::<()>(1);
// Communicate between background thread(s) and main lua thread using mpsc and oneshot
let (tx_request, mut rx_request) = mpsc::channel::<ProcessedRequest>(64);
let (tx_websocket, mut rx_websocket) = mpsc::channel::<HyperWebsocket>(64);
let tx_request_arc = Arc::new(tx_request);
let tx_websocket_arc = Arc::new(tx_websocket);
let response_senders = Arc::new(Mutex::new(HashMap::new()));
let response_senders_bg = Arc::clone(&response_senders);
let response_senders_lua = Arc::clone(&response_senders_bg);
// Create our background service which will accept
// requests, do some processing, then forward to lua
let has_websocket_handler = config.handle_web_socket.is_some();
let hyper_make_service = make_service_fn(move |_| {
let tx_request = Arc::clone(&tx_request_arc);
let tx_websocket = Arc::clone(&tx_websocket_arc);
let response_senders = Arc::clone(&response_senders_bg);
let handler = service_fn(move |mut req| {
let tx_request = Arc::clone(&tx_request);
let tx_websocket = Arc::clone(&tx_websocket);
let response_senders = Arc::clone(&response_senders);
async move {
// FUTURE: Improve error messages when lua is busy and queue is full
if has_websocket_handler && is_upgrade_request(&req) {
let (response, ws) = match upgrade(&mut req, None) {
Err(_) => return Err(LuaError::runtime("Failed to upgrade websocket")),
Ok(v) => v,
};
if (tx_websocket.send(ws).await).is_err() {
return Err(LuaError::runtime("Lua handler is busy"));
}
Ok(response)
} else {
let processed = ProcessedRequest::from_request(req).await?;
let request_id = processed.id;
if (tx_request.send(processed).await).is_err() {
return Err(LuaError::runtime("Lua handler is busy"));
}
let (response_tx, response_rx) = oneshot::channel::<NetServeResponse>();
response_senders
.lock()
.await
.insert(request_id, response_tx);
match response_rx.await {
Err(_) => Err(LuaError::runtime("Internal Server Error")),
Ok(r) => r.into_response(),
}
}
}
});
async move { Ok::<_, Infallible>(handler) }
});
// Start up our service
sched.spawn(async move {
let result = builder
.http1_only(true) // Web sockets can only use http1
.http1_keepalive(true) // Web sockets must be kept alive
.serve(hyper_make_service)
.with_graceful_shutdown(async move {
shutdown_rx.recv().await;
});
if let Err(e) = result.await {
eprintln!("Net serve error: {e}")
}
});
// Spawn a local thread with access to lua and the same lifetime
sched.spawn_local(async move {
loop {
// Wait for either a request or a websocket to handle,
// if we got neither it means both channels were dropped
// and our server has stopped, either gracefully or panic
let (req, sock) = tokio::select! {
req = rx_request.recv() => (req, None),
sock = rx_websocket.recv() => (None, sock),
};
// NOTE: The closure here is not really necessary, we
// make the closure so that we can use the `?` operator
let handle_req_or_sock = || async {
match (req, sock) {
(None, None) => Ok::<_, LuaError>(true),
(Some(req), _) => {
let req_id = req.id;
let req_handler = config.handle_request.clone();
let req_table = req.into_lua_table(lua)?;
let thread_id = sched.push_back(lua, req_handler, req_table)?;
let thread_res = sched.wait_for_thread(lua, thread_id).await?;
let response = NetServeResponse::from_lua_multi(thread_res, lua)?;
let response_sender = response_senders_lua
.lock()
.await
.remove(&req_id)
.expect("Response channel was removed unexpectedly");
// NOTE: We ignore the error here, if the sender is no longer
// being listened to its because our client disconnected during
// handler being called, which is fine and should not emit errors
response_sender.send(response).ok();
Ok(false)
}
(_, Some(sock)) => {
let sock = sock.await.into_lua_err()?;
let sock_handler = config
.handle_web_socket
.as_ref()
.cloned()
.expect("Got web socket but web socket handler is missing");
let sock_table = NetWebSocket::new(sock).into_lua_table(lua)?;
// NOTE: Web socket handler does not need to send any
// response back, the websocket upgrade response is
// automatically sent above in the background thread(s)
let thread_id = sched.push_back(lua, sock_handler, sock_table)?;
let _thread_res = sched.wait_for_thread(lua, thread_id).await?;
Ok(false)
}
}
};
match handle_req_or_sock().await {
Ok(true) => break,
Ok(false) => continue,
Err(e) => lua.emit_error(e),
}
}
});
// Create a new read-only table that contains methods
// for manipulating server behavior and shutting it down
let handle_stop = move |_, _: ()| match shutdown_tx.try_send(()) {
Ok(_) => Ok(()),
Err(_) => Err(LuaError::RuntimeError(
"Server has already been stopped".to_string(),
)),
};
TableBuilder::new(lua)?
.with_function("stop", handle_stop)?
.build_readonly()
}

View file

@ -22,7 +22,7 @@ use hyper_tungstenite::{
}; };
use tokio_tungstenite::MaybeTlsStream; use tokio_tungstenite::MaybeTlsStream;
use crate::lune::lua::table::TableBuilder; use crate::lune::util::TableBuilder;
const WEB_SOCKET_IMPL_LUA: &str = r#" const WEB_SOCKET_IMPL_LUA: &str = r#"
return freeze(setmetatable({ return freeze(setmetatable({
@ -89,20 +89,19 @@ where
type NetWebSocketStreamClient = MaybeTlsStream<TcpStream>; type NetWebSocketStreamClient = MaybeTlsStream<TcpStream>;
impl NetWebSocket<NetWebSocketStreamClient> { impl NetWebSocket<NetWebSocketStreamClient> {
pub fn into_lua_table(self, lua: &'static Lua) -> LuaResult<LuaTable> { pub fn into_lua_table(self, lua: &'static Lua) -> LuaResult<LuaTable> {
let setmetatable = lua.globals().get::<_, LuaFunction>("setmetatable")?;
let table_freeze = lua
.globals()
.get::<_, LuaTable>("table")?
.get::<_, LuaFunction>("freeze")?;
let socket_env = TableBuilder::new(lua)? let socket_env = TableBuilder::new(lua)?
.with_value("websocket", self)? .with_value("websocket", self)?
.with_function("close_code", close_code::<NetWebSocketStreamClient>)? .with_function("close_code", close_code::<NetWebSocketStreamClient>)?
.with_async_function("close", close::<NetWebSocketStreamClient>)? .with_async_function("close", close::<NetWebSocketStreamClient>)?
.with_async_function("send", send::<NetWebSocketStreamClient>)? .with_async_function("send", send::<NetWebSocketStreamClient>)?
.with_async_function("next", next::<NetWebSocketStreamClient>)? .with_async_function("next", next::<NetWebSocketStreamClient>)?
.with_value( .with_value("setmetatable", setmetatable)?
"setmetatable", .with_value("freeze", table_freeze)?
lua.named_registry_value::<LuaFunction>("tab.setmeta")?,
)?
.with_value(
"freeze",
lua.named_registry_value::<LuaFunction>("tab.freeze")?,
)?
.build_readonly()?; .build_readonly()?;
Self::into_lua_table_with_env(lua, socket_env) Self::into_lua_table_with_env(lua, socket_env)
} }
@ -111,20 +110,19 @@ impl NetWebSocket<NetWebSocketStreamClient> {
type NetWebSocketStreamServer = Upgraded; type NetWebSocketStreamServer = Upgraded;
impl NetWebSocket<NetWebSocketStreamServer> { impl NetWebSocket<NetWebSocketStreamServer> {
pub fn into_lua_table(self, lua: &'static Lua) -> LuaResult<LuaTable> { pub fn into_lua_table(self, lua: &'static Lua) -> LuaResult<LuaTable> {
let setmetatable = lua.globals().get::<_, LuaFunction>("setmetatable")?;
let table_freeze = lua
.globals()
.get::<_, LuaTable>("table")?
.get::<_, LuaFunction>("freeze")?;
let socket_env = TableBuilder::new(lua)? let socket_env = TableBuilder::new(lua)?
.with_value("websocket", self)? .with_value("websocket", self)?
.with_function("close_code", close_code::<NetWebSocketStreamServer>)? .with_function("close_code", close_code::<NetWebSocketStreamServer>)?
.with_async_function("close", close::<NetWebSocketStreamServer>)? .with_async_function("close", close::<NetWebSocketStreamServer>)?
.with_async_function("send", send::<NetWebSocketStreamServer>)? .with_async_function("send", send::<NetWebSocketStreamServer>)?
.with_async_function("next", next::<NetWebSocketStreamServer>)? .with_async_function("next", next::<NetWebSocketStreamServer>)?
.with_value( .with_value("setmetatable", setmetatable)?
"setmetatable", .with_value("freeze", table_freeze)?
lua.named_registry_value::<LuaFunction>("tab.setmeta")?,
)?
.with_value(
"freeze",
lua.named_registry_value::<LuaFunction>("tab.freeze")?,
)?
.build_readonly()?; .build_readonly()?;
Self::into_lua_table_with_env(lua, socket_env) Self::into_lua_table_with_env(lua, socket_env)
} }

View file

@ -1,296 +0,0 @@
use std::{
collections::HashMap,
env::{self, consts},
path::{self, PathBuf},
process::{ExitCode, Stdio},
};
use directories::UserDirs;
use dunce::canonicalize;
use mlua::prelude::*;
use os_str_bytes::RawOsString;
use tokio::process::Command;
use crate::lune::lua::{
process::pipe_and_inherit_child_process_stdio, table::TableBuilder, task::TaskScheduler,
};
const PROCESS_EXIT_IMPL_LUA: &str = r#"
exit(...)
yield()
"#;
pub fn create(lua: &'static Lua, args_vec: Vec<String>) -> LuaResult<LuaTable> {
let cwd_str = {
let cwd = canonicalize(env::current_dir()?)?;
let cwd_str = cwd.to_string_lossy().to_string();
if !cwd_str.ends_with(path::MAIN_SEPARATOR) {
format!("{cwd_str}{}", path::MAIN_SEPARATOR)
} else {
cwd_str
}
};
// Create constants for OS & processor architecture
let os = lua.create_string(&consts::OS.to_lowercase())?;
let arch = lua.create_string(&consts::ARCH.to_lowercase())?;
// Create readonly args array
let args_tab = TableBuilder::new(lua)?
.with_sequential_values(args_vec)?
.build_readonly()?;
// Create proxied table for env that gets & sets real env vars
let env_tab = TableBuilder::new(lua)?
.with_metatable(
TableBuilder::new(lua)?
.with_function(LuaMetaMethod::Index.name(), process_env_get)?
.with_function(LuaMetaMethod::NewIndex.name(), process_env_set)?
.with_function(LuaMetaMethod::Iter.name(), process_env_iter)?
.build_readonly()?,
)?
.build_readonly()?;
// Create our process exit function, this is a bit involved since
// we have no way to yield from c / rust, we need to load a lua
// chunk that will set the exit code and yield for us instead
let process_exit_env_yield: LuaFunction = lua.named_registry_value("co.yield")?;
let process_exit_env_exit: LuaFunction = lua.create_function(|lua, code: Option<u8>| {
let exit_code = code.map_or(ExitCode::SUCCESS, ExitCode::from);
let sched = lua
.app_data_ref::<&TaskScheduler>()
.expect("Missing task scheduler - make sure it is added as a lua app data before the first scheduler resumption");
sched.set_exit_code(exit_code);
Ok(())
})?;
let process_exit = lua
.load(PROCESS_EXIT_IMPL_LUA)
.set_name("=process.exit")
.set_environment(
TableBuilder::new(lua)?
.with_value("yield", process_exit_env_yield)?
.with_value("exit", process_exit_env_exit)?
.build_readonly()?,
)
.into_function()?;
// Create the full process table
TableBuilder::new(lua)?
.with_value("os", os)?
.with_value("arch", arch)?
.with_value("args", args_tab)?
.with_value("cwd", cwd_str)?
.with_value("env", env_tab)?
.with_value("exit", process_exit)?
.with_async_function("spawn", process_spawn)?
.build_readonly()
}
fn process_env_get<'lua>(
lua: &'lua Lua,
(_, key): (LuaValue<'lua>, String),
) -> LuaResult<LuaValue<'lua>> {
match env::var_os(key) {
Some(value) => {
let raw_value = RawOsString::new(value);
Ok(LuaValue::String(
lua.create_string(raw_value.as_raw_bytes())?,
))
}
None => Ok(LuaValue::Nil),
}
}
fn process_env_set<'lua>(
_: &'lua Lua,
(_, key, value): (LuaValue<'lua>, String, Option<String>),
) -> LuaResult<()> {
// Make sure key is valid, otherwise set_var will panic
if key.is_empty() {
Err(LuaError::RuntimeError("Key must not be empty".to_string()))
} else if key.contains('=') {
Err(LuaError::RuntimeError(
"Key must not contain the equals character '='".to_string(),
))
} else if key.contains('\0') {
Err(LuaError::RuntimeError(
"Key must not contain the NUL character".to_string(),
))
} else {
match value {
Some(value) => {
// Make sure value is valid, otherwise set_var will panic
if value.contains('\0') {
Err(LuaError::RuntimeError(
"Value must not contain the NUL character".to_string(),
))
} else {
env::set_var(&key, &value);
Ok(())
}
}
None => {
env::remove_var(&key);
Ok(())
}
}
}
}
fn process_env_iter<'lua>(
lua: &'lua Lua,
(_, _): (LuaValue<'lua>, ()),
) -> LuaResult<LuaFunction<'lua>> {
let mut vars = env::vars_os().collect::<Vec<_>>().into_iter();
lua.create_function_mut(move |lua, _: ()| match vars.next() {
Some((key, value)) => {
let raw_key = RawOsString::new(key);
let raw_value = RawOsString::new(value);
Ok((
LuaValue::String(lua.create_string(raw_key.as_raw_bytes())?),
LuaValue::String(lua.create_string(raw_value.as_raw_bytes())?),
))
}
None => Ok((LuaValue::Nil, LuaValue::Nil)),
})
}
async fn process_spawn<'lua>(
lua: &'static Lua,
(mut program, args, options): (String, Option<Vec<String>>, Option<LuaTable<'lua>>),
) -> LuaResult<LuaTable<'lua>> {
// Parse any given options or create defaults
let (child_cwd, child_envs, child_shell, child_stdio_inherit) = match options {
Some(options) => {
let mut cwd = env::current_dir()?;
let mut envs = HashMap::new();
let mut shell = None;
let mut inherit = false;
match options.raw_get("cwd")? {
LuaValue::Nil => {}
LuaValue::String(s) => {
cwd = PathBuf::from(s.to_string_lossy().to_string());
// Substitute leading tilde (~) for the actual home dir
if cwd.starts_with("~") {
if let Some(user_dirs) = UserDirs::new() {
cwd = user_dirs.home_dir().join(cwd.strip_prefix("~").unwrap())
}
};
if !cwd.exists() {
return Err(LuaError::RuntimeError(
"Invalid value for option 'cwd' - path does not exist".to_string(),
));
}
}
value => {
return Err(LuaError::RuntimeError(format!(
"Invalid type for option 'cwd' - expected 'string', got '{}'",
value.type_name()
)))
}
}
match options.raw_get("env")? {
LuaValue::Nil => {}
LuaValue::Table(t) => {
for pair in t.pairs::<String, String>() {
let (k, v) = pair?;
envs.insert(k, v);
}
}
value => {
return Err(LuaError::RuntimeError(format!(
"Invalid type for option 'env' - expected 'table', got '{}'",
value.type_name()
)))
}
}
match options.raw_get("shell")? {
LuaValue::Nil => {}
LuaValue::String(s) => shell = Some(s.to_string_lossy().to_string()),
LuaValue::Boolean(true) => {
shell = match env::consts::FAMILY {
"unix" => Some("/bin/sh".to_string()),
"windows" => Some("/bin/sh".to_string()),
_ => None,
};
}
value => {
return Err(LuaError::RuntimeError(format!(
"Invalid type for option 'shell' - expected 'true' or 'string', got '{}'",
value.type_name()
)))
}
}
match options.raw_get("stdio")? {
LuaValue::Nil => {}
LuaValue::String(s) => {
match s.to_str()? {
"inherit" => {
inherit = true;
},
"default" => {
inherit = false;
}
_ => return Err(LuaError::RuntimeError(
format!("Invalid value for option 'stdio' - expected 'inherit' or 'default', got '{}'", s.to_string_lossy()),
))
}
}
value => {
return Err(LuaError::RuntimeError(format!(
"Invalid type for option 'stdio' - expected 'string', got '{}'",
value.type_name()
)))
}
}
Ok::<_, LuaError>((cwd, envs, shell, inherit))
}
None => Ok((env::current_dir()?, HashMap::new(), None, false)),
}?;
// Run a shell using the command param if wanted
let child_args = if let Some(shell) = child_shell {
let shell_args = match args {
Some(args) => vec!["-c".to_string(), format!("{} {}", program, args.join(" "))],
None => vec!["-c".to_string(), program],
};
program = shell;
Some(shell_args)
} else {
args
};
// Create command with the wanted options
let mut cmd = match child_args {
None => Command::new(program),
Some(args) => {
let mut cmd = Command::new(program);
cmd.args(args);
cmd
}
};
// Set dir to run in and env variables
cmd.current_dir(child_cwd);
cmd.envs(child_envs);
// Spawn the child process
let child = cmd
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
// Inherit the output and stderr if wanted
let result = if child_stdio_inherit {
pipe_and_inherit_child_process_stdio(child).await
} else {
let output = child.wait_with_output().await?;
Ok((output.status, output.stdout, output.stderr))
};
// Extract result
let (status, stdout, stderr) = result?;
// NOTE: If an exit code was not given by the child process,
// we default to 1 if it yielded any error output, otherwise 0
let code = status.code().unwrap_or(match stderr.is_empty() {
true => 0,
false => 1,
});
// Construct and return a readonly lua table with results
TableBuilder::new(lua)?
.with_value("ok", code == 0)?
.with_value("code", code)?
.with_value("stdout", lua.create_string(&stdout)?)?
.with_value("stderr", lua.create_string(&stderr)?)?
.build_readonly()
}

View file

@ -0,0 +1,219 @@
use std::{
env::{self, consts},
path,
process::{ExitStatus, Stdio},
};
use dunce::canonicalize;
use mlua::prelude::*;
use os_str_bytes::RawOsString;
use crate::lune::{scheduler::Scheduler, util::TableBuilder};
mod tee_writer;
mod pipe_inherit;
use pipe_inherit::pipe_and_inherit_child_process_stdio;
mod options;
use options::ProcessSpawnOptions;
const PROCESS_EXIT_IMPL_LUA: &str = r#"
exit(...)
yield()
"#;
pub fn create(lua: &'static Lua) -> LuaResult<LuaTable> {
let cwd_str = {
let cwd = canonicalize(env::current_dir()?)?;
let cwd_str = cwd.to_string_lossy().to_string();
if !cwd_str.ends_with(path::MAIN_SEPARATOR) {
format!("{cwd_str}{}", path::MAIN_SEPARATOR)
} else {
cwd_str
}
};
// Create constants for OS & processor architecture
let os = lua.create_string(&consts::OS.to_lowercase())?;
let arch = lua.create_string(&consts::ARCH.to_lowercase())?;
// Create readonly args array
let args_vec = lua
.app_data_ref::<Vec<String>>()
.ok_or_else(|| LuaError::runtime("Missing args vec in Lua app data"))?
.clone();
let args_tab = TableBuilder::new(lua)?
.with_sequential_values(args_vec)?
.build_readonly()?;
// Create proxied table for env that gets & sets real env vars
let env_tab = TableBuilder::new(lua)?
.with_metatable(
TableBuilder::new(lua)?
.with_function(LuaMetaMethod::Index.name(), process_env_get)?
.with_function(LuaMetaMethod::NewIndex.name(), process_env_set)?
.with_function(LuaMetaMethod::Iter.name(), process_env_iter)?
.build_readonly()?,
)?
.build_readonly()?;
// Create our process exit function, this is a bit involved since
// we have no way to yield from c / rust, we need to load a lua
// chunk that will set the exit code and yield for us instead
let coroutine_yield = lua
.globals()
.get::<_, LuaTable>("coroutine")?
.get::<_, LuaFunction>("yield")?;
let set_scheduler_exit_code = lua.create_function(|lua, code: Option<u8>| {
let sched = lua
.app_data_ref::<&Scheduler>()
.expect("Lua struct is missing scheduler");
sched.set_exit_code(code.unwrap_or_default());
Ok(())
})?;
let process_exit = lua
.load(PROCESS_EXIT_IMPL_LUA)
.set_name("=process.exit")
.set_environment(
TableBuilder::new(lua)?
.with_value("yield", coroutine_yield)?
.with_value("exit", set_scheduler_exit_code)?
.build_readonly()?,
)
.into_function()?;
// Create the full process table
TableBuilder::new(lua)?
.with_value("os", os)?
.with_value("arch", arch)?
.with_value("args", args_tab)?
.with_value("cwd", cwd_str)?
.with_value("env", env_tab)?
.with_value("exit", process_exit)?
.with_async_function("spawn", process_spawn)?
.build_readonly()
}
fn process_env_get<'lua>(
lua: &'lua Lua,
(_, key): (LuaValue<'lua>, String),
) -> LuaResult<LuaValue<'lua>> {
match env::var_os(key) {
Some(value) => {
let raw_value = RawOsString::new(value);
Ok(LuaValue::String(
lua.create_string(raw_value.as_raw_bytes())?,
))
}
None => Ok(LuaValue::Nil),
}
}
fn process_env_set<'lua>(
_: &'lua Lua,
(_, key, value): (LuaValue<'lua>, String, Option<String>),
) -> LuaResult<()> {
// Make sure key is valid, otherwise set_var will panic
if key.is_empty() {
Err(LuaError::RuntimeError("Key must not be empty".to_string()))
} else if key.contains('=') {
Err(LuaError::RuntimeError(
"Key must not contain the equals character '='".to_string(),
))
} else if key.contains('\0') {
Err(LuaError::RuntimeError(
"Key must not contain the NUL character".to_string(),
))
} else {
match value {
Some(value) => {
// Make sure value is valid, otherwise set_var will panic
if value.contains('\0') {
Err(LuaError::RuntimeError(
"Value must not contain the NUL character".to_string(),
))
} else {
env::set_var(&key, &value);
Ok(())
}
}
None => {
env::remove_var(&key);
Ok(())
}
}
}
}
fn process_env_iter<'lua>(
lua: &'lua Lua,
(_, _): (LuaValue<'lua>, ()),
) -> LuaResult<LuaFunction<'lua>> {
let mut vars = env::vars_os().collect::<Vec<_>>().into_iter();
lua.create_function_mut(move |lua, _: ()| match vars.next() {
Some((key, value)) => {
let raw_key = RawOsString::new(key);
let raw_value = RawOsString::new(value);
Ok((
LuaValue::String(lua.create_string(raw_key.as_raw_bytes())?),
LuaValue::String(lua.create_string(raw_value.as_raw_bytes())?),
))
}
None => Ok((LuaValue::Nil, LuaValue::Nil)),
})
}
async fn process_spawn(
lua: &Lua,
(program, args, options): (String, Option<Vec<String>>, ProcessSpawnOptions),
) -> LuaResult<LuaTable> {
/*
Spawn the new process in the background, letting the tokio
runtime place it on a different thread if possible / necessary
Note that we have to use our scheduler here, we can't
use anything like tokio::task::spawn because our lua
scheduler will not drive those futures to completion
*/
let sched = lua
.app_data_ref::<&Scheduler>()
.expect("Lua struct is missing scheduler");
let (status, stdout, stderr) = sched
.spawn(spawn_command(program, args, options))
.await
.expect("Failed to receive result of spawned process")?;
// NOTE: If an exit code was not given by the child process,
// we default to 1 if it yielded any error output, otherwise 0
let code = status.code().unwrap_or(match stderr.is_empty() {
true => 0,
false => 1,
});
// Construct and return a readonly lua table with results
TableBuilder::new(lua)?
.with_value("ok", code == 0)?
.with_value("code", code)?
.with_value("stdout", lua.create_string(&stdout)?)?
.with_value("stderr", lua.create_string(&stderr)?)?
.build_readonly()
}
async fn spawn_command(
program: String,
args: Option<Vec<String>>,
options: ProcessSpawnOptions,
) -> LuaResult<(ExitStatus, Vec<u8>, Vec<u8>)> {
let inherit_stdio = options.inherit_stdio;
let child = options
.into_command(program, args)
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?;
if inherit_stdio {
pipe_and_inherit_child_process_stdio(child).await
} else {
let output = child.wait_with_output().await?;
Ok((output.status, output.stdout, output.stderr))
}
}

View file

@ -0,0 +1,177 @@
use std::{
collections::HashMap,
env::{self},
path::PathBuf,
};
use directories::UserDirs;
use mlua::prelude::*;
use tokio::process::Command;
#[derive(Debug, Clone, Default)]
pub struct ProcessSpawnOptions {
pub(crate) cwd: Option<PathBuf>,
pub(crate) envs: HashMap<String, String>,
pub(crate) shell: Option<String>,
pub(crate) inherit_stdio: bool,
}
impl<'lua> FromLua<'lua> for ProcessSpawnOptions {
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
let mut this = Self::default();
let value = match value {
LuaValue::Nil => return Ok(this),
LuaValue::Table(t) => t,
_ => {
return Err(LuaError::FromLuaConversionError {
from: value.type_name(),
to: "ProcessSpawnOptions",
message: Some(format!(
"Invalid spawn options - expected table, got {}",
value.type_name()
)),
})
}
};
/*
If we got a working directory to use:
1. Substitute leading tilde (~) for the users home dir
2. Make sure it exists
*/
match value.get("cwd")? {
LuaValue::Nil => {}
LuaValue::String(s) => {
let mut cwd = PathBuf::from(s.to_str()?);
if let Ok(stripped) = cwd.strip_prefix("~") {
let user_dirs = UserDirs::new().ok_or_else(|| {
LuaError::runtime(
"Invalid value for option 'cwd' - failed to get home directory",
)
})?;
cwd = user_dirs.home_dir().join(stripped)
}
if !cwd.exists() {
return Err(LuaError::runtime(
"Invalid value for option 'cwd' - path does not exist",
));
};
this.cwd = Some(cwd);
}
value => {
return Err(LuaError::RuntimeError(format!(
"Invalid type for option 'cwd' - expected string, got '{}'",
value.type_name()
)))
}
}
/*
If we got environment variables, make sure they are strings
*/
match value.get("env")? {
LuaValue::Nil => {}
LuaValue::Table(e) => {
for pair in e.pairs::<String, String>() {
let (k, v) = pair.context("Environment variables must be strings")?;
this.envs.insert(k, v);
}
}
value => {
return Err(LuaError::RuntimeError(format!(
"Invalid type for option 'env' - expected table, got '{}'",
value.type_name()
)))
}
}
/*
If we got a shell to use:
1. When given as a string, use that literally
2. When set to true, use a default shell for the platform
*/
match value.get("shell")? {
LuaValue::Nil => {}
LuaValue::String(s) => this.shell = Some(s.to_string_lossy().to_string()),
LuaValue::Boolean(true) => {
this.shell = match env::consts::FAMILY {
"unix" => Some("/bin/sh".to_string()),
"windows" => Some("/bin/sh".to_string()),
_ => None,
};
}
value => {
return Err(LuaError::RuntimeError(format!(
"Invalid type for option 'shell' - expected 'true' or 'string', got '{}'",
value.type_name()
)))
}
}
/*
If we got options for stdio handling, make sure its one of the constant values
*/
match value.get("stdio")? {
LuaValue::Nil => {}
LuaValue::String(s) => match s.to_str()? {
"inherit" => this.inherit_stdio = true,
"default" => this.inherit_stdio = false,
_ => {
return Err(LuaError::RuntimeError(format!(
"Invalid value for option 'stdio' - expected 'inherit' or 'default', got '{}'",
s.to_string_lossy()
)))
}
},
value => {
return Err(LuaError::RuntimeError(format!(
"Invalid type for option 'stdio' - expected 'string', got '{}'",
value.type_name()
)))
}
}
Ok(this)
}
}
impl ProcessSpawnOptions {
pub fn into_command(self, program: impl Into<String>, args: Option<Vec<String>>) -> Command {
let mut program = program.into();
// Run a shell using the command param if wanted
let pargs = match self.shell {
None => args,
Some(shell) => {
let shell_args = match args {
Some(args) => vec!["-c".to_string(), format!("{} {}", program, args.join(" "))],
None => vec!["-c".to_string(), program.to_string()],
};
program = shell.to_string();
Some(shell_args)
}
};
// Create command with the wanted options
let mut cmd = match pargs {
None => Command::new(program),
Some(args) => {
let mut cmd = Command::new(program);
cmd.args(args);
cmd
}
};
// Set dir to run in and env variables
if let Some(cwd) = self.cwd {
cmd.current_dir(cwd);
}
if !self.envs.is_empty() {
cmd.envs(self.envs);
}
cmd
}
}

View file

@ -3,8 +3,7 @@ use std::process::ExitStatus;
use mlua::prelude::*; use mlua::prelude::*;
use tokio::{io, process::Child, task}; use tokio::{io, process::Child, task};
mod tee_writer; use super::tee_writer::AsyncTeeWriter;
use tee_writer::AsyncTeeWriter;
pub async fn pipe_and_inherit_child_process_stdio( pub async fn pipe_and_inherit_child_process_stdio(
mut child: Child, mut child: Child,

View file

@ -1,25 +1,28 @@
use mlua::prelude::*; use mlua::prelude::*;
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use crate::roblox::{ use crate::{
lune::util::TableBuilder,
roblox::{
self, self,
document::{Document, DocumentError, DocumentFormat, DocumentKind}, document::{Document, DocumentError, DocumentFormat, DocumentKind},
instance::Instance, instance::Instance,
reflection::Database as ReflectionDatabase, reflection::Database as ReflectionDatabase,
},
}; };
use tokio::task; use tokio::task;
use crate::lune::lua::table::TableBuilder;
static REFLECTION_DATABASE: OnceCell<ReflectionDatabase> = OnceCell::new(); static REFLECTION_DATABASE: OnceCell<ReflectionDatabase> = OnceCell::new();
pub fn create(lua: &'static Lua) -> LuaResult<LuaTable> { pub fn create(lua: &'static Lua) -> LuaResult<LuaTable> {
let mut roblox_constants = Vec::new(); let mut roblox_constants = Vec::new();
let roblox_module = roblox::module(lua)?; let roblox_module = roblox::module(lua)?;
for pair in roblox_module.pairs::<LuaValue, LuaValue>() { for pair in roblox_module.pairs::<LuaValue, LuaValue>() {
roblox_constants.push(pair?); roblox_constants.push(pair?);
} }
TableBuilder::new(lua)? TableBuilder::new(lua)?
.with_values(roblox_constants)? .with_values(roblox_constants)?
.with_async_function("deserializePlace", deserialize_place)? .with_async_function("deserializePlace", deserialize_place)?

View file

@ -10,6 +10,7 @@ const LUA_SERIALIZE_OPTIONS: LuaSerializeOptions = LuaSerializeOptions::new()
.serialize_unit_to_null(false); .serialize_unit_to_null(false);
const LUA_DESERIALIZE_OPTIONS: LuaDeserializeOptions = LuaDeserializeOptions::new() const LUA_DESERIALIZE_OPTIONS: LuaDeserializeOptions = LuaDeserializeOptions::new()
.sort_keys(true)
.deny_recursive_tables(false) .deny_recursive_tables(false)
.deny_unsupported_types(true); .deny_unsupported_types(true);

View file

@ -1,11 +1,12 @@
use mlua::prelude::*; use mlua::prelude::*;
use crate::lune::lua::{ pub(super) mod compress_decompress;
serde::{ pub(super) mod encode_decode;
compress, decompress, CompressDecompressFormat, EncodeDecodeConfig, EncodeDecodeFormat,
}, use compress_decompress::{compress, decompress, CompressDecompressFormat};
table::TableBuilder, use encode_decode::{EncodeDecodeConfig, EncodeDecodeFormat};
};
use crate::lune::util::TableBuilder;
pub fn create(lua: &'static Lua) -> LuaResult<LuaTable> { pub fn create(lua: &'static Lua) -> LuaResult<LuaTable> {
TableBuilder::new(lua)? TableBuilder::new(lua)?

View file

@ -1,59 +1,68 @@
use dialoguer::{theme::ColorfulTheme, Confirm, Input, MultiSelect, Select};
use mlua::prelude::*; use mlua::prelude::*;
use dialoguer::{theme::ColorfulTheme, Confirm, Input, MultiSelect, Select};
use tokio::{ use tokio::{
io::{self, AsyncWriteExt}, io::{self, AsyncWriteExt},
task, task,
}; };
use crate::lune::lua::{ use crate::lune::util::{
stdio::{
formatting::{ formatting::{
format_style, pretty_format_multi_value, style_from_color_str, style_from_style_str, format_style, pretty_format_multi_value, style_from_color_str, style_from_style_str,
}, },
prompt::{PromptKind, PromptOptions, PromptResult}, TableBuilder,
},
table::TableBuilder,
}; };
pub fn create(lua: &'static Lua) -> LuaResult<LuaTable> { mod prompt;
use prompt::{PromptKind, PromptOptions, PromptResult};
pub fn create(lua: &'static Lua) -> LuaResult<LuaTable<'_>> {
TableBuilder::new(lua)? TableBuilder::new(lua)?
.with_function("color", |_, color: String| { .with_function("color", stdio_color)?
.with_function("style", stdio_style)?
.with_function("format", stdio_format)?
.with_async_function("write", stdio_write)?
.with_async_function("ewrite", stdio_ewrite)?
.with_async_function("prompt", stdio_prompt)?
.build_readonly()
}
fn stdio_color(_: &Lua, color: String) -> LuaResult<String> {
let ansi_string = format_style(style_from_color_str(&color)?); let ansi_string = format_style(style_from_color_str(&color)?);
Ok(ansi_string) Ok(ansi_string)
})? }
.with_function("style", |_, style: String| {
let ansi_string = format_style(style_from_style_str(&style)?); fn stdio_style(_: &Lua, color: String) -> LuaResult<String> {
let ansi_string = format_style(style_from_style_str(&color)?);
Ok(ansi_string) Ok(ansi_string)
})? }
.with_function("format", |_, args: LuaMultiValue| {
fn stdio_format(_: &Lua, args: LuaMultiValue) -> LuaResult<String> {
pretty_format_multi_value(&args) pretty_format_multi_value(&args)
})? }
.with_async_function("write", |_, s: LuaString| async move {
async fn stdio_write(_: &Lua, s: LuaString<'_>) -> LuaResult<()> {
let mut stdout = io::stdout(); let mut stdout = io::stdout();
stdout.write_all(s.as_bytes()).await?; stdout.write_all(s.as_bytes()).await?;
stdout.flush().await?; stdout.flush().await?;
Ok(()) Ok(())
})? }
.with_async_function("ewrite", |_, s: LuaString| async move {
async fn stdio_ewrite(_: &Lua, s: LuaString<'_>) -> LuaResult<()> {
let mut stderr = io::stderr(); let mut stderr = io::stderr();
stderr.write_all(s.as_bytes()).await?; stderr.write_all(s.as_bytes()).await?;
stderr.flush().await?; stderr.flush().await?;
Ok(()) Ok(())
})? }
.with_async_function("prompt", |_, options: PromptOptions| async move {
async fn stdio_prompt(_: &Lua, options: PromptOptions) -> LuaResult<PromptResult> {
task::spawn_blocking(move || prompt(options)) task::spawn_blocking(move || prompt(options))
.await .await
.into_lua_err()? .into_lua_err()?
})?
.build_readonly()
}
fn prompt_theme() -> ColorfulTheme {
ColorfulTheme::default()
} }
fn prompt(options: PromptOptions) -> LuaResult<PromptResult> { fn prompt(options: PromptOptions) -> LuaResult<PromptResult> {
let theme = prompt_theme(); let theme = ColorfulTheme::default();
match options.kind { match options.kind {
PromptKind::Text => { PromptKind::Text => {
let input: String = Input::with_theme(&theme) let input: String = Input::with_theme(&theme)
@ -74,7 +83,7 @@ fn prompt(options: PromptOptions) -> LuaResult<PromptResult> {
Ok(PromptResult::Boolean(result)) Ok(PromptResult::Boolean(result))
} }
PromptKind::Select => { PromptKind::Select => {
let chosen = Select::with_theme(&prompt_theme()) let chosen = Select::with_theme(&theme)
.with_prompt(&options.text.unwrap_or_default()) .with_prompt(&options.text.unwrap_or_default())
.items(&options.options.expect("Missing options in prompt options")) .items(&options.options.expect("Missing options in prompt options"))
.interact_opt()?; .interact_opt()?;
@ -84,7 +93,7 @@ fn prompt(options: PromptOptions) -> LuaResult<PromptResult> {
}) })
} }
PromptKind::MultiSelect => { PromptKind::MultiSelect => {
let chosen = MultiSelect::with_theme(&prompt_theme()) let chosen = MultiSelect::with_theme(&theme)
.with_prompt(&options.text.unwrap_or_default()) .with_prompt(&options.text.unwrap_or_default())
.items(&options.options.expect("Missing options in prompt options")) .items(&options.options.expect("Missing options in prompt options"))
.interact_opt()?; .interact_opt()?;

View file

@ -1,168 +0,0 @@
use mlua::prelude::*;
use crate::lune::lua::{
async_ext::LuaAsyncExt,
table::TableBuilder,
task::{
LuaThreadOrFunction, LuaThreadOrTaskReference, TaskKind, TaskReference, TaskScheduler,
TaskSchedulerScheduleExt,
},
};
const SPAWN_IMPL_LUA: &str = r#"
scheduleNext(thread())
local task = scheduleNext(...)
yield()
return task
"#;
pub fn create(lua: &'static Lua) -> LuaResult<LuaTable<'static>> {
lua.app_data_ref::<&TaskScheduler>()
.expect("Missing task scheduler in app data");
/*
1. Schedule the current thread at the front
2. Schedule the wanted task arg at the front,
the previous schedule now comes right after
3. Give control over to the scheduler, which will
resume the above tasks in order when its ready
The spawn function needs special treatment,
we need to yield right away to allow the
spawned task to run until first yield
*/
let task_spawn_env_yield: LuaFunction = lua.named_registry_value("co.yield")?;
let task_spawn = lua
.load(SPAWN_IMPL_LUA)
.set_name("task.spawn")
.set_environment(
TableBuilder::new(lua)?
.with_function("thread", |lua, _: ()| Ok(lua.current_thread()))?
.with_value("yield", task_spawn_env_yield)?
.with_function(
"scheduleNext",
|lua, (tof, args): (LuaThreadOrFunction, LuaMultiValue)| {
let sched = lua.app_data_ref::<&TaskScheduler>().unwrap();
sched.schedule_blocking(tof.into_thread(lua)?, args)
},
)?
.build_readonly()?,
)
.into_function()?;
// Functions in the built-in coroutine library also need to be
// replaced, these are a bit different than the ones above because
// calling resume or the function that wrap returns must return
// whatever lua value(s) that the thread or task yielded back
let globals = lua.globals();
let coroutine = globals.get::<_, LuaTable>("coroutine")?;
coroutine.set("status", lua.create_function(coroutine_status)?)?;
coroutine.set("resume", lua.create_function(coroutine_resume)?)?;
coroutine.set("wrap", lua.create_function(coroutine_wrap)?)?;
// All good, return the task scheduler lib
TableBuilder::new(lua)?
.with_value("wait", lua.create_waiter_function()?)?
.with_value("spawn", task_spawn)?
.with_function("cancel", task_cancel)?
.with_function("defer", task_defer)?
.with_function("delay", task_delay)?
.build_readonly()
}
/*
Basic task functions
*/
fn task_cancel(lua: &Lua, task: LuaUserDataRef<TaskReference>) -> LuaResult<()> {
let sched = lua.app_data_ref::<&TaskScheduler>().unwrap();
sched.remove_task(*task)?;
Ok(())
}
fn task_defer(
lua: &Lua,
(tof, args): (LuaThreadOrFunction, LuaMultiValue),
) -> LuaResult<TaskReference> {
let sched = lua.app_data_ref::<&TaskScheduler>().unwrap();
sched.schedule_blocking_deferred(tof.into_thread(lua)?, args)
}
fn task_delay(
lua: &Lua,
(secs, tof, args): (f64, LuaThreadOrFunction, LuaMultiValue),
) -> LuaResult<TaskReference> {
let sched = lua.app_data_ref::<&TaskScheduler>().unwrap();
sched.schedule_blocking_after_seconds(secs, tof.into_thread(lua)?, args)
}
/*
Coroutine library overrides for compat with task scheduler
*/
fn coroutine_status<'a>(
lua: &'a Lua,
value: LuaThreadOrTaskReference<'a>,
) -> LuaResult<LuaString<'a>> {
Ok(match value {
LuaThreadOrTaskReference::Thread(thread) => {
let get_status: LuaFunction = lua.named_registry_value("co.status")?;
get_status.call(thread)?
}
LuaThreadOrTaskReference::TaskReference(task) => {
let sched = lua.app_data_ref::<&TaskScheduler>().unwrap();
sched
.get_task_status(task)
.unwrap_or_else(|| lua.create_string("dead").unwrap())
}
})
}
fn coroutine_resume<'lua>(
lua: &'lua Lua,
(value, arguments): (LuaThreadOrTaskReference, LuaMultiValue<'lua>),
) -> LuaResult<(bool, LuaMultiValue<'lua>)> {
let sched = lua.app_data_ref::<&TaskScheduler>().unwrap();
if sched.current_task().is_none() {
return Err(LuaError::RuntimeError(
"No current task to inherit".to_string(),
));
}
let current = sched.current_task().unwrap();
let result = match value {
LuaThreadOrTaskReference::Thread(t) => {
let task = sched.create_task(TaskKind::Instant, t, Some(arguments), true)?;
sched.resume_task(task, None)
}
LuaThreadOrTaskReference::TaskReference(t) => sched.resume_task(t, Some(Ok(arguments))),
};
sched.force_set_current_task(Some(current));
match result {
Ok(rets) => Ok((true, rets.1)),
Err(e) => Ok((false, e.into_lua_multi(lua)?)),
}
}
fn coroutine_wrap<'lua>(lua: &'lua Lua, func: LuaFunction) -> LuaResult<LuaFunction<'lua>> {
let task = lua.app_data_ref::<&TaskScheduler>().unwrap().create_task(
TaskKind::Instant,
lua.create_thread(func)?,
None,
false,
)?;
lua.create_function(move |lua, args: LuaMultiValue| {
let sched = lua.app_data_ref::<&TaskScheduler>().unwrap();
if sched.current_task().is_none() {
return Err(LuaError::RuntimeError(
"No current task to inherit".to_string(),
));
}
let current = sched.current_task().unwrap();
let result = lua
.app_data_ref::<&TaskScheduler>()
.unwrap()
.resume_task(task, Some(Ok(args)));
sched.force_set_current_task(Some(current));
match result {
Ok(rets) => Ok(rets.1),
Err(e) => Err(e),
}
})
}

View file

@ -0,0 +1,126 @@
use std::time::Duration;
use mlua::prelude::*;
use tokio::time::{self, Instant};
use crate::lune::{scheduler::Scheduler, util::TableBuilder};
mod tof;
use tof::LuaThreadOrFunction;
/*
The spawn function needs special treatment,
we need to yield right away to allow the
spawned task to run until first yield
1. Schedule this current thread at the front
2. Schedule given thread/function at the front,
the previous schedule now comes right after
3. Give control over to the scheduler, which will
resume the above tasks in order when its ready
*/
const SPAWN_IMPL_LUA: &str = r#"
push(currentThread())
local thread = push(...)
yield()
return thread
"#;
pub fn create(lua: &'static Lua) -> LuaResult<LuaTable<'_>> {
let coroutine_running = lua
.globals()
.get::<_, LuaTable>("coroutine")?
.get::<_, LuaFunction>("running")?;
let coroutine_yield = lua
.globals()
.get::<_, LuaTable>("coroutine")?
.get::<_, LuaFunction>("yield")?;
let push_front =
lua.create_function(|lua, (tof, args): (LuaThreadOrFunction, LuaMultiValue)| {
let thread = tof.into_thread(lua)?;
let sched = lua
.app_data_ref::<&Scheduler>()
.expect("Lua struct is missing scheduler");
sched.push_front(lua, thread.clone(), args)?;
Ok(thread)
})?;
let task_spawn_env = TableBuilder::new(lua)?
.with_value("currentThread", coroutine_running)?
.with_value("yield", coroutine_yield)?
.with_value("push", push_front)?
.build_readonly()?;
let task_spawn = lua
.load(SPAWN_IMPL_LUA)
.set_name("task.spawn")
.set_environment(task_spawn_env)
.into_function()?;
TableBuilder::new(lua)?
.with_function("cancel", task_cancel)?
.with_function("defer", task_defer)?
.with_function("delay", task_delay)?
.with_value("spawn", task_spawn)?
.with_async_function("wait", task_wait)?
.build_readonly()
}
fn task_cancel(lua: &Lua, thread: LuaThread) -> LuaResult<()> {
let close = lua
.globals()
.get::<_, LuaTable>("coroutine")?
.get::<_, LuaFunction>("close")?;
match close.call(thread) {
Err(LuaError::CoroutineInactive) => Ok(()),
Err(e) => Err(e),
Ok(()) => Ok(()),
}
}
fn task_defer<'lua>(
lua: &'lua Lua,
(tof, args): (LuaThreadOrFunction<'lua>, LuaMultiValue<'_>),
) -> LuaResult<LuaThread<'lua>> {
let thread = tof.into_thread(lua)?;
let sched = lua
.app_data_ref::<&Scheduler>()
.expect("Lua struct is missing scheduler");
sched.push_back(lua, thread.clone(), args)?;
Ok(thread)
}
// FIXME: `self` escapes outside of method because we are borrowing `tof` and
// `args` when we call `schedule_future_thread` in the lua function body below
// For now we solve this by using the 'static lifetime bound in the impl
fn task_delay<'lua>(
lua: &'lua Lua,
(secs, tof, args): (f64, LuaThreadOrFunction<'lua>, LuaMultiValue<'lua>),
) -> LuaResult<LuaThread<'lua>>
where
'lua: 'static,
{
let thread = tof.into_thread(lua)?;
let sched = lua
.app_data_ref::<&Scheduler>()
.expect("Lua struct is missing scheduler");
let thread2 = thread.clone();
sched.spawn_thread(lua, thread.clone(), async move {
let duration = Duration::from_secs_f64(secs);
time::sleep(duration).await;
sched.push_back(lua, thread2, args)?;
Ok(())
})?;
Ok(thread)
}
async fn task_wait(_: &Lua, secs: Option<f64>) -> LuaResult<f64> {
let duration = Duration::from_secs_f64(secs.unwrap_or_default());
let before = Instant::now();
time::sleep(duration).await;
let after = Instant::now();
Ok((after - before).as_secs_f64())
}

View file

@ -0,0 +1,30 @@
use mlua::prelude::*;
#[derive(Clone)]
pub(super) enum LuaThreadOrFunction<'lua> {
Thread(LuaThread<'lua>),
Function(LuaFunction<'lua>),
}
impl<'lua> LuaThreadOrFunction<'lua> {
pub(super) fn into_thread(self, lua: &'lua Lua) -> LuaResult<LuaThread<'lua>> {
match self {
Self::Thread(t) => Ok(t),
Self::Function(f) => lua.create_thread(f),
}
}
}
impl<'lua> FromLua<'lua> for LuaThreadOrFunction<'lua> {
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
match value {
LuaValue::Thread(t) => Ok(Self::Thread(t)),
LuaValue::Function(f) => Ok(Self::Function(f)),
value => Err(LuaError::FromLuaConversionError {
from: value.type_name(),
to: "LuaThreadOrFunction",
message: Some("Expected thread or function".to_string()),
}),
}
}
}

View file

@ -1,80 +0,0 @@
use mlua::prelude::*;
use std::io::{self, Write as _};
#[cfg(feature = "roblox")]
use crate::roblox::datatypes::extension::RobloxUserdataTypenameExt;
use crate::lune::lua::{
stdio::formatting::{format_label, pretty_format_multi_value},
task::TaskReference,
};
pub fn print(_: &Lua, args: LuaMultiValue) -> LuaResult<()> {
let formatted = format!("{}\n", pretty_format_multi_value(&args)?);
let mut stdout = io::stdout();
stdout.write_all(formatted.as_bytes())?;
stdout.flush()?;
Ok(())
}
pub fn warn(_: &Lua, args: LuaMultiValue) -> LuaResult<()> {
let formatted = format!(
"{}\n{}",
format_label("warn"),
pretty_format_multi_value(&args)?
);
let mut stdout = io::stdout();
stdout.write_all(formatted.as_bytes())?;
stdout.flush()?;
Ok(())
}
// HACK: We need to preserve the default behavior of
// the lua error function, for pcall and such, which
// is really tricky to do from scratch so we will
// just proxy the default function here instead
pub fn error(lua: &Lua, (arg, level): (LuaValue, Option<u32>)) -> LuaResult<()> {
let error: LuaFunction = lua.named_registry_value("error")?;
let trace: LuaFunction = lua.named_registry_value("dbg.trace")?;
error.call((
LuaError::CallbackError {
traceback: format!("override traceback:{}", trace.call::<_, String>(())?),
cause: LuaError::external(format!(
"{}\n{}",
format_label("error"),
pretty_format_multi_value(&arg.into_lua_multi(lua)?)?
))
.into(),
},
level,
))?;
Ok(())
}
pub fn proxy_type<'lua>(lua: &'lua Lua, value: LuaValue<'lua>) -> LuaResult<LuaString<'lua>> {
if let LuaValue::UserData(u) = &value {
if u.is::<TaskReference>() {
return lua.create_string("thread");
}
}
lua.named_registry_value::<LuaFunction>("type")?.call(value)
}
pub fn proxy_typeof<'lua>(lua: &'lua Lua, value: LuaValue<'lua>) -> LuaResult<LuaString<'lua>> {
if let LuaValue::UserData(u) = &value {
if u.is::<TaskReference>() {
return lua.create_string("thread");
}
#[cfg(feature = "roblox")]
{
if let Some(type_name) = u.roblox_type_name() {
return lua.create_string(type_name);
}
}
}
lua.named_registry_value::<LuaFunction>("typeof")?
.call(value)
}
// TODO: Add an override for tostring that formats errors in a nicer way

View file

@ -5,7 +5,7 @@ use std::{
use mlua::prelude::*; use mlua::prelude::*;
use crate::lune::lua::stdio::formatting::pretty_format_luau_error; use crate::lune::util::formatting::pretty_format_luau_error;
/** /**
An opaque error type for formatted lua errors. An opaque error type for formatted lua errors.
@ -16,6 +16,8 @@ pub struct LuneError {
disable_colors: bool, disable_colors: bool,
} }
// TODO: Rename this struct to "RuntimeError" instead for
// the next breaking release, it's a more fitting name
impl LuneError { impl LuneError {
/** /**
Enables colorization of the error message when formatted using the [`Display`] trait. Enables colorization of the error message when formatted using the [`Display`] trait.
@ -64,6 +66,15 @@ impl From<LuaError> for LuneError {
} }
} }
impl From<&LuaError> for LuneError {
fn from(value: &LuaError) -> Self {
Self {
error: value.clone(),
disable_colors: false,
}
}
}
impl Display for LuneError { impl Display for LuneError {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
write!( write!(

View file

@ -0,0 +1,5 @@
use mlua::prelude::*;
pub fn create(lua: &Lua) -> LuaResult<impl IntoLua<'_>> {
lua.create_table()
}

28
src/lune/globals/mod.rs Normal file
View file

@ -0,0 +1,28 @@
use mlua::prelude::*;
use super::util::TableBuilder;
mod g_table;
mod print;
mod require;
mod r#typeof;
mod version;
mod warn;
pub fn inject_all(lua: &'static Lua) -> LuaResult<()> {
let all = TableBuilder::new(lua)?
.with_value("_G", g_table::create(lua)?)?
.with_value("_VERSION", version::create(lua)?)?
.with_value("print", print::create(lua)?)?
.with_value("require", require::create(lua)?)?
.with_value("typeof", r#typeof::create(lua)?)?
.with_value("warn", warn::create(lua)?)?
.build_readonly()?;
for res in all.pairs() {
let (key, value): (LuaValue, LuaValue) = res.unwrap();
lua.globals().set(key, value)?;
}
Ok(())
}

14
src/lune/globals/print.rs Normal file
View file

@ -0,0 +1,14 @@
use mlua::prelude::*;
use tokio::io::{self, AsyncWriteExt};
use crate::lune::{scheduler::LuaSchedulerExt, util::formatting::pretty_format_multi_value};
pub fn create(lua: &'static Lua) -> LuaResult<impl IntoLua<'_>> {
lua.create_async_function(|_, args: LuaMultiValue| async move {
let formatted = format!("{}\n", pretty_format_multi_value(&args)?);
let mut stdout = io::stdout();
stdout.write_all(formatted.as_bytes()).await?;
stdout.flush().await?;
Ok(())
})
}

View file

@ -0,0 +1,16 @@
use mlua::prelude::*;
use super::context::*;
pub(super) async fn require<'lua, 'ctx>(
_ctx: &'ctx RequireContext<'lua>,
alias: &str,
name: &str,
) -> LuaResult<LuaMultiValue<'lua>>
where
'lua: 'ctx,
{
Err(LuaError::runtime(format!(
"TODO: Support require for built-in libraries (tried to require '{name}' with alias '{alias}')"
)))
}

View file

@ -0,0 +1,14 @@
use mlua::prelude::*;
use super::context::*;
pub(super) async fn require<'lua, 'ctx>(
ctx: &'ctx RequireContext<'lua>,
name: &str,
) -> LuaResult<LuaMultiValue<'lua>>
where
'lua: 'ctx,
'lua: 'static, // FIXME: Remove static lifetime bound here when builtin libraries no longer need it
{
ctx.load_builtin(name)
}

View file

@ -0,0 +1,310 @@
use std::{
collections::HashMap,
env,
path::{Path, PathBuf},
sync::Arc,
};
use mlua::prelude::*;
use tokio::{
fs,
sync::{
broadcast::{self, Sender},
Mutex as AsyncMutex,
},
};
use crate::lune::{
builtins::LuneBuiltin,
scheduler::{IntoLuaThread, Scheduler},
};
/**
Context containing cached results for all `require` operations.
The cache uses absolute paths, so any given relative
path will first be transformed into an absolute path.
*/
#[derive(Debug, Clone)]
pub(super) struct RequireContext<'lua> {
lua: &'lua Lua,
use_cwd_relative_paths: bool,
working_directory: PathBuf,
cache_builtins: Arc<AsyncMutex<HashMap<LuneBuiltin, LuaResult<LuaRegistryKey>>>>,
cache_results: Arc<AsyncMutex<HashMap<PathBuf, LuaResult<LuaRegistryKey>>>>,
cache_pending: Arc<AsyncMutex<HashMap<PathBuf, Sender<()>>>>,
}
impl<'lua> RequireContext<'lua> {
/**
Creates a new require context for the given [`Lua`] struct.
Note that this require context is global and only one require
context should be created per [`Lua`] struct, creating more
than one context may lead to undefined require-behavior.
*/
pub fn new(lua: &'lua Lua) -> Self {
// FUTURE: We could load some kind of config or env var
// to check if we should be using cwd-relative paths
let cwd = env::current_dir().expect("Failed to get current working directory");
Self {
lua,
use_cwd_relative_paths: false,
working_directory: cwd,
cache_builtins: Arc::new(AsyncMutex::new(HashMap::new())),
cache_results: Arc::new(AsyncMutex::new(HashMap::new())),
cache_pending: Arc::new(AsyncMutex::new(HashMap::new())),
}
}
/**
Resolves the given `source` and `path` into require paths
to use, based on the current require context settings.
This will resolve path segments such as `./`, `../`, ..., and
if the resolved path is not an absolute path, will create an
absolute path by prepending the current working directory.
*/
pub fn resolve_paths(
&self,
source: impl AsRef<str>,
path: impl AsRef<str>,
) -> LuaResult<(PathBuf, PathBuf)> {
let path = if self.use_cwd_relative_paths {
PathBuf::from(path.as_ref())
} else {
PathBuf::from(source.as_ref())
.parent()
.ok_or_else(|| LuaError::runtime("Failed to get parent path of source"))?
.join(path.as_ref())
};
let rel_path = path_clean::clean(path);
let abs_path = if rel_path.is_absolute() {
rel_path.to_path_buf()
} else {
self.working_directory.join(&rel_path)
};
Ok((rel_path, abs_path))
}
/**
Checks if the given path has a cached require result.
*/
pub fn is_cached(&self, abs_path: impl AsRef<Path>) -> LuaResult<bool> {
let is_cached = self
.cache_results
.try_lock()
.expect("RequireContext may not be used from multiple threads")
.contains_key(abs_path.as_ref());
Ok(is_cached)
}
/**
Checks if the given path is currently being used in `require`.
*/
pub fn is_pending(&self, abs_path: impl AsRef<Path>) -> LuaResult<bool> {
let is_pending = self
.cache_pending
.try_lock()
.expect("RequireContext may not be used from multiple threads")
.contains_key(abs_path.as_ref());
Ok(is_pending)
}
/**
Gets the resulting value from the require cache.
Will panic if the path has not been cached, use [`is_cached`] first.
*/
pub fn get_from_cache(&self, abs_path: impl AsRef<Path>) -> LuaResult<LuaMultiValue<'lua>> {
let results = self
.cache_results
.try_lock()
.expect("RequireContext may not be used from multiple threads");
let cached = results
.get(abs_path.as_ref())
.expect("Path does not exist in results cache");
match cached {
Err(e) => Err(e.clone()),
Ok(k) => {
let multi_vec = self
.lua
.registry_value::<Vec<LuaValue>>(k)
.expect("Missing require result in lua registry");
Ok(LuaMultiValue::from_vec(multi_vec))
}
}
}
/**
Waits for the resulting value from the require cache.
Will panic if the path has not been cached, use [`is_cached`] first.
*/
pub async fn wait_for_cache(
&self,
abs_path: impl AsRef<Path>,
) -> LuaResult<LuaMultiValue<'lua>> {
let mut thread_recv = {
let pending = self
.cache_pending
.try_lock()
.expect("RequireContext may not be used from multiple threads");
let thread_id = pending
.get(abs_path.as_ref())
.expect("Path is not currently pending require");
thread_id.subscribe()
};
thread_recv.recv().await.into_lua_err()?;
self.get_from_cache(abs_path.as_ref())
}
async fn load(
&self,
abs_path: impl AsRef<Path>,
rel_path: impl AsRef<Path>,
) -> LuaResult<LuaRegistryKey> {
let abs_path = abs_path.as_ref();
let rel_path = rel_path.as_ref();
let sched = self
.lua
.app_data_ref::<&Scheduler>()
.expect("Lua struct is missing scheduler");
// Read the file at the given path, try to parse and
// load it into a new lua thread that we can schedule
let file_contents = fs::read(&abs_path).await?;
let file_thread = self
.lua
.load(file_contents)
.set_name(rel_path.to_string_lossy().to_string())
.into_function()?
.into_lua_thread(self.lua)?;
// Schedule the thread to run, wait for it to finish running
let thread_id = sched.push_back(self.lua, file_thread, ())?;
let thread_res = sched.wait_for_thread(self.lua, thread_id).await;
// Return the result of the thread, storing any lua value(s) in the registry
match thread_res {
Err(e) => Err(e),
Ok(v) => {
let multi_vec = v.into_vec();
let multi_key = self
.lua
.create_registry_value(multi_vec)
.expect("Failed to store require result in registry - out of memory");
Ok(multi_key)
}
}
}
/**
Loads (requires) the file at the given path.
*/
pub async fn load_with_caching(
&self,
abs_path: impl AsRef<Path>,
rel_path: impl AsRef<Path>,
) -> LuaResult<LuaMultiValue<'lua>> {
let abs_path = abs_path.as_ref();
let rel_path = rel_path.as_ref();
// Set this abs path as currently pending
let (broadcast_tx, _) = broadcast::channel(1);
self.cache_pending
.try_lock()
.expect("RequireContext may not be used from multiple threads")
.insert(abs_path.to_path_buf(), broadcast_tx);
// Try to load at this abs path
let load_res = self.load(abs_path, rel_path).await;
let load_val = match &load_res {
Err(e) => Err(e.clone()),
Ok(k) => {
let multi_vec = self
.lua
.registry_value::<Vec<LuaValue>>(k)
.expect("Failed to fetch require result from registry");
Ok(LuaMultiValue::from_vec(multi_vec))
}
};
// NOTE: We use the async lock and not try_lock here because
// some other thread may be wanting to insert into the require
// cache at the same time, and that's not an actual error case
self.cache_results
.lock()
.await
.insert(abs_path.to_path_buf(), load_res);
// Remove the pending thread id from the require context,
// broadcast a message to let any listeners know that this
// path has now finished the require process and is cached
let broadcast_tx = self
.cache_pending
.try_lock()
.expect("RequireContext may not be used from multiple threads")
.remove(abs_path)
.expect("Pending require broadcaster was unexpectedly removed");
broadcast_tx.send(()).ok();
load_val
}
/**
Loads (requires) the builtin with the given name.
*/
pub fn load_builtin(&self, name: impl AsRef<str>) -> LuaResult<LuaMultiValue<'lua>>
where
'lua: 'static, // FIXME: Remove static lifetime bound here when builtin libraries no longer need it
{
let builtin: LuneBuiltin = match name.as_ref().parse() {
Err(e) => return Err(LuaError::runtime(e)),
Ok(b) => b,
};
let mut cache = self
.cache_builtins
.try_lock()
.expect("RequireContext may not be used from multiple threads");
if let Some(res) = cache.get(&builtin) {
return match res {
Err(e) => return Err(e.clone()),
Ok(key) => {
let multi_vec = self
.lua
.registry_value::<Vec<LuaValue>>(key)
.expect("Missing builtin result in lua registry");
Ok(LuaMultiValue::from_vec(multi_vec))
}
};
};
let result = builtin.create(self.lua);
cache.insert(
builtin,
match result.clone() {
Err(e) => Err(e),
Ok(multi) => {
let multi_vec = multi.into_vec();
let multi_key = self
.lua
.create_registry_value(multi_vec)
.expect("Failed to store require result in registry - out of memory");
Ok(multi_key)
}
},
);
result
}
}

View file

@ -0,0 +1,98 @@
use mlua::prelude::*;
use crate::lune::{scheduler::LuaSchedulerExt, util::TableBuilder};
mod context;
use context::RequireContext;
mod alias;
mod builtin;
mod path;
const REQUIRE_IMPL: &str = r#"
return require(source(), ...)
"#;
pub fn create(lua: &'static Lua) -> LuaResult<impl IntoLua<'_>> {
lua.set_app_data(RequireContext::new(lua));
/*
Require implementation needs a few workarounds:
- Async functions run outside of the lua resumption cycle,
so the current lua thread, as well as its stack/debug info
is not available, meaning we have to use a normal function
- Using the async require function directly in another lua function
would mean yielding across the metamethod/c-call boundary, meaning
we have to first load our two functions into a normal lua chunk
and then load that new chunk into our final require function
Also note that we inspect the stack at level 2:
1. The current c / rust function
2. The wrapper lua chunk defined above
3. The lua chunk we are require-ing from
*/
let require_fn = lua.create_async_function(require)?;
let get_source_fn = lua.create_function(move |lua, _: ()| match lua.inspect_stack(2) {
None => Err(LuaError::runtime(
"Failed to get stack info for require source",
)),
Some(info) => match info.source().source {
None => Err(LuaError::runtime(
"Stack info is missing source for require",
)),
Some(source) => lua.create_string(source.as_bytes()),
},
})?;
let require_env = TableBuilder::new(lua)?
.with_value("source", get_source_fn)?
.with_value("require", require_fn)?
.build_readonly()?;
lua.load(REQUIRE_IMPL)
.set_name("require")
.set_environment(require_env)
.into_function()
}
async fn require<'lua>(
lua: &'lua Lua,
(source, path): (LuaString<'lua>, LuaString<'lua>),
) -> LuaResult<LuaMultiValue<'lua>>
where
'lua: 'static, // FIXME: Remove static lifetime bound here when builtin libraries no longer need it
{
let source = source
.to_str()
.into_lua_err()
.context("Failed to parse require source as string")?
.to_string();
let path = path
.to_str()
.into_lua_err()
.context("Failed to parse require path as string")?
.to_string();
let context = lua
.app_data_ref()
.expect("Failed to get RequireContext from app data");
if let Some(builtin_name) = path
.strip_prefix("@lune/")
.map(|name| name.to_ascii_lowercase())
{
builtin::require(&context, &builtin_name).await
} else if let Some(aliased_path) = path.strip_prefix('@') {
let (alias, name) = aliased_path.split_once('/').ok_or(LuaError::runtime(
"Require with custom alias must contain '/' delimiter",
))?;
alias::require(&context, alias, name).await
} else {
path::require(&context, &source, &path).await
}
}

View file

@ -0,0 +1,98 @@
use std::path::{Path, PathBuf};
use mlua::prelude::*;
use super::context::*;
pub(super) async fn require<'lua, 'ctx>(
ctx: &'ctx RequireContext<'lua>,
source: &str,
path: &str,
) -> LuaResult<LuaMultiValue<'lua>>
where
'lua: 'ctx,
{
let (abs_path, rel_path) = ctx.resolve_paths(source, path)?;
// 1. Try to require the exact path
if let Ok(res) = require_inner(ctx, &abs_path, &rel_path).await {
return Ok(res);
}
// 2. Try to require the path with an added "luau" extension
let (luau_abs_path, luau_rel_path) = (
append_extension(&abs_path, "luau"),
append_extension(&rel_path, "luau"),
);
if let Ok(res) = require_inner(ctx, &luau_abs_path, &luau_rel_path).await {
return Ok(res);
}
// 3. Try to require the path with an added "lua" extension
let (lua_abs_path, lua_rel_path) = (
append_extension(&abs_path, "lua"),
append_extension(&rel_path, "lua"),
);
if let Ok(res) = require_inner(ctx, &lua_abs_path, &lua_rel_path).await {
return Ok(res);
}
// We didn't find any direct file paths, look
// for directories with "init" files in them...
let abs_init = abs_path.join("init");
let rel_init = rel_path.join("init");
// 4. Try to require the init path with an added "luau" extension
let (luau_abs_init, luau_rel_init) = (
append_extension(&abs_init, "luau"),
append_extension(&rel_init, "luau"),
);
if let Ok(res) = require_inner(ctx, &luau_abs_init, &luau_rel_init).await {
return Ok(res);
}
// 5. Try to require the init path with an added "lua" extension
let (lua_abs_init, lua_rel_init) = (
append_extension(&abs_init, "lua"),
append_extension(&rel_init, "lua"),
);
if let Ok(res) = require_inner(ctx, &lua_abs_init, &lua_rel_init).await {
return Ok(res);
}
// Nothing left to try, throw an error
Err(LuaError::runtime(format!(
"No file exist at the path '{}'",
rel_path.display()
)))
}
async fn require_inner<'lua, 'ctx>(
ctx: &'ctx RequireContext<'lua>,
abs_path: impl AsRef<Path>,
rel_path: impl AsRef<Path>,
) -> LuaResult<LuaMultiValue<'lua>>
where
'lua: 'ctx,
{
let abs_path = abs_path.as_ref();
let rel_path = rel_path.as_ref();
if ctx.is_cached(abs_path)? {
ctx.get_from_cache(abs_path)
} else if ctx.is_pending(abs_path)? {
ctx.wait_for_cache(&abs_path).await
} else {
ctx.load_with_caching(&abs_path, &rel_path).await
}
}
fn append_extension(path: impl Into<PathBuf>, ext: &'static str) -> PathBuf {
let mut new = path.into();
match new.extension() {
// FUTURE: There's probably a better way to do this than converting to a lossy string
Some(e) => new.set_extension(format!("{}.{ext}", e.to_string_lossy())),
None => new.set_extension(ext),
};
new
}

View file

@ -0,0 +1,27 @@
use mlua::prelude::*;
use crate::roblox::datatypes::extension::RobloxUserdataTypenameExt;
const REGISTRY_KEY: &str = "LuauTypeof";
pub fn create(lua: &Lua) -> LuaResult<impl IntoLua<'_>> {
let original = lua.globals().get::<_, LuaFunction>("typeof")?;
#[cfg(feature = "roblox")]
{
lua.set_named_registry_value(REGISTRY_KEY, original)
.expect("Failed to store typeof function in registry");
lua.create_function(|lua, value: LuaValue| {
if let LuaValue::UserData(u) = &value {
if let Some(type_name) = u.roblox_type_name() {
return lua.create_string(type_name);
}
}
let original_fn: LuaFunction = lua
.named_registry_value(REGISTRY_KEY)
.expect("Missing typeof function in registry");
original_fn.call(value)
})
}
#[cfg(not(feature = "roblox"))]
original
}

View file

@ -0,0 +1,24 @@
use mlua::prelude::*;
pub fn create(lua: &Lua) -> LuaResult<impl IntoLua<'_>> {
let luau_version_full = lua
.globals()
.get::<_, LuaString>("_VERSION")
.expect("Missing _VERSION global");
let luau_version = luau_version_full
.to_str()?
.strip_prefix("Luau 0.")
.expect("_VERSION global is formatted incorrectly")
.trim();
if luau_version.is_empty() {
panic!("_VERSION global is missing version number")
}
lua.create_string(format!(
"Lune {lune}+{luau}",
lune = env!("CARGO_PKG_VERSION"),
luau = luau_version,
))
}

21
src/lune/globals/warn.rs Normal file
View file

@ -0,0 +1,21 @@
use mlua::prelude::*;
use tokio::io::{self, AsyncWriteExt};
use crate::lune::{
scheduler::LuaSchedulerExt,
util::formatting::{format_label, pretty_format_multi_value},
};
pub fn create(lua: &'static Lua) -> LuaResult<impl IntoLua<'_>> {
lua.create_async_function(|_, args: LuaMultiValue| async move {
let formatted = format!(
"{}\n{}",
format_label("warn"),
pretty_format_multi_value(&args)?
);
let mut stdout = io::stderr();
stdout.write_all(formatted.as_bytes()).await?;
stdout.flush().await?;
Ok(())
})
}

View file

@ -1,42 +0,0 @@
use mlua::prelude::*;
mod require;
mod require_waker;
use crate::lune::builtins::{self, top_level};
pub fn create(lua: &'static Lua, args: Vec<String>) -> LuaResult<()> {
// Create all builtins
let builtins = vec![
("fs", builtins::fs::create(lua)?),
("net", builtins::net::create(lua)?),
("process", builtins::process::create(lua, args)?),
("serde", builtins::serde::create(lua)?),
("stdio", builtins::stdio::create(lua)?),
("task", builtins::task::create(lua)?),
("luau", builtins::luau::create(lua)?),
#[cfg(feature = "roblox")]
("roblox", builtins::roblox::create(lua)?),
];
// Create our importer (require) with builtins
let require_fn = require::create(lua, builtins)?;
// Create all top-level globals
let globals = vec![
("require", require_fn),
("print", lua.create_function(top_level::print)?),
("warn", lua.create_function(top_level::warn)?),
("error", lua.create_function(top_level::error)?),
("type", lua.create_function(top_level::proxy_type)?),
("typeof", lua.create_function(top_level::proxy_typeof)?),
];
// Set top-level globals
let lua_globals = lua.globals();
for (name, global) in globals {
lua_globals.set(name, global)?;
}
Ok(())
}

View file

@ -1,319 +0,0 @@
use std::{
cell::RefCell,
collections::{HashMap, HashSet},
env::current_dir,
path::{self, PathBuf},
sync::Arc,
};
use mlua::prelude::*;
use tokio::fs;
use tokio::sync::Mutex as AsyncMutex;
use crate::lune::lua::{
table::TableBuilder,
task::{TaskScheduler, TaskSchedulerScheduleExt},
};
use super::require_waker::{RequireWakerFuture, RequireWakerState};
const REQUIRE_IMPL_LUA: &str = r#"
local source = info(1, "s")
if source == '[string "require"]' then
source = info(2, "s")
end
load(context, source, ...)
return yield()
"#;
type RequireWakersVec<'lua> = Vec<Arc<AsyncMutex<RequireWakerState<'lua>>>>;
fn append_extension_and_canonicalize(
path: impl Into<PathBuf>,
ext: &'static str,
) -> Result<PathBuf, std::io::Error> {
let mut new = path.into();
match new.extension() {
// FUTURE: There's probably a better way to do this than converting to a lossy string
Some(e) => new.set_extension(format!("{}.{ext}", e.to_string_lossy())),
None => new.set_extension(ext),
};
dunce::canonicalize(new)
}
#[derive(Debug, Clone, Default)]
struct RequireContext<'lua> {
// NOTE: We need to use arc here so that mlua clones
// the reference and not the entire inner value(s)
builtins: Arc<HashMap<String, LuaMultiValue<'lua>>>,
cached: Arc<RefCell<HashMap<String, LuaResult<LuaMultiValue<'lua>>>>>,
wakers: Arc<RefCell<HashMap<String, RequireWakersVec<'lua>>>>,
locks: Arc<RefCell<HashSet<String>>>,
pwd: String,
}
impl<'lua> RequireContext<'lua> {
pub fn new<K, V>(lua: &'lua Lua, builtins_vec: Vec<(K, V)>) -> LuaResult<Self>
where
K: Into<String>,
V: IntoLua<'lua>,
{
let mut pwd = current_dir()
.expect("Failed to access current working directory")
.to_string_lossy()
.to_string();
if !pwd.ends_with(path::MAIN_SEPARATOR) {
pwd = format!("{pwd}{}", path::MAIN_SEPARATOR)
}
let mut builtins = HashMap::new();
for (key, value) in builtins_vec {
builtins.insert(key.into(), value.into_lua_multi(lua)?);
}
Ok(Self {
pwd,
builtins: Arc::new(builtins),
..Default::default()
})
}
pub fn is_locked(&self, absolute_path: &str) -> bool {
self.locks.borrow().contains(absolute_path)
}
pub fn set_locked(&self, absolute_path: &str) -> bool {
self.locks.borrow_mut().insert(absolute_path.to_string())
}
pub fn set_unlocked(&self, absolute_path: &str) -> bool {
self.locks.borrow_mut().remove(absolute_path)
}
pub fn try_acquire_lock_sync(&self, absolute_path: &str) -> bool {
if self.is_locked(absolute_path) {
false
} else {
self.set_locked(absolute_path);
true
}
}
pub fn set_cached(&self, absolute_path: &str, result: &LuaResult<LuaMultiValue<'lua>>) {
self.cached
.borrow_mut()
.insert(absolute_path.to_string(), result.clone());
if let Some(wakers) = self.wakers.borrow_mut().remove(absolute_path) {
for waker in wakers {
waker
.try_lock()
.expect("Failed to lock waker")
.finalize(result.clone());
}
}
}
pub fn wait_for_cache(self, absolute_path: &str) -> RequireWakerFuture<'lua> {
let state = RequireWakerState::new();
let fut = RequireWakerFuture::new(&state);
self.wakers
.borrow_mut()
.entry(absolute_path.to_string())
.or_insert_with(Vec::new)
.push(Arc::clone(&state));
fut
}
pub fn get_paths(
&self,
require_source: String,
require_path: String,
) -> LuaResult<(String, String)> {
if require_path.starts_with('@') {
return Ok((require_path.clone(), require_path));
}
let path_relative_to_pwd = PathBuf::from(
&require_source
.trim_start_matches("[string \"")
.trim_end_matches("\"]"),
)
.parent()
.unwrap()
.join(&require_path);
// Try to normalize and resolve relative path segments such as './' and '../'
let file_path = match (
append_extension_and_canonicalize(&path_relative_to_pwd, "luau"),
append_extension_and_canonicalize(&path_relative_to_pwd, "lua"),
) {
(Ok(luau), _) => luau,
(_, Ok(lua)) => lua,
// If we did not find a luau/lua file at the wanted path,
// we should also look for "init" files in directories
_ => {
let init_dir_path = path_relative_to_pwd.join("init");
match (
append_extension_and_canonicalize(&init_dir_path, "luau"),
append_extension_and_canonicalize(&init_dir_path, "lua"),
) {
(Ok(luau), _) => luau,
(_, Ok(lua)) => lua,
_ => {
return Err(LuaError::RuntimeError(format!(
"File does not exist at path '{require_path}'"
)));
}
}
}
};
let absolute = file_path.to_string_lossy().to_string();
let relative = absolute.trim_start_matches(&self.pwd).to_string();
Ok((absolute, relative))
}
}
impl<'lua> LuaUserData for RequireContext<'lua> {}
fn load_builtin<'lua>(
_lua: &'lua Lua,
context: RequireContext<'lua>,
module_name: String,
_has_acquired_lock: bool,
) -> LuaResult<LuaMultiValue<'lua>> {
match context.builtins.get(&module_name) {
Some(module) => Ok(module.clone()),
None => Err(LuaError::RuntimeError(format!(
"No builtin module exists with the name '{}'",
module_name
))),
}
}
async fn load_file<'lua>(
lua: &'lua Lua,
context: RequireContext<'lua>,
absolute_path: String,
relative_path: String,
has_acquired_lock: bool,
) -> LuaResult<LuaMultiValue<'lua>> {
let cached = { context.cached.borrow().get(&absolute_path).cloned() };
match cached {
Some(cached) => cached,
None => {
if !has_acquired_lock {
return context.wait_for_cache(&absolute_path).await;
}
// Try to read the wanted file, note that we use bytes instead of reading
// to a string since lua scripts are not necessarily valid utf-8 strings
let contents = fs::read(&absolute_path).await.into_lua_err()?;
// Use a name without extensions for loading the chunk, some
// other code assumes the require path is without extensions
let path_relative_no_extension = relative_path
.trim_end_matches(".lua")
.trim_end_matches(".luau");
// Load the file into a thread
let loaded_func = lua
.load(contents)
.set_name(path_relative_no_extension)
.into_function()?;
let loaded_thread = lua.create_thread(loaded_func)?;
// Run the thread and wait for completion using the native task scheduler waker
let task_fut = {
let sched = lua.app_data_ref::<&TaskScheduler>().unwrap();
let task = sched.schedule_blocking(loaded_thread, LuaMultiValue::new())?;
sched.wait_for_task_completion(task)
};
// Wait for the thread to finish running, cache + return our result,
// notify any other threads that are also waiting on this to finish
let rets = task_fut.await;
context.set_cached(&absolute_path, &rets);
rets
}
}
}
async fn load<'lua>(
lua: &'lua Lua,
context: LuaUserDataRef<'lua, RequireContext<'lua>>,
absolute_path: String,
relative_path: String,
has_acquired_lock: bool,
) -> LuaResult<LuaMultiValue<'lua>> {
let result = if absolute_path == relative_path && absolute_path.starts_with('@') {
if let Some(module_name) = absolute_path.strip_prefix("@lune/") {
load_builtin(
lua,
context.clone(),
module_name.to_string(),
has_acquired_lock,
)
} else {
// FUTURE: '@' can be used a special prefix for users to set their own
// paths relative to a project file, similar to typescript paths config
// https://www.typescriptlang.org/tsconfig#paths
Err(LuaError::RuntimeError(
"Require paths prefixed by '@' are not yet supported".to_string(),
))
}
} else {
load_file(
lua,
context.clone(),
absolute_path.to_string(),
relative_path,
has_acquired_lock,
)
.await
};
if has_acquired_lock {
context.set_unlocked(&absolute_path);
}
result
}
pub fn create<K, V>(lua: &'static Lua, builtins: Vec<(K, V)>) -> LuaResult<LuaFunction>
where
K: Clone + Into<String>,
V: Clone + IntoLua<'static>,
{
let require_context = RequireContext::new(lua, builtins)?;
let require_yield: LuaFunction = lua.named_registry_value("co.yield")?;
let require_info: LuaFunction = lua.named_registry_value("dbg.info")?;
let require_print: LuaFunction = lua.named_registry_value("print")?;
let require_env = TableBuilder::new(lua)?
.with_value("context", require_context)?
.with_value("yield", require_yield)?
.with_value("info", require_info)?
.with_value("print", require_print)?
.with_function(
"load",
|lua,
(context, require_source, require_path): (
LuaUserDataRef<RequireContext>,
String,
String,
)| {
let (absolute_path, relative_path) =
context.get_paths(require_source, require_path)?;
// NOTE: We can not acquire the lock in the async part of the require
// load process since several requires may have happened for the
// same path before the async load task even gets a chance to run
let has_lock = context.try_acquire_lock_sync(&absolute_path);
let fut = load(lua, context, absolute_path, relative_path, has_lock);
let sched = lua
.app_data_ref::<&TaskScheduler>()
.expect("Missing task scheduler as a lua app data");
sched.queue_async_task_inherited(lua.current_thread(), None, async {
let rets = fut.await?;
let mult = rets.into_lua_multi(lua)?;
Ok(Some(mult))
})
},
)?
.build_readonly()?;
let require_fn_lua = lua
.load(REQUIRE_IMPL_LUA)
.set_name("require")
.set_environment(require_env)
.into_function()?;
Ok(require_fn_lua)
}

View file

@ -1,66 +0,0 @@
use std::{
future::Future,
pin::Pin,
sync::Arc,
task::{Context, Poll, Waker},
};
use tokio::sync::Mutex as AsyncMutex;
use mlua::prelude::*;
#[derive(Debug, Clone)]
pub(super) struct RequireWakerState<'lua> {
rets: Option<LuaResult<LuaMultiValue<'lua>>>,
waker: Option<Waker>,
}
impl<'lua> RequireWakerState<'lua> {
pub fn new() -> Arc<AsyncMutex<Self>> {
Arc::new(AsyncMutex::new(RequireWakerState {
rets: None,
waker: None,
}))
}
pub fn finalize(&mut self, rets: LuaResult<LuaMultiValue<'lua>>) {
self.rets = Some(rets);
if let Some(waker) = self.waker.take() {
waker.wake();
}
}
}
#[derive(Debug)]
pub(super) struct RequireWakerFuture<'lua> {
state: Arc<AsyncMutex<RequireWakerState<'lua>>>,
}
impl<'lua> RequireWakerFuture<'lua> {
pub fn new(state: &Arc<AsyncMutex<RequireWakerState<'lua>>>) -> Self {
Self {
state: Arc::clone(state),
}
}
}
impl<'lua> Clone for RequireWakerFuture<'lua> {
fn clone(&self) -> Self {
Self {
state: Arc::clone(&self.state),
}
}
}
impl<'lua> Future for RequireWakerFuture<'lua> {
type Output = LuaResult<LuaMultiValue<'lua>>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut shared_state = self.state.try_lock().unwrap();
if let Some(rets) = shared_state.rets.clone() {
Poll::Ready(rets)
} else {
shared_state.waker = Some(cx.waker().clone());
Poll::Pending
}
}
}

View file

@ -1,93 +0,0 @@
use async_trait::async_trait;
use futures_util::Future;
use mlua::prelude::*;
use crate::lune::{lua::table::TableBuilder, lua::task::TaskScheduler};
use super::task::TaskSchedulerAsyncExt;
const ASYNC_IMPL_LUA: &str = r#"
resumeAsync(...)
return yield()
"#;
const WAIT_IMPL_LUA: &str = r#"
resumeAfter(...)
return yield()
"#;
#[async_trait(?Send)]
pub trait LuaAsyncExt {
fn create_async_function<'lua, A, R, F, FR>(self, func: F) -> LuaResult<LuaFunction<'lua>>
where
A: FromLuaMulti<'static>,
R: IntoLuaMulti<'static>,
F: 'static + Fn(&'lua Lua, A) -> FR,
FR: 'static + Future<Output = LuaResult<R>>;
fn create_waiter_function<'lua>(self) -> LuaResult<LuaFunction<'lua>>;
}
impl LuaAsyncExt for &'static Lua {
/**
Creates a function callable from Lua that runs an async
closure and returns the results of it to the call site.
*/
fn create_async_function<'lua, A, R, F, FR>(self, func: F) -> LuaResult<LuaFunction<'lua>>
where
A: FromLuaMulti<'static>,
R: IntoLuaMulti<'static>,
F: 'static + Fn(&'lua Lua, A) -> FR,
FR: 'static + Future<Output = LuaResult<R>>,
{
let async_env_yield: LuaFunction = self.named_registry_value("co.yield")?;
let async_env = TableBuilder::new(self)?
.with_value("yield", async_env_yield)?
.with_function("resumeAsync", move |lua: &Lua, args: A| {
let thread = lua.current_thread();
let fut = func(lua, args);
let sched = lua
.app_data_ref::<&TaskScheduler>()
.expect("Missing task scheduler as a lua app data");
sched.queue_async_task(thread, None, async {
let rets = fut.await?;
let mult = rets.into_lua_multi(lua)?;
Ok(Some(mult))
})
})?
.build_readonly()?;
let async_func = self
.load(ASYNC_IMPL_LUA)
.set_name("async")
.set_environment(async_env)
.into_function()?;
Ok(async_func)
}
/**
Creates a special async function that waits the
desired amount of time, inheriting the guid of the
current thread / task for proper cancellation.
This will yield the lua thread calling the function until the
desired time has passed and the scheduler resumes the thread.
*/
fn create_waiter_function<'lua>(self) -> LuaResult<LuaFunction<'lua>> {
let async_env_yield: LuaFunction = self.named_registry_value("co.yield")?;
let async_env = TableBuilder::new(self)?
.with_value("yield", async_env_yield)?
.with_function("resumeAfter", move |lua: &Lua, duration: Option<f64>| {
let sched = lua
.app_data_ref::<&TaskScheduler>()
.expect("Missing task scheduler as a lua app data");
sched.schedule_wait(lua.current_thread(), duration)
})?
.build_readonly()?;
let async_func = self
.load(WAIT_IMPL_LUA)
.set_name("wait")
.set_environment(async_env)
.into_function()?;
Ok(async_func)
}
}

View file

@ -1,184 +0,0 @@
use mlua::{prelude::*, Compiler as LuaCompiler};
/*
- Level 0 is the call to info
- Level 1 is the load call in create() below where we load this into a function
- Level 2 is the call to the trace, which we also want to skip, so start at 3
Also note that we must match the mlua traceback format here so that we
can pattern match and beautify it properly later on when outputting it
*/
const TRACE_IMPL_LUA: &str = r#"
local lines = {}
for level = 3, 16 do
local parts = {}
local source, line, name = info(level, "sln")
if source then
push(parts, source)
else
break
end
if line == -1 then
line = nil
end
if name and #name <= 0 then
name = nil
end
if line then
push(parts, format("%d", line))
end
if name and #parts > 1 then
push(parts, format(" in function '%s'", name))
elseif name then
push(parts, format("in function '%s'", name))
end
if #parts > 0 then
push(lines, concat(parts, ":"))
end
end
if #lines > 0 then
return concat(lines, "\n")
else
return nil
end
"#;
/**
Stores the following globals in the Lua registry:
| Registry Name | Global |
|-----------------|-------------------|
| `"print"` | `print` |
| `"error"` | `error` |
| `"type"` | `type` |
| `"typeof"` | `typeof` |
| `"pcall"` | `pcall` |
| `"xpcall"` | `xpcall` |
| `"tostring"` | `tostring` |
| `"tonumber"` | `tonumber` |
| `"co.yield"` | `coroutine.yield` |
| `"co.close"` | `coroutine.close` |
| `"tab.pack"` | `table.pack` |
| `"tab.unpack"` | `table.unpack` |
| `"tab.freeze"` | `table.freeze` |
| `"tab.getmeta"` | `getmetatable` |
| `"tab.setmeta"` | `setmetatable` |
| `"dbg.info"` | `debug.info` |
| `"dbg.trace"` | `debug.traceback` |
These globals can then be modified safely from other runtime code.
*/
fn store_globals_in_registry(lua: &Lua) -> LuaResult<()> {
// Extract some global tables that we will extract
// built-in functions from and store in the registry
let globals = lua.globals();
let debug: LuaTable = globals.get("debug")?;
let string: LuaTable = globals.get("string")?;
let table: LuaTable = globals.get("table")?;
let coroutine: LuaTable = globals.get("coroutine")?;
// Store original lua global functions in the registry so we can use
// them later without passing them around and dealing with lifetimes
lua.set_named_registry_value("print", globals.get::<_, LuaFunction>("print")?)?;
lua.set_named_registry_value("error", globals.get::<_, LuaFunction>("error")?)?;
lua.set_named_registry_value("type", globals.get::<_, LuaFunction>("type")?)?;
lua.set_named_registry_value("typeof", globals.get::<_, LuaFunction>("typeof")?)?;
lua.set_named_registry_value("xpcall", globals.get::<_, LuaFunction>("xpcall")?)?;
lua.set_named_registry_value("pcall", globals.get::<_, LuaFunction>("pcall")?)?;
lua.set_named_registry_value("tostring", globals.get::<_, LuaFunction>("tostring")?)?;
lua.set_named_registry_value("tonumber", globals.get::<_, LuaFunction>("tonumber")?)?;
lua.set_named_registry_value("co.status", coroutine.get::<_, LuaFunction>("status")?)?;
lua.set_named_registry_value("co.yield", coroutine.get::<_, LuaFunction>("yield")?)?;
lua.set_named_registry_value("co.close", coroutine.get::<_, LuaFunction>("close")?)?;
lua.set_named_registry_value("dbg.info", debug.get::<_, LuaFunction>("info")?)?;
lua.set_named_registry_value("tab.pack", table.get::<_, LuaFunction>("pack")?)?;
lua.set_named_registry_value("tab.unpack", table.get::<_, LuaFunction>("unpack")?)?;
lua.set_named_registry_value("tab.freeze", table.get::<_, LuaFunction>("freeze")?)?;
lua.set_named_registry_value(
"tab.getmeta",
globals.get::<_, LuaFunction>("getmetatable")?,
)?;
lua.set_named_registry_value(
"tab.setmeta",
globals.get::<_, LuaFunction>("setmetatable")?,
)?;
// Create a trace function that can be called to obtain a full stack trace from
// lua, this is not possible to do from rust when using our manual scheduler
let dbg_trace_env = lua.create_table_with_capacity(0, 1)?;
dbg_trace_env.set("info", debug.get::<_, LuaFunction>("info")?)?;
dbg_trace_env.set("push", table.get::<_, LuaFunction>("insert")?)?;
dbg_trace_env.set("concat", table.get::<_, LuaFunction>("concat")?)?;
dbg_trace_env.set("format", string.get::<_, LuaFunction>("format")?)?;
let dbg_trace_fn = lua
.load(TRACE_IMPL_LUA)
.set_name("=dbg.trace")
.set_environment(dbg_trace_env)
.into_function()?;
lua.set_named_registry_value("dbg.trace", dbg_trace_fn)?;
Ok(())
}
/**
Sets the `_VERSION` global to a value matching the string `Lune x.y.z+w` where
`x.y.z` is the current Lune runtime version and `w` is the current Luau version
*/
fn set_global_version(lua: &Lua) -> LuaResult<()> {
let luau_version_full = lua
.globals()
.get::<_, LuaString>("_VERSION")
.expect("Missing _VERSION global");
let luau_version = luau_version_full
.to_str()?
.strip_prefix("Luau 0.")
.expect("_VERSION global is formatted incorrectly")
.trim();
if luau_version.is_empty() {
panic!("_VERSION global is missing version number")
}
lua.globals().set(
"_VERSION",
lua.create_string(&format!(
"Lune {lune}+{luau}",
lune = env!("CARGO_PKG_VERSION"),
luau = luau_version,
))?,
)
}
/**
Creates a _G table that is separate from our built-in globals
*/
fn set_global_table(lua: &Lua) -> LuaResult<()> {
lua.globals().set("_G", lua.create_table()?)
}
/**
Enables JIT and sets default compiler settings for the Lua struct.
*/
fn init_compiler_settings(lua: &Lua) {
lua.enable_jit(true);
lua.set_compiler(
LuaCompiler::default()
.set_coverage_level(0)
.set_debug_level(1)
.set_optimization_level(1),
);
}
/**
Creates a new [`mlua::Lua`] struct with compiler,
registry, and globals customized for the Lune runtime.
Refer to the source code for additional details and specifics.
*/
pub fn create() -> LuaResult<Lua> {
let lua = Lua::new();
init_compiler_settings(&lua);
store_globals_in_registry(&lua)?;
set_global_version(&lua)?;
set_global_table(&lua)?;
Ok(lua)
}

View file

@ -1,7 +0,0 @@
mod copy;
mod metadata;
mod options;
pub use copy::copy;
pub use metadata::FsMetadata;
pub use options::FsWriteOptions;

View file

@ -1,3 +0,0 @@
mod options;
pub use options::{LuauCompileOptions, LuauLoadOptions};

View file

@ -1,13 +0,0 @@
mod create;
pub mod async_ext;
pub mod fs;
pub mod luau;
pub mod net;
pub mod process;
pub mod serde;
pub mod stdio;
pub mod table;
pub mod task;
pub use create::create as create_lune_lua;

View file

@ -1,11 +0,0 @@
mod client;
mod config;
mod response;
mod server;
mod websocket;
pub use client::{NetClient, NetClientBuilder};
pub use config::{RequestConfig, ServeConfig};
pub use response::{NetServeResponse, NetServeResponseKind};
pub use server::{NetLocalExec, NetService};
pub use websocket::NetWebSocket;

View file

@ -1,178 +0,0 @@
use std::{
future::Future,
pin::Pin,
sync::Arc,
task::{Context, Poll},
};
use mlua::prelude::*;
use hyper::{body::to_bytes, server::conn::AddrStream, service::Service};
use hyper::{Body, Request, Response};
use hyper_tungstenite::{is_upgrade_request as is_ws_upgrade_request, upgrade as ws_upgrade};
use tokio::task;
use crate::lune::{
lua::table::TableBuilder,
lua::task::{TaskScheduler, TaskSchedulerAsyncExt, TaskSchedulerScheduleExt},
};
use super::{NetServeResponse, NetWebSocket};
// Hyper service implementation for net, lots of boilerplate here
// but make_svc and make_svc_function do not work for what we need
pub struct NetServiceInner(
&'static Lua,
Arc<LuaRegistryKey>,
Arc<Option<LuaRegistryKey>>,
);
impl Service<Request<Body>> for NetServiceInner {
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(()))
}
fn call(&mut self, mut req: Request<Body>) -> Self::Future {
let lua = self.0;
if self.2.is_some() && is_ws_upgrade_request(&req) {
// Websocket upgrade request + websocket handler exists,
// we should now upgrade this connection to a websocket
// and then call our handler with a new socket object
let kopt = self.2.clone();
let key = kopt.as_ref().as_ref().unwrap();
let handler: LuaFunction = lua.registry_value(key).expect("Missing websocket handler");
let (response, ws) = ws_upgrade(&mut req, None).expect("Failed to upgrade websocket");
// This should be spawned as a registered task, otherwise
// the scheduler may exit early and cancel this even though what
// we want here is a long-running task that keeps the program alive
let sched = lua
.app_data_ref::<&TaskScheduler>()
.expect("Missing task scheduler");
let task = sched.register_background_task();
task::spawn_local(async move {
// Create our new full websocket object, then
// schedule our handler to get called asap
let ws = ws.await.into_lua_err()?;
let sock = NetWebSocket::new(ws).into_lua_table(lua)?;
let sched = lua
.app_data_ref::<&TaskScheduler>()
.expect("Missing task scheduler");
let result = sched.schedule_blocking(
lua.create_thread(handler)?,
LuaMultiValue::from_vec(vec![LuaValue::Table(sock)]),
);
task.unregister(Ok(()));
result
});
Box::pin(async move { Ok(response) })
} else {
// Got a normal http request or no websocket handler
// exists, just call the http request handler
let key = self.1.clone();
let (parts, body) = req.into_parts();
Box::pin(async move {
// Convert request body into bytes, extract handler
let bytes = to_bytes(body).await.into_lua_err()?;
let handler: LuaFunction = lua.registry_value(&key)?;
// Create a readonly table for the request query params
let query_params = TableBuilder::new(lua)?
.with_values(
parts
.uri
.query()
.unwrap_or_default()
.split('&')
.filter_map(|q| q.split_once('='))
.collect(),
)?
.build_readonly()?;
// Do the same for headers
let header_map = TableBuilder::new(lua)?
.with_values(
parts
.headers
.iter()
.map(|(name, value)| {
(name.to_string(), value.to_str().unwrap().to_string())
})
.collect(),
)?
.build_readonly()?;
// Create a readonly table with request info to pass to the handler
let request = TableBuilder::new(lua)?
.with_value("path", parts.uri.path())?
.with_value("query", query_params)?
.with_value("method", parts.method.as_str())?
.with_value("headers", header_map)?
.with_value("body", lua.create_string(&bytes)?)?
.build_readonly()?;
let response: LuaResult<NetServeResponse> = handler.call(request);
// Send below errors to task scheduler so that they can emit properly
let lua_error = match response {
Ok(r) => match r.into_response() {
Ok(res) => return Ok(res),
Err(err) => err,
},
Err(err) => err,
};
lua.app_data_ref::<&TaskScheduler>()
.expect("Missing task scheduler")
.forward_lua_error(lua_error);
Ok(Response::builder()
.status(500)
.body(Body::from("Internal Server Error"))
.unwrap())
})
}
}
}
pub struct NetService(
&'static Lua,
Arc<LuaRegistryKey>,
Arc<Option<LuaRegistryKey>>,
);
impl NetService {
pub fn new(
lua: &'static Lua,
callback_http: LuaRegistryKey,
callback_websocket: Option<LuaRegistryKey>,
) -> Self {
Self(lua, Arc::new(callback_http), Arc::new(callback_websocket))
}
}
impl Service<&AddrStream> for NetService {
type Response = NetServiceInner;
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, _: &AddrStream) -> Self::Future {
let lua = self.0;
let key1 = self.1.clone();
let key2 = self.2.clone();
Box::pin(async move { Ok(NetServiceInner(lua, key1, key2)) })
}
}
#[derive(Clone, Copy, Debug)]
pub struct NetLocalExec;
impl<F> hyper::rt::Executor<F> for NetLocalExec
where
F: std::future::Future + 'static, // not requiring `Send`
{
fn execute(&self, fut: F) {
task::spawn_local(fut);
}
}

View file

@ -1,5 +0,0 @@
mod compress_decompress;
mod encode_decode;
pub use compress_decompress::{compress, decompress, CompressDecompressFormat};
pub use encode_decode::{EncodeDecodeConfig, EncodeDecodeFormat};

View file

@ -1,2 +0,0 @@
pub mod formatting;
pub mod prompt;

View file

@ -1,3 +0,0 @@
mod builder;
pub use builder::TableBuilder;

View file

@ -1,135 +0,0 @@
use std::time::Duration;
use async_trait::async_trait;
use futures_util::Future;
use mlua::prelude::*;
use tokio::time::{sleep, Instant};
use crate::lune::lua::task::TaskKind;
use super::super::{
scheduler::TaskReference, scheduler::TaskScheduler, scheduler_handle::TaskSchedulerAsyncHandle,
scheduler_message::TaskSchedulerMessage,
};
/*
Trait definition - same as the implementation, ignore this
We use traits here to prevent misuse of certain scheduler
APIs, making importing of them as intentional as possible
*/
#[async_trait(?Send)]
pub trait TaskSchedulerAsyncExt<'fut> {
fn register_background_task(&self) -> TaskSchedulerAsyncHandle;
fn schedule_async<'sched, R, F, FR>(
&'sched self,
thread: LuaThread<'_>,
func: F,
) -> LuaResult<TaskReference>
where
'sched: 'fut,
R: IntoLuaMulti<'static>,
F: 'static + Fn(&'static Lua) -> FR,
FR: 'static + Future<Output = LuaResult<R>>;
fn schedule_wait(
&'fut self,
reference: LuaThread<'_>,
duration: Option<f64>,
) -> LuaResult<TaskReference>;
}
/*
Trait implementation
*/
#[async_trait(?Send)]
impl<'fut> TaskSchedulerAsyncExt<'fut> for TaskScheduler<'fut> {
/**
Registers a new background task with the task scheduler.
The returned [`TaskSchedulerAsyncHandle`] must have its
[`TaskSchedulerAsyncHandle::unregister`] method called
upon completion of the background task to prevent
the task scheduler from running indefinitely.
*/
fn register_background_task(&self) -> TaskSchedulerAsyncHandle {
let sender = self.futures_tx.clone();
sender
.send(TaskSchedulerMessage::Spawned)
.unwrap_or_else(|e| {
panic!(
"\
\nFailed to unregister background task - this is an internal error! \
\nPlease report it at {} \
\nDetails: {e} \
",
env!("CARGO_PKG_REPOSITORY")
)
});
TaskSchedulerAsyncHandle::new(sender)
}
/**
Schedules a lua thread or function
to be resumed after running a future.
The given lua thread or function will be resumed
using the optional arguments returned by the future.
*/
fn schedule_async<'sched, R, F, FR>(
&'sched self,
thread: LuaThread<'_>,
func: F,
) -> LuaResult<TaskReference>
where
'sched: 'fut, // Scheduler must live at least as long as the future
R: IntoLuaMulti<'static>,
F: 'static + Fn(&'static Lua) -> FR,
FR: 'static + Future<Output = LuaResult<R>>,
{
self.queue_async_task(thread, None, async move {
match func(self.lua).await {
Ok(res) => match res.into_lua_multi(self.lua) {
Ok(multi) => Ok(Some(multi)),
Err(e) => Err(e),
},
Err(e) => Err(e),
}
})
}
/**
Schedules a task reference to be resumed after a certain amount of time.
The given task will be resumed with the elapsed time as its one and only argument.
*/
fn schedule_wait(
&'fut self,
thread: LuaThread<'_>,
duration: Option<f64>,
) -> LuaResult<TaskReference> {
let reference = self.create_task(TaskKind::Future, thread, None, true)?;
// Insert the future
let futs = self
.futures
.try_lock()
.expect("Tried to add future to queue during futures resumption");
futs.push(Box::pin(async move {
let before = Instant::now();
sleep(Duration::from_secs_f64(
duration.unwrap_or_default().max(0.0),
))
.await;
let elapsed_secs = before.elapsed().as_secs_f64();
let args = elapsed_secs.into_lua_multi(self.lua).unwrap();
(Some(reference), Ok(Some(args)))
}));
Ok(reference)
}
}

View file

@ -1,7 +0,0 @@
mod async_ext;
mod resume_ext;
mod schedule_ext;
pub use async_ext::TaskSchedulerAsyncExt;
pub use resume_ext::TaskSchedulerResumeExt;
pub use schedule_ext::TaskSchedulerScheduleExt;

View file

@ -1,180 +0,0 @@
use std::time::Duration;
use async_trait::async_trait;
use mlua::prelude::*;
use futures_util::StreamExt;
use tokio::time::sleep;
use super::super::{
scheduler_message::TaskSchedulerMessage, scheduler_state::TaskSchedulerState, TaskScheduler,
};
/*
Trait definition - same as the implementation, ignore this
We use traits here to prevent misuse of certain scheduler
APIs, making importing of them as intentional as possible
*/
#[async_trait(?Send)]
pub trait TaskSchedulerResumeExt {
async fn resume_queue(&self) -> TaskSchedulerState;
}
/*
Trait implementation
*/
#[async_trait(?Send)]
impl TaskSchedulerResumeExt for TaskScheduler<'_> {
/**
Resumes the task scheduler queue.
This will run any spawned or deferred Lua tasks in a blocking manner.
Once all spawned and / or deferred Lua tasks have finished running,
this will process delayed tasks, waiting tasks, and native Rust
futures concurrently, awaiting the first one to be ready for resumption.
*/
async fn resume_queue(&self) -> TaskSchedulerState {
let current = TaskSchedulerState::new(self);
if current.num_blocking > 0 {
// 1. Blocking tasks
resume_next_blocking_task(self, None)
} else if current.num_futures > 0 || current.num_background > 0 {
// 2. Async and/or background tasks
tokio::select! {
result = resume_next_async_task(self) => result,
result = receive_next_message(self) => result,
}
} else {
// 3. No tasks left, here we sleep one millisecond in case
// the caller of resume_queue accidentally calls this in
// a busy loop to prevent cpu usage from going to 100%
sleep(Duration::from_millis(1)).await;
TaskSchedulerState::new(self)
}
}
}
/*
Private functions for the trait that operate on the task scheduler
These could be implemented as normal methods but if we put them in the
trait they become public, and putting them in the task scheduler's
own implementation block will clutter that up unnecessarily
*/
/**
Resumes the next queued Lua task, if one exists, blocking
the current thread until it either yields or finishes.
*/
fn resume_next_blocking_task<'sched, 'args>(
scheduler: &TaskScheduler<'sched>,
override_args: Option<LuaResult<LuaMultiValue<'args>>>,
) -> TaskSchedulerState
where
'args: 'sched,
{
match {
let mut queue_guard = scheduler.tasks_queue_blocking.borrow_mut();
let task = queue_guard.pop_front();
drop(queue_guard);
task
} {
None => TaskSchedulerState::new(scheduler),
Some(task) => match scheduler.resume_task(task, override_args) {
Err(task_err) => {
scheduler.wake_completed_task(task, Err(task_err.clone()));
TaskSchedulerState::err(scheduler, task_err)
}
Ok(rets) if rets.0 == LuaThreadStatus::Unresumable => {
scheduler.wake_completed_task(task, Ok(rets.1));
TaskSchedulerState::new(scheduler)
}
Ok(_) => TaskSchedulerState::new(scheduler),
},
}
}
/**
Awaits the first available queued future, and resumes its associated
Lua task which will be ready for resumption when that future wakes.
Panics if there are no futures currently queued.
Use [`TaskScheduler::next_queue_future_exists`]
to check if there are any queued futures.
*/
async fn resume_next_async_task(scheduler: &TaskScheduler<'_>) -> TaskSchedulerState {
let (task, result) = {
let mut futs = scheduler
.futures
.try_lock()
.expect("Tried to resume next queued future while already resuming or modifying");
futs.next()
.await
.expect("Tried to resume next queued future but none are queued")
};
// The future might not return a reference that it wants to resume
if let Some(task) = task {
// Promote this future task to a blocking task and resume it
// right away, also taking care to not borrow mutably twice
// by dropping this guard before trying to resume it
let mut queue_guard = scheduler.tasks_queue_blocking.borrow_mut();
queue_guard.push_front(task);
drop(queue_guard);
}
resume_next_blocking_task(scheduler, result.transpose())
}
/**
Awaits the next background task registration
message, if any messages exist in the queue.
This is a no-op if there are no background tasks left running
and / or the background task messages channel was closed.
*/
async fn receive_next_message(scheduler: &TaskScheduler<'_>) -> TaskSchedulerState {
let message_opt = {
let mut rx = scheduler.futures_rx.lock().await;
rx.recv().await
};
if let Some(message) = message_opt {
match message {
TaskSchedulerMessage::NewBlockingTaskReady => TaskSchedulerState::new(scheduler),
TaskSchedulerMessage::NewLuaErrorReady(err) => TaskSchedulerState::err(scheduler, err),
TaskSchedulerMessage::Spawned => {
let prev = scheduler.futures_background_count.get();
scheduler.futures_background_count.set(prev + 1);
TaskSchedulerState::new(scheduler)
}
TaskSchedulerMessage::Terminated(result) => {
let prev = scheduler.futures_background_count.get();
scheduler.futures_background_count.set(prev - 1);
if prev == 0 {
panic!(
r#"
Terminated a background task without it running - this is an internal error!
Please report it at {}
"#,
env!("CARGO_PKG_REPOSITORY")
)
}
if let Err(e) = result {
TaskSchedulerState::err(scheduler, e)
} else {
TaskSchedulerState::new(scheduler)
}
}
}
} else {
TaskSchedulerState::new(scheduler)
}
}

View file

@ -1,91 +0,0 @@
use std::time::Duration;
use mlua::prelude::*;
use tokio::time::sleep;
use super::super::{scheduler::TaskKind, scheduler::TaskReference, scheduler::TaskScheduler};
/*
Trait definition - same as the implementation, ignore this
We use traits here to prevent misuse of certain scheduler
APIs, making importing of them as intentional as possible
*/
pub trait TaskSchedulerScheduleExt {
fn schedule_blocking(
&self,
thread: LuaThread<'_>,
thread_args: LuaMultiValue<'_>,
) -> LuaResult<TaskReference>;
fn schedule_blocking_deferred(
&self,
thread: LuaThread<'_>,
thread_args: LuaMultiValue<'_>,
) -> LuaResult<TaskReference>;
fn schedule_blocking_after_seconds(
&self,
after_secs: f64,
thread: LuaThread<'_>,
thread_args: LuaMultiValue<'_>,
) -> LuaResult<TaskReference>;
}
/*
Trait implementation
*/
impl TaskSchedulerScheduleExt for TaskScheduler<'_> {
/**
Schedules a lua thread or function to resume ***first*** during this
resumption point, ***skipping ahead*** of any other currently queued tasks.
The given lua thread or function will be resumed
using the given `thread_args` as its argument(s).
*/
fn schedule_blocking(
&self,
thread: LuaThread<'_>,
thread_args: LuaMultiValue<'_>,
) -> LuaResult<TaskReference> {
self.queue_blocking_task(TaskKind::Instant, thread, Some(thread_args))
}
/**
Schedules a lua thread or function to resume ***after all***
currently resuming tasks, during this resumption point.
The given lua thread or function will be resumed
using the given `thread_args` as its argument(s).
*/
fn schedule_blocking_deferred(
&self,
thread: LuaThread<'_>,
thread_args: LuaMultiValue<'_>,
) -> LuaResult<TaskReference> {
self.queue_blocking_task(TaskKind::Deferred, thread, Some(thread_args))
}
/**
Schedules a lua thread or function to
be resumed after waiting asynchronously.
The given lua thread or function will be resumed
using the given `thread_args` as its argument(s).
*/
fn schedule_blocking_after_seconds(
&self,
after_secs: f64,
thread: LuaThread<'_>,
thread_args: LuaMultiValue<'_>,
) -> LuaResult<TaskReference> {
self.queue_async_task(thread, Some(thread_args), async move {
sleep(Duration::from_secs_f64(after_secs)).await;
Ok(None)
})
}
}

View file

@ -1,15 +0,0 @@
mod ext;
mod proxy;
mod scheduler;
mod scheduler_handle;
mod scheduler_message;
mod scheduler_state;
mod task_kind;
mod task_reference;
mod task_waiter;
pub use ext::*;
pub use proxy::*;
pub use scheduler::*;
pub use scheduler_handle::*;
pub use scheduler_state::*;

View file

@ -1,118 +0,0 @@
use mlua::prelude::*;
use super::TaskReference;
/*
Proxy enum to deal with both threads & functions
*/
#[derive(Debug, Clone)]
pub enum LuaThreadOrFunction<'lua> {
Thread(LuaThread<'lua>),
Function(LuaFunction<'lua>),
}
impl<'lua> LuaThreadOrFunction<'lua> {
pub fn into_thread(self, lua: &'lua Lua) -> LuaResult<LuaThread<'lua>> {
match self {
Self::Thread(t) => Ok(t),
Self::Function(f) => lua.create_thread(f),
}
}
}
impl<'lua> From<LuaThread<'lua>> for LuaThreadOrFunction<'lua> {
fn from(value: LuaThread<'lua>) -> Self {
Self::Thread(value)
}
}
impl<'lua> From<LuaFunction<'lua>> for LuaThreadOrFunction<'lua> {
fn from(value: LuaFunction<'lua>) -> Self {
Self::Function(value)
}
}
impl<'lua> FromLua<'lua> for LuaThreadOrFunction<'lua> {
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
match value {
LuaValue::Thread(t) => Ok(Self::Thread(t)),
LuaValue::Function(f) => Ok(Self::Function(f)),
value => Err(LuaError::FromLuaConversionError {
from: value.type_name(),
to: "LuaThreadOrFunction",
message: Some(format!(
"Expected thread or function, got '{}'",
value.type_name()
)),
}),
}
}
}
impl<'lua> IntoLua<'lua> for LuaThreadOrFunction<'lua> {
fn into_lua(self, _: &'lua Lua) -> LuaResult<LuaValue<'lua>> {
match self {
Self::Thread(t) => Ok(LuaValue::Thread(t)),
Self::Function(f) => Ok(LuaValue::Function(f)),
}
}
}
/*
Proxy enum to deal with both threads & task scheduler task references
*/
#[derive(Debug, Clone)]
pub enum LuaThreadOrTaskReference<'lua> {
Thread(LuaThread<'lua>),
TaskReference(TaskReference),
}
impl<'lua> From<LuaThread<'lua>> for LuaThreadOrTaskReference<'lua> {
fn from(value: LuaThread<'lua>) -> Self {
Self::Thread(value)
}
}
impl<'lua> From<TaskReference> for LuaThreadOrTaskReference<'lua> {
fn from(value: TaskReference) -> Self {
Self::TaskReference(value)
}
}
impl<'lua> FromLua<'lua> for LuaThreadOrTaskReference<'lua> {
fn from_lua(value: LuaValue<'lua>, lua: &'lua Lua) -> LuaResult<Self> {
let tname = value.type_name();
match value {
LuaValue::Thread(t) => Ok(Self::Thread(t)),
LuaValue::UserData(u) => {
if let Ok(task) =
LuaUserDataRef::<TaskReference>::from_lua(LuaValue::UserData(u), lua)
{
Ok(Self::TaskReference(*task))
} else {
Err(LuaError::FromLuaConversionError {
from: tname,
to: "thread",
message: Some(format!("Expected thread, got '{tname}'")),
})
}
}
_ => Err(LuaError::FromLuaConversionError {
from: tname,
to: "thread",
message: Some(format!("Expected thread, got '{tname}'")),
}),
}
}
}
impl<'lua> IntoLua<'lua> for LuaThreadOrTaskReference<'lua> {
fn into_lua(self, lua: &'lua Lua) -> LuaResult<LuaValue<'lua>> {
match self {
Self::TaskReference(t) => t.into_lua(lua),
Self::Thread(t) => Ok(LuaValue::Thread(t)),
}
}
}

View file

@ -1,480 +0,0 @@
use core::panic;
use std::{
cell::{Cell, RefCell},
collections::{HashMap, VecDeque},
process::ExitCode,
sync::Arc,
};
use futures_util::{future::LocalBoxFuture, stream::FuturesUnordered, Future};
use mlua::prelude::*;
use tokio::sync::{mpsc, Mutex as AsyncMutex};
use super::{
scheduler_message::TaskSchedulerMessage,
task_waiter::{TaskWaiterFuture, TaskWaiterState},
};
pub use super::{task_kind::TaskKind, task_reference::TaskReference};
type TaskFutureRets<'fut> = LuaResult<Option<LuaMultiValue<'fut>>>;
type TaskFuture<'fut> = LocalBoxFuture<'fut, (Option<TaskReference>, TaskFutureRets<'fut>)>;
/// A struct representing a task contained in the task scheduler
#[derive(Debug)]
pub struct Task {
kind: TaskKind,
thread: LuaRegistryKey,
args: LuaRegistryKey,
}
/// A task scheduler that implements task queues
/// with instant, deferred, and delayed tasks
#[derive(Debug)]
pub struct TaskScheduler<'fut> {
/*
Lots of cell and refcell here, however we need full interior mutability and never outer
since the scheduler struct may be accessed from lua more than once at the same time.
An example of this is the implementation of coroutine.resume, which instantly resumes the given
task, where the task getting resumed may also create new scheduler tasks during its resumption.
The same goes for values used during resumption of futures (`futures` and `futures_rx`)
which must use async-aware mutexes to be cancellation safe across await points.
*/
// Internal state & flags
pub(super) lua: &'static Lua,
pub(super) guid: Cell<usize>,
pub(super) exit_code: Cell<Option<ExitCode>>,
// Blocking tasks
pub(super) tasks: RefCell<HashMap<TaskReference, Task>>,
pub(super) tasks_count: Cell<usize>,
pub(super) tasks_current: Cell<Option<TaskReference>>,
pub(super) tasks_queue_blocking: RefCell<VecDeque<TaskReference>>,
pub(super) tasks_waiter_states:
RefCell<HashMap<TaskReference, Vec<Arc<AsyncMutex<TaskWaiterState<'fut>>>>>>,
pub(super) tasks_current_lua_error: Arc<AsyncMutex<Option<LuaError>>>,
// Future tasks & objects for waking
pub(super) futures: AsyncMutex<FuturesUnordered<TaskFuture<'fut>>>,
pub(super) futures_count: Cell<usize>,
pub(super) futures_background_count: Cell<usize>,
pub(super) futures_tx: mpsc::UnboundedSender<TaskSchedulerMessage>,
pub(super) futures_rx: AsyncMutex<mpsc::UnboundedReceiver<TaskSchedulerMessage>>,
}
impl<'fut> TaskScheduler<'fut> {
/**
Creates a new task scheduler.
*/
pub fn new(lua: &'static Lua) -> LuaResult<Self> {
let (tx, rx) = mpsc::unbounded_channel();
let tasks_current_lua_error = Arc::new(AsyncMutex::new(None));
let tasks_current_lua_error_inner = tasks_current_lua_error.clone();
lua.set_interrupt(move |_| {
match tasks_current_lua_error_inner.try_lock().unwrap().take() {
Some(err) => Err(err),
None => Ok(LuaVmState::Continue),
}
});
Ok(Self {
lua,
guid: Cell::new(0),
exit_code: Cell::new(None),
tasks: RefCell::new(HashMap::new()),
tasks_count: Cell::new(0),
tasks_current: Cell::new(None),
tasks_queue_blocking: RefCell::new(VecDeque::new()),
tasks_waiter_states: RefCell::new(HashMap::new()),
tasks_current_lua_error,
futures: AsyncMutex::new(FuturesUnordered::new()),
futures_tx: tx,
futures_rx: AsyncMutex::new(rx),
futures_count: Cell::new(0),
futures_background_count: Cell::new(0),
})
}
/**
Consumes and leaks the task scheduler,
returning a static reference `&'static TaskScheduler`.
This function is useful when the task scheduler object is
supposed to live for the remainder of the program's life.
Note that dropping the returned reference will cause a memory leak.
*/
pub fn into_static(self) -> &'static Self {
Box::leak(Box::new(self))
}
/**
Stores the exit code for the task scheduler.
This will be passed back to the Rust thread that is running the task scheduler,
in the [`TaskSchedulerState`] returned on resumption of the task scheduler queue.
Setting this exit code will signal to that thread that it
should stop resuming tasks, and gracefully terminate the program.
*/
pub fn set_exit_code(&self, code: ExitCode) {
self.exit_code.set(Some(code));
}
/**
Forwards a lua error to be emitted as soon as possible,
after any current blocking / queued tasks have been resumed.
Useful when an async function may call into Lua and get a
result back, without erroring out of the entire async block.
*/
pub fn forward_lua_error(&self, err: LuaError) {
let sender = self.futures_tx.clone();
sender
.send(TaskSchedulerMessage::NewLuaErrorReady(err))
.unwrap_or_else(|e| {
panic!(
"\
\nFailed to forward lua error - this is an internal error! \
\nPlease report it at {} \
\nDetails: {e} \
",
env!("CARGO_PKG_REPOSITORY")
)
});
}
/**
Forces the current task to be set to the given reference.
Useful if a task is to be resumed externally but full
compatibility with the task scheduler is still necessary.
*/
pub(crate) fn force_set_current_task(&self, reference: Option<TaskReference>) {
self.tasks_current.set(reference);
}
/**
Checks if a task still exists in the scheduler.
A task may no longer exist in the scheduler if it has been manually
cancelled and removed by calling [`TaskScheduler::cancel_task()`].
*/
#[allow(dead_code)]
pub fn contains_task(&self, reference: TaskReference) -> bool {
self.tasks.borrow().contains_key(&reference)
}
/**
Returns the currently running task, if any.
*/
pub fn current_task(&self) -> Option<TaskReference> {
self.tasks_current.get()
}
/**
Returns the status of a specific task, if it exists in the scheduler.
*/
pub fn get_task_status(&self, reference: TaskReference) -> Option<LuaString> {
self.tasks.borrow().get(&reference).map(|task| {
let status: LuaFunction = self
.lua
.named_registry_value("co.status")
.expect("Missing coroutine status function in registry");
let thread: LuaThread = self
.lua
.registry_value(&task.thread)
.expect("Task thread missing from registry");
status
.call(thread)
.expect("Task thread failed to call status")
})
}
/**
Creates a new task, storing a new Lua thread
for it, as well as the arguments to give the
thread on resumption, in the Lua registry.
Note that this task will ***not*** resume on its
own, it needs to be used together with either the
scheduling functions or [`TaskScheduler::resume_task`].
*/
pub fn create_task(
&self,
kind: TaskKind,
thread: LuaThread<'_>,
thread_args: Option<LuaMultiValue<'_>>,
inherit_current_guid: bool,
) -> LuaResult<TaskReference> {
// Store the thread and its arguments in the registry
// NOTE: We must convert to a vec since multis
// can't be stored in the registry directly
let task_args_vec: Option<Vec<LuaValue>> = thread_args.map(|opt| opt.into_vec());
let task_args_key: LuaRegistryKey = self.lua.create_registry_value(task_args_vec)?;
let task_thread_key: LuaRegistryKey = self.lua.create_registry_value(thread)?;
// Create the full task struct
let task = Task {
kind,
thread: task_thread_key,
args: task_args_key,
};
// Create the task ref to use
let guid = if inherit_current_guid {
self.current_task()
.ok_or_else(|| LuaError::RuntimeError("No current guid to inherit".to_string()))?
.id()
} else {
let guid = self.guid.get();
self.guid.set(guid + 1);
guid
};
let reference = TaskReference::new(kind, guid);
// Increment the corresponding task counter
match kind {
TaskKind::Future => self.futures_count.set(self.futures_count.get() + 1),
_ => self.tasks_count.set(self.tasks_count.get() + 1),
}
// Add the task to the scheduler
{
let mut tasks = self.tasks.borrow_mut();
tasks.insert(reference, task);
}
Ok(reference)
}
/**
Cancels a task, if the task still exists in the scheduler.
It is possible to hold one or more task references that point
to a task that no longer exists in the scheduler, and calling
this method with one of those references will return `false`.
*/
pub fn remove_task(&self, reference: TaskReference) -> LuaResult<bool> {
/*
Remove the task from the task list and the Lua registry
This is all we need to do since resume_task will always
ignore resumption of any task that no longer exists there
This does lead to having some amount of "junk" futures that will
build up in the queue but these will get cleaned up and not block
the program from exiting since the scheduler only runs until there
are no tasks left in the task list, the futures do not matter there
*/
let mut found = false;
let mut tasks = self.tasks.borrow_mut();
// Unfortunately we have to loop through to find which task
// references to remove instead of removing directly since
// tasks can switch kinds between instant, deferred, future
let tasks_to_remove: Vec<_> = tasks
.keys()
.filter(|task_ref| task_ref.id() == reference.id())
.copied()
.collect();
for task_ref in &tasks_to_remove {
if let Some(task) = tasks.remove(task_ref) {
// Decrement the corresponding task counter
match task.kind {
TaskKind::Future => self.futures_count.set(self.futures_count.get() - 1),
_ => self.tasks_count.set(self.tasks_count.get() - 1),
}
// NOTE: We need to close the thread here to
// make 100% sure that nothing can resume it
let close: LuaFunction = self.lua.named_registry_value("co.close")?;
let thread: LuaThread = self.lua.registry_value(&task.thread)?;
close.call(thread)?;
self.lua.remove_registry_value(task.thread)?;
self.lua.remove_registry_value(task.args)?;
found = true;
}
}
Ok(found)
}
/**
Resumes a task, if the task still exists in the scheduler.
A task may no longer exist in the scheduler if it has been manually
cancelled and removed by calling [`TaskScheduler::cancel_task()`].
This will be a no-op if the task no longer exists.
*/
pub fn resume_task<'a, 'r>(
&self,
reference: TaskReference,
override_args: Option<LuaResult<LuaMultiValue<'a>>>,
) -> LuaResult<(LuaThreadStatus, LuaMultiValue<'r>)>
where
'a: 'r,
{
// Fetch and check if the task was removed, if it got
// removed it means it was intentionally cancelled
let task = {
let mut tasks = self.tasks.borrow_mut();
match tasks.remove(&reference) {
Some(task) => task,
None => return Ok((LuaThreadStatus::Unresumable, LuaMultiValue::new())),
}
};
// Decrement the corresponding task counter
match task.kind {
TaskKind::Future => self.futures_count.set(self.futures_count.get() - 1),
_ => self.tasks_count.set(self.tasks_count.get() - 1),
}
// Fetch and remove the thread to resume + its arguments
let thread: LuaThread = self.lua.registry_value(&task.thread)?;
let thread_args: Option<LuaMultiValue> = {
self.lua
.registry_value::<Option<Vec<LuaValue>>>(&task.args)
.expect("Failed to get stored args for task")
.map(LuaMultiValue::from_vec)
};
self.lua.remove_registry_value(task.thread)?;
self.lua.remove_registry_value(task.args)?;
// We got everything we need and our references
// were cleaned up properly, resume the thread
self.tasks_current.set(Some(reference));
let rets = match override_args {
Some(override_res) => match override_res {
Ok(args) => thread.resume(args),
Err(e) => {
// NOTE: Setting this error here means that when the thread
// is resumed it will error instantly, so we don't need
// to call it with proper args, empty args is fine
self.tasks_current_lua_error.try_lock().unwrap().replace(e);
thread.resume(())
}
},
None => match thread_args {
Some(args) => thread.resume(args),
None => thread.resume(()),
},
};
self.tasks_current.set(None);
match rets {
Ok(rets) => Ok((thread.status(), rets)),
Err(e) => Err(e),
}
}
/**
Queues a new blocking task to run on the task scheduler.
*/
pub(crate) fn queue_blocking_task(
&self,
kind: TaskKind,
thread: LuaThread<'_>,
thread_args: Option<LuaMultiValue<'_>>,
) -> LuaResult<TaskReference> {
if kind == TaskKind::Future {
panic!("Tried to schedule future using normal task schedule method")
}
let task_ref = self.create_task(kind, thread, thread_args, false)?;
// Add the task to the front of the queue, unless it
// should be deferred, in that case add it to the back
let mut queue = self.tasks_queue_blocking.borrow_mut();
let num_prev_blocking_tasks = queue.len();
if kind == TaskKind::Deferred {
queue.push_back(task_ref);
} else {
queue.push_front(task_ref);
}
/*
If we had any previous task and are currently async
waiting on tasks, we should send a signal to wake up
and run the new blocking task that was just queued
This can happen in cases such as an async http
server waking up from a connection and then wanting to
run a lua callback in response, to create the.. response
*/
if num_prev_blocking_tasks == 0 {
self.futures_tx
.send(TaskSchedulerMessage::NewBlockingTaskReady)
.expect("Futures waker channel was closed")
}
Ok(task_ref)
}
/**
Queues a new future to run on the task scheduler.
*/
pub(crate) fn queue_async_task(
&self,
thread: LuaThread<'_>,
thread_args: Option<LuaMultiValue<'_>>,
fut: impl Future<Output = TaskFutureRets<'fut>> + 'fut,
) -> LuaResult<TaskReference> {
let task_ref = self.create_task(TaskKind::Future, thread, thread_args, false)?;
let futs = self
.futures
.try_lock()
.expect("Tried to add future to queue during futures resumption");
futs.push(Box::pin(async move {
let result = fut.await;
(Some(task_ref), result)
}));
Ok(task_ref)
}
/**
Queues a new future to run on the task scheduler,
inheriting the task id of the currently running task.
*/
pub(crate) fn queue_async_task_inherited(
&self,
thread: LuaThread<'_>,
thread_args: Option<LuaMultiValue<'_>>,
fut: impl Future<Output = TaskFutureRets<'fut>> + 'fut,
) -> LuaResult<TaskReference> {
let task_ref = self.create_task(TaskKind::Future, thread, thread_args, true)?;
let futs = self
.futures
.try_lock()
.expect("Tried to add future to queue during futures resumption");
futs.push(Box::pin(async move {
let result = fut.await;
(Some(task_ref), result)
}));
Ok(task_ref)
}
/**
Waits for a task to complete.
Panics if the task is not currently in the scheduler.
*/
pub(crate) async fn wait_for_task_completion(
&self,
reference: TaskReference,
) -> LuaResult<LuaMultiValue> {
if !self.tasks.borrow().contains_key(&reference) {
panic!("Task does not exist in scheduler")
}
let state = TaskWaiterState::new();
{
let mut all_states = self.tasks_waiter_states.borrow_mut();
all_states
.entry(reference)
.or_insert_with(Vec::new)
.push(Arc::clone(&state));
}
TaskWaiterFuture::new(&state).await
}
/**
Wakes a task that has been completed and may have external code
waiting on it using [`TaskScheduler::wait_for_task_completion`].
*/
pub(super) fn wake_completed_task(
&self,
reference: TaskReference,
result: LuaResult<LuaMultiValue<'fut>>,
) {
if let Some(waiter_states) = self.tasks_waiter_states.borrow_mut().remove(&reference) {
for waiter_state in waiter_states {
waiter_state.try_lock().unwrap().finalize(result.clone());
}
}
}
}

View file

@ -1,46 +0,0 @@
use core::panic;
use mlua::prelude::*;
use tokio::sync::mpsc;
use super::scheduler_message::TaskSchedulerMessage;
/**
A handle to a registered asynchronous background task.
[`TaskSchedulerAsyncHandle::unregister`] must be
called upon completion of the background task to
prevent the task scheduler from running indefinitely.
*/
#[must_use = "Background tasks must be unregistered"]
#[derive(Debug)]
pub struct TaskSchedulerAsyncHandle {
unregistered: bool,
sender: mpsc::UnboundedSender<TaskSchedulerMessage>,
}
impl TaskSchedulerAsyncHandle {
pub fn new(sender: mpsc::UnboundedSender<TaskSchedulerMessage>) -> Self {
Self {
unregistered: false,
sender,
}
}
pub fn unregister(mut self, result: LuaResult<()>) {
self.unregistered = true;
self.sender
.send(TaskSchedulerMessage::Terminated(result))
.unwrap_or_else(|_| {
panic!(
"\
\nFailed to unregister background task - this is an internal error! \
\nPlease report it at {} \
\nDetails: Manual \
",
env!("CARGO_PKG_REPOSITORY")
)
});
}
}

View file

@ -1,11 +0,0 @@
use mlua::prelude::*;
/// Internal message enum for the task scheduler, used to notify
/// futures to wake up and schedule their respective blocking tasks
#[derive(Debug, Clone)]
pub enum TaskSchedulerMessage {
NewBlockingTaskReady,
NewLuaErrorReady(LuaError),
Spawned,
Terminated(LuaResult<()>),
}

View file

@ -1,172 +0,0 @@
use std::{fmt, process::ExitCode};
use mlua::prelude::*;
use super::scheduler::TaskScheduler;
/// Struct representing the current state of the task scheduler
#[derive(Debug, Clone)]
#[must_use = "Scheduler state must be checked after every resumption"]
pub struct TaskSchedulerState {
pub(super) lua_error: Option<LuaError>,
pub(super) exit_code: Option<ExitCode>,
pub(super) num_blocking: usize,
pub(super) num_futures: usize,
pub(super) num_background: usize,
}
impl TaskSchedulerState {
pub(super) fn new(sched: &TaskScheduler) -> Self {
Self {
lua_error: None,
exit_code: sched.exit_code.get(),
num_blocking: sched.tasks_count.get(),
num_futures: sched.futures_count.get(),
num_background: sched.futures_background_count.get(),
}
}
pub(super) fn err(sched: &TaskScheduler, err: LuaError) -> Self {
let mut this = Self::new(sched);
this.lua_error = Some(err);
this
}
/**
Returns a clone of the error from
this task scheduler result, if any.
*/
pub fn get_lua_error(&self) -> Option<LuaError> {
self.lua_error.clone()
}
/**
Returns a clone of the exit code from
this task scheduler result, if any.
*/
pub fn get_exit_code(&self) -> Option<ExitCode> {
self.exit_code
}
/**
Returns `true` if the task scheduler still
has blocking lua threads left to run.
*/
pub fn is_blocking(&self) -> bool {
self.num_blocking > 0
}
/**
Returns `true` if the task scheduler has finished all
blocking lua tasks, but still has yielding tasks running.
*/
pub fn is_yielding(&self) -> bool {
self.num_blocking == 0 && self.num_futures > 0
}
/**
Returns `true` if the task scheduler has finished all
lua threads, but still has background tasks running.
*/
pub fn is_background(&self) -> bool {
self.num_blocking == 0 && self.num_futures == 0 && self.num_background > 0
}
/**
Returns `true` if the task scheduler is done,
meaning it has no lua threads left to run, and
no spawned tasks are running in the background.
Also returns `true` if a task has requested to exit the process.
*/
pub fn is_done(&self) -> bool {
self.exit_code.is_some()
|| (self.num_blocking == 0 && self.num_futures == 0 && self.num_background == 0)
}
}
impl fmt::Display for TaskSchedulerState {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let status = if self.is_blocking() {
"Busy"
} else if self.is_yielding() {
"Yielding"
} else if self.is_background() {
"Background"
} else {
"Done"
};
let code = match self.get_exit_code() {
Some(code) => format!("{code:?}"),
None => "-".to_string(),
};
let err = match self.get_lua_error() {
Some(e) => format!("{e:?}")
.as_bytes()
.chunks(42) // Kinda arbitrary but should fit in most terminals
.enumerate()
.map(|(idx, buf)| {
format!(
"{}{}{}{}{}",
if idx == 0 { "" } else { "\n" },
if idx == 0 {
"".to_string()
} else {
" ".repeat(16)
},
if idx == 0 { "" } else { "" },
String::from_utf8_lossy(buf),
if buf.len() == 42 { "" } else { "" },
)
})
.collect::<String>(),
None => "-".to_string(),
};
let parts = vec![
format!("Status │ {status}"),
format!("Tasks active │ {}", self.num_blocking),
format!("Tasks background │ {}", self.num_background),
format!("Status code │ {code}"),
format!("Lua error │ {err}"),
];
let lengths = parts
.iter()
.map(|part| {
part.lines()
.next()
.unwrap()
.trim_end_matches("")
.chars()
.count()
})
.collect::<Vec<_>>();
let longest = &parts
.iter()
.enumerate()
.fold(0, |acc, (index, _)| acc.max(lengths[index]));
let sep = "".repeat(longest + 2);
writeln!(f, "┌{}┐", &sep)?;
for (index, part) in parts.iter().enumerate() {
writeln!(
f,
"│ {}{} │",
part.trim_end_matches(""),
" ".repeat(
longest
- part
.lines()
.last()
.unwrap()
.trim_end_matches("")
.chars()
.count()
)
)?;
if index < parts.len() - 1 {
writeln!(f, "┝{}┥", &sep)?;
}
}
write!(f, "└{}┘", &sep)?;
Ok(())
}
}

View file

@ -1,39 +0,0 @@
use std::fmt;
/// Enum representing different kinds of tasks
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum TaskKind {
Instant,
Deferred,
Future,
}
#[allow(dead_code)]
impl TaskKind {
pub fn is_instant(&self) -> bool {
*self == Self::Instant
}
pub fn is_deferred(&self) -> bool {
*self == Self::Deferred
}
pub fn is_blocking(&self) -> bool {
*self != Self::Future
}
pub fn is_future(&self) -> bool {
*self == Self::Future
}
}
impl fmt::Display for TaskKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name: &'static str = match self {
TaskKind::Instant => "Instant",
TaskKind::Deferred => "Deferred",
TaskKind::Future => "Future",
};
write!(f, "{name}")
}
}

View file

@ -1,51 +0,0 @@
use std::{
fmt,
hash::{Hash, Hasher},
};
use mlua::prelude::*;
use super::task_kind::TaskKind;
/// A lightweight, copyable struct that represents a
/// task in the scheduler and is accessible from Lua
#[derive(Debug, Clone, Copy)]
pub struct TaskReference {
kind: TaskKind,
guid: usize,
}
impl TaskReference {
pub const fn new(kind: TaskKind, guid: usize) -> Self {
Self { kind, guid }
}
pub const fn id(&self) -> usize {
self.guid
}
}
impl fmt::Display for TaskReference {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.guid == 0 {
write!(f, "TaskReference(MAIN)")
} else {
write!(f, "TaskReference({} - {})", self.kind, self.guid)
}
}
}
impl Eq for TaskReference {}
impl PartialEq for TaskReference {
fn eq(&self, other: &Self) -> bool {
self.guid == other.guid
}
}
impl Hash for TaskReference {
fn hash<H: Hasher>(&self, state: &mut H) {
self.guid.hash(state);
}
}
impl LuaUserData for TaskReference {}

View file

@ -1,66 +0,0 @@
use std::{
future::Future,
pin::Pin,
sync::Arc,
task::{Context, Poll, Waker},
};
use tokio::sync::Mutex as AsyncMutex;
use mlua::prelude::*;
#[derive(Debug, Clone)]
pub(super) struct TaskWaiterState<'fut> {
rets: Option<LuaResult<LuaMultiValue<'fut>>>,
waker: Option<Waker>,
}
impl<'fut> TaskWaiterState<'fut> {
pub fn new() -> Arc<AsyncMutex<Self>> {
Arc::new(AsyncMutex::new(TaskWaiterState {
rets: None,
waker: None,
}))
}
pub fn finalize(&mut self, rets: LuaResult<LuaMultiValue<'fut>>) {
self.rets = Some(rets);
if let Some(waker) = self.waker.take() {
waker.wake();
}
}
}
#[derive(Debug)]
pub(super) struct TaskWaiterFuture<'fut> {
state: Arc<AsyncMutex<TaskWaiterState<'fut>>>,
}
impl<'fut> TaskWaiterFuture<'fut> {
pub fn new(state: &Arc<AsyncMutex<TaskWaiterState<'fut>>>) -> Self {
Self {
state: Arc::clone(state),
}
}
}
impl<'fut> Clone for TaskWaiterFuture<'fut> {
fn clone(&self) -> Self {
Self {
state: Arc::clone(&self.state),
}
}
}
impl<'fut> Future for TaskWaiterFuture<'fut> {
type Output = LuaResult<LuaMultiValue<'fut>>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut shared_state = self.state.try_lock().unwrap();
if let Some(rets) = shared_state.rets.clone() {
Poll::Ready(rets)
} else {
shared_state.waker = Some(cx.waker().clone());
Poll::Pending
}
}
}

View file

@ -1,107 +1,82 @@
use std::process::ExitCode; use std::process::ExitCode;
use lua::task::{TaskScheduler, TaskSchedulerResumeExt, TaskSchedulerScheduleExt}; use mlua::Lua;
use mlua::prelude::*;
use tokio::task::LocalSet;
pub mod builtins;
pub mod importer;
pub mod lua;
mod builtins;
mod error; mod error;
mod globals;
mod scheduler;
pub(crate) mod util;
use self::scheduler::{LuaSchedulerExt, Scheduler};
pub use error::LuneError; pub use error::LuneError;
#[derive(Clone, Debug, Default)] // TODO: Rename this struct to "Runtime" instead for the
// next breaking release, it's a more fitting name and
// will probably be more obvious when browsing files
#[derive(Debug, Clone)]
pub struct Lune { pub struct Lune {
lua: &'static Lua,
scheduler: &'static Scheduler<'static>,
args: Vec<String>, args: Vec<String>,
} }
impl Lune { impl Lune {
/** /**
Creates a new Lune script runner. Creates a new Lune runtime, with a new Luau VM and task scheduler.
*/ */
#[allow(clippy::new_without_default)]
pub fn new() -> Self { pub fn new() -> Self {
Self::default() /*
FUTURE: Stop leaking these when we have removed the lifetime
on the scheduler and can place them in lua app data using arc
See the scheduler struct for more notes
*/
let lua = Lua::new().into_static();
let scheduler = Scheduler::new().into_static();
lua.set_scheduler(scheduler);
globals::inject_all(lua).expect("Failed to inject lua globals");
Self {
lua,
scheduler,
args: Vec::new(),
}
} }
/** /**
Arguments to give in `process.args` for a Lune script. Sets arguments to give in `process.args` for Lune scripts.
*/ */
pub fn with_args<V>(mut self, args: V) -> Self pub fn with_args<V>(mut self, args: V) -> Self
where where
V: Into<Vec<String>>, V: Into<Vec<String>>,
{ {
self.args = args.into(); self.args = args.into();
self.lua.set_app_data(self.args.clone());
self self
} }
/** /**
Runs a Lune script. Runs a Lune script inside of the current runtime.
This will create a new sandboxed Luau environment with the configured This will preserve any modifications to global values / context.
globals and arguments, running inside of a [`tokio::task::LocalSet`].
Some Lune globals may spawn separate tokio tasks on other threads, but the Luau
environment itself is guaranteed to run on a single thread in the local set.
Note that this will create a static Lua instance and task scheduler that will
both live for the remainer of the program, and that this leaks memory using
[`Box::leak`] that will then get deallocated when the program exits.
*/ */
pub async fn run( pub async fn run(
&self, &mut self,
script_name: impl AsRef<str>, script_name: impl AsRef<str>,
script_contents: impl AsRef<[u8]>, script_contents: impl AsRef<[u8]>,
) -> Result<ExitCode, LuneError> { ) -> Result<ExitCode, LuneError> {
self.run_inner(script_name, script_contents) let main = self
.await .lua
.map_err(LuneError::from)
}
async fn run_inner(
&self,
script_name: impl AsRef<str>,
script_contents: impl AsRef<[u8]>,
) -> Result<ExitCode, LuaError> {
// Create our special lune-flavored Lua object with extra registry values
let lua = lua::create_lune_lua()?.into_static();
// Create our task scheduler and all globals
// NOTE: Some globals require the task scheduler to exist on startup
let sched = TaskScheduler::new(lua)?.into_static();
lua.set_app_data(sched);
importer::create(lua, self.args.clone())?;
// Create the main thread and schedule it
let main_chunk = lua
.load(script_contents.as_ref()) .load(script_contents.as_ref())
.set_name(script_name.as_ref()) .set_name(script_name.as_ref());
.into_function()?;
let main_thread = lua.create_thread(main_chunk)?; self.scheduler.push_back(self.lua, main, ())?;
let main_thread_args = LuaValue::Nil.into_lua_multi(lua)?;
sched.schedule_blocking(main_thread, main_thread_args)?; Ok(self.scheduler.run_to_completion(self.lua).await)
// Keep running the scheduler until there are either no tasks
// left to run, or until a task requests to exit the process
let exit_code = LocalSet::new()
.run_until(async move {
let mut got_error = false;
loop {
let result = sched.resume_queue().await;
if let Some(err) = result.get_lua_error() {
eprintln!("{}", LuneError::from(err));
got_error = true;
}
if result.is_done() {
if let Some(exit_code) = result.get_exit_code() {
break exit_code;
} else if got_error {
break ExitCode::FAILURE;
} else {
break ExitCode::SUCCESS;
}
}
}
})
.await;
Ok(exit_code)
} }
} }

View file

@ -0,0 +1,138 @@
use futures_util::Future;
use mlua::prelude::*;
use tokio::{
sync::oneshot::{self, Receiver},
task,
};
use super::{IntoLuaThread, Scheduler};
impl<'fut> Scheduler<'fut> {
/**
Checks if there are any futures to run, for
lua futures and background futures respectively.
*/
pub(super) fn has_futures(&self) -> (bool, bool) {
(
self.futures_lua
.try_lock()
.expect("Failed to lock lua futures for check")
.len()
> 0,
self.futures_background
.try_lock()
.expect("Failed to lock background futures for check")
.len()
> 0,
)
}
/**
Schedules a plain future to run in the background.
This will potentially spawn the future on a different thread, using
[`task::spawn`], meaning the provided future must implement [`Send`].
Returns a [`Receiver`] which may be `await`-ed
to retrieve the result of the spawned future.
This [`Receiver`] may be safely ignored if the result of the
spawned future is not needed, the future will run either way.
*/
pub fn spawn<F>(&self, fut: F) -> Receiver<F::Output>
where
F: Future + Send + 'static,
F::Output: Send + 'static,
{
let (tx, rx) = oneshot::channel();
let handle = task::spawn(async move {
let res = fut.await;
tx.send(res).ok();
});
// NOTE: We must spawn a future on our scheduler which awaits
// the handle from tokio to start driving our future properly
let futs = self
.futures_background
.try_lock()
.expect("Failed to lock futures queue for background tasks");
futs.push(Box::pin(async move {
handle.await.ok();
}));
// NOTE: We might be resuming lua futures, need to signal that a
// new background future is ready to break out of futures resumption
self.state.message_sender().send_spawned_background_future();
rx
}
/**
Equivalent to [`spawn`], except the future is only
spawned on the Lune scheduler, and on the main thread.
*/
pub fn spawn_local<F>(&self, fut: F) -> Receiver<F::Output>
where
F: Future + 'static,
F::Output: 'static,
{
let (tx, rx) = oneshot::channel();
let futs = self
.futures_background
.try_lock()
.expect("Failed to lock futures queue for background tasks");
futs.push(Box::pin(async move {
let res = fut.await;
tx.send(res).ok();
}));
// NOTE: We might be resuming lua futures, need to signal that a
// new background future is ready to break out of futures resumption
self.state.message_sender().send_spawned_background_future();
rx
}
/**
Schedules the given `thread` to run when the given `fut` completes.
If the given future returns a [`LuaError`], that error will be passed to the given `thread`.
*/
pub fn spawn_thread<F, FR>(
&'fut self,
lua: &'fut Lua,
thread: impl IntoLuaThread<'fut>,
fut: F,
) -> LuaResult<()>
where
FR: IntoLuaMulti<'fut>,
F: Future<Output = LuaResult<FR>> + 'fut,
{
let thread = thread.into_lua_thread(lua)?;
let futs = self.futures_lua.try_lock().expect(
"Failed to lock futures queue - \
can't schedule future lua threads during futures resumption",
);
futs.push(Box::pin(async move {
match fut.await.and_then(|rets| rets.into_lua_multi(lua)) {
Err(e) => {
self.push_err(lua, thread, e)
.expect("Failed to schedule future err thread");
}
Ok(v) => {
self.push_back(lua, thread, v)
.expect("Failed to schedule future thread");
}
}
}));
// NOTE: We might be resuming background futures, need to signal that a
// new background future is ready to break out of futures resumption
self.state.message_sender().send_spawned_lua_future();
Ok(())
}
}

View file

@ -0,0 +1,260 @@
use std::{process::ExitCode, sync::Arc};
use futures_util::StreamExt;
use mlua::prelude::*;
use tokio::task::LocalSet;
use tracing::debug;
use crate::lune::util::traits::LuaEmitErrorExt;
use super::Scheduler;
impl<'fut> Scheduler<'fut> {
/**
Runs all lua threads to completion.
*/
fn run_lua_threads(&self, lua: &Lua) {
if self.state.has_exit_code() {
return;
}
let mut count = 0;
// Pop threads from the scheduler until there are none left
while let Some(thread) = self
.pop_thread()
.expect("Failed to pop thread from scheduler")
{
// Deconstruct the scheduler thread into its parts
let thread_id = thread.id();
let (thread, args) = thread.into_inner(lua);
// Make sure this thread is still resumable, it might have
// been resumed somewhere else or even have been cancelled
if thread.status() != LuaThreadStatus::Resumable {
continue;
}
// Resume the thread, ensuring that the schedulers
// current thread id is set correctly for error catching
self.state.set_current_thread_id(Some(thread_id));
let res = thread.resume::<_, LuaMultiValue>(args);
self.state.set_current_thread_id(None);
count += 1;
// If we got any resumption (lua-side) error, increment
// the error count of the scheduler so we can exit with
// a non-zero exit code, and print it out to stderr
if let Err(err) = &res {
self.state.increment_error_count();
lua.emit_error(err.clone());
}
// If the thread has finished running completely,
// send results of final resume to any listeners
if thread.status() != LuaThreadStatus::Resumable {
// NOTE: Threads that were spawned to resume
// with an error will not have a result sender
if let Some(sender) = self.thread_senders.borrow_mut().remove(&thread_id) {
if sender.receiver_count() > 0 {
let stored = match res {
Err(e) => Err(e),
Ok(v) => Ok(Arc::new(lua.create_registry_value(v.into_vec()).expect(
"Failed to store thread results in registry - out of memory",
))),
};
sender
.send(stored)
.expect("Failed to broadcast thread results");
}
}
}
if self.state.has_exit_code() {
break;
}
}
if count > 0 {
debug! {
%count,
"resumed lua"
}
}
}
/**
Runs the next lua future to completion.
Panics if no lua future is queued.
*/
async fn run_future_lua(&self) {
let mut futs = self
.futures_lua
.try_lock()
.expect("Failed to lock lua futures for resumption");
assert!(futs.len() > 0, "No lua futures are queued");
futs.next().await;
}
/**
Runs the next background future to completion.
Panics if no background future is queued.
*/
async fn run_future_background(&self) {
let mut futs = self
.futures_background
.try_lock()
.expect("Failed to lock background futures for resumption");
assert!(futs.len() > 0, "No background futures are queued");
futs.next().await;
}
/**
Runs as many futures as possible, until a new lua thread
is ready, or an exit code has been set for the scheduler.
### Implementation details
Running futures on our scheduler consists of a couple moving parts:
1. An unordered futures queue for lua (main thread, local) futures
2. An unordered futures queue for background (multithreaded, 'static lifetime) futures
3. A signal for breaking out of futures resumption
The two unordered futures queues need to run concurrently,
but since `FuturesUnordered` returns instantly if it does
not currently have any futures queued on it, we need to do
this branching loop, checking if each queue has futures first.
We also need to listen for our signal, to see if we should break out of resumption:
* Always break out of resumption if a new lua thread is ready
* Always break out of resumption if an exit code has been set
* Break out of lua futures resumption if we have a new background future
* Break out of background futures resumption if we have a new lua future
We need to listen for both future queues concurrently,
and break out whenever the other corresponding queue has
a new future, since the other queue may resume sooner.
*/
async fn run_futures(&self) {
let (mut has_lua, mut has_background) = self.has_futures();
if !has_lua && !has_background {
return;
}
let mut rx = self.state.message_receiver();
let mut count = 0;
while has_lua || has_background {
if has_lua && has_background {
tokio::select! {
_ = self.run_future_lua() => {},
_ = self.run_future_background() => {},
msg = rx.recv() => {
if let Some(msg) = msg {
if msg.should_break_futures() {
break;
}
}
}
}
count += 1;
} else if has_lua {
tokio::select! {
_ = self.run_future_lua() => {},
msg = rx.recv() => {
if let Some(msg) = msg {
if msg.should_break_lua_futures() {
break;
}
}
}
}
count += 1;
} else if has_background {
tokio::select! {
_ = self.run_future_background() => {},
msg = rx.recv() => {
if let Some(msg) = msg {
if msg.should_break_background_futures() {
break;
}
}
}
}
count += 1;
}
(has_lua, has_background) = self.has_futures();
}
if count > 0 {
debug! {
%count,
"resumed lua futures"
}
}
}
/**
Runs the scheduler to completion in a [`LocalSet`],
both normal lua threads and futures, prioritizing
lua threads over completion of any pending futures.
Will emit lua output and errors to stdout and stderr.
*/
pub async fn run_to_completion(&self, lua: &Lua) -> ExitCode {
if let Some(code) = self.state.exit_code() {
return ExitCode::from(code);
}
let set = LocalSet::new();
let _guard = set.enter();
loop {
// 1. Run lua threads until exit or there are none left
self.run_lua_threads(lua);
// 2. If we got a manual exit code from lua we should
// not try to wait for any pending futures to complete
if self.state.has_exit_code() {
break;
}
// 3. Keep resuming futures until there are no futures left to
// resume, or until we manually break out of resumption for any
// reason, this may be because a future spawned a new lua thread
self.run_futures().await;
// 4. Once again, check for an exit code, in case a future sets one
if self.state.has_exit_code() {
break;
}
// 5. If we have no lua threads or futures remaining,
// we have now run the scheduler until completion
let (has_future_lua, has_future_background) = self.has_futures();
if !has_future_lua && !has_future_background && !self.has_thread() {
break;
}
}
if let Some(code) = self.state.exit_code() {
debug! {
%code,
"scheduler ran to completion"
};
ExitCode::from(code)
} else if self.state.has_errored() {
debug!("scheduler ran to completion, with failure");
ExitCode::FAILURE
} else {
debug!("scheduler ran to completion, with success");
ExitCode::SUCCESS
}
}
}

View file

@ -0,0 +1,181 @@
use std::sync::Arc;
use mlua::prelude::*;
use super::{
thread::{SchedulerThread, SchedulerThreadId, SchedulerThreadSender},
IntoLuaThread, Scheduler,
};
impl<'fut> Scheduler<'fut> {
/**
Checks if there are any lua threads to run.
*/
pub(super) fn has_thread(&self) -> bool {
!self
.threads
.try_borrow()
.expect("Failed to borrow threads vec")
.is_empty()
}
/**
Pops the next thread to run, from the front of the scheduler.
Returns `None` if there are no threads left to run.
*/
pub(super) fn pop_thread(&self) -> LuaResult<Option<SchedulerThread>> {
match self
.threads
.try_borrow_mut()
.into_lua_err()
.context("Failed to borrow threads vec")?
.pop_front()
{
Some(thread) => Ok(Some(thread)),
None => Ok(None),
}
}
/**
Schedules the `thread` to be resumed with the given [`LuaError`].
*/
pub fn push_err<'a>(
&self,
lua: &'a Lua,
thread: impl IntoLuaThread<'a>,
err: LuaError,
) -> LuaResult<()> {
let thread = thread.into_lua_thread(lua)?;
let args = LuaMultiValue::new(); // Will be resumed with error, don't need real args
let thread = SchedulerThread::new(lua, thread, args);
let thread_id = thread.id();
self.state.set_thread_error(thread_id, err);
self.threads
.try_borrow_mut()
.into_lua_err()
.context("Failed to borrow threads vec")?
.push_front(thread);
// NOTE: We might be resuming futures, need to signal that a
// new lua thread is ready to break out of futures resumption
self.state.message_sender().send_pushed_lua_thread();
Ok(())
}
/**
Schedules the `thread` to be resumed with the given `args`
right away, before any other currently scheduled threads.
*/
pub fn push_front<'a>(
&self,
lua: &'a Lua,
thread: impl IntoLuaThread<'a>,
args: impl IntoLuaMulti<'a>,
) -> LuaResult<SchedulerThreadId> {
let thread = thread.into_lua_thread(lua)?;
let args = args.into_lua_multi(lua)?;
let thread = SchedulerThread::new(lua, thread, args);
let thread_id = thread.id();
self.threads
.try_borrow_mut()
.into_lua_err()
.context("Failed to borrow threads vec")?
.push_front(thread);
// NOTE: We might be resuming the same thread several times and
// pushing it to the scheduler several times before it is done,
// and we should only ever create one result sender per thread
self.thread_senders
.borrow_mut()
.entry(thread_id)
.or_insert_with(|| SchedulerThreadSender::new(1));
// NOTE: We might be resuming futures, need to signal that a
// new lua thread is ready to break out of futures resumption
self.state.message_sender().send_pushed_lua_thread();
Ok(thread_id)
}
/**
Schedules the `thread` to be resumed with the given `args`
after all other current threads have been resumed.
*/
pub fn push_back<'a>(
&self,
lua: &'a Lua,
thread: impl IntoLuaThread<'a>,
args: impl IntoLuaMulti<'a>,
) -> LuaResult<SchedulerThreadId> {
let thread = thread.into_lua_thread(lua)?;
let args = args.into_lua_multi(lua)?;
let thread = SchedulerThread::new(lua, thread, args);
let thread_id = thread.id();
self.threads
.try_borrow_mut()
.into_lua_err()
.context("Failed to borrow threads vec")?
.push_back(thread);
// NOTE: We might be resuming the same thread several times and
// pushing it to the scheduler several times before it is done,
// and we should only ever create one result sender per thread
self.thread_senders
.borrow_mut()
.entry(thread_id)
.or_insert_with(|| SchedulerThreadSender::new(1));
// NOTE: We might be resuming futures, need to signal that a
// new lua thread is ready to break out of futures resumption
self.state.message_sender().send_pushed_lua_thread();
Ok(thread_id)
}
/**
Waits for the given thread to finish running, and returns its result.
*/
pub async fn wait_for_thread<'a>(
&self,
lua: &'a Lua,
thread_id: SchedulerThreadId,
) -> LuaResult<LuaMultiValue<'a>> {
let mut recv = {
let senders = self.thread_senders.borrow();
let sender = senders
.get(&thread_id)
.expect("Tried to wait for thread that is not queued");
sender.subscribe()
};
let res = match recv.recv().await {
Err(_) => panic!("Sender was dropped while waiting for {thread_id:?}"),
Ok(r) => r,
};
match res {
Err(e) => Err(e),
Ok(k) => {
let vals = lua
.registry_value::<Vec<LuaValue>>(&k)
.expect("Received invalid registry key for thread");
// NOTE: This is not strictly necessary, mlua can clean
// up registry values on its own, but doing this will add
// some extra safety and clean up registry values faster
if let Some(key) = Arc::into_inner(k) {
lua.remove_registry_value(key)
.expect("Failed to remove registry key for thread");
}
Ok(LuaMultiValue::from_vec(vals))
}
}
}
}

View file

@ -0,0 +1,98 @@
use std::sync::{MutexGuard, TryLockError};
use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
use super::state::SchedulerState;
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub(crate) enum SchedulerMessage {
ExitCodeSet,
PushedLuaThread,
SpawnedLuaFuture,
SpawnedBackgroundFuture,
}
impl SchedulerMessage {
pub fn should_break_futures(self) -> bool {
matches!(self, Self::ExitCodeSet | Self::PushedLuaThread)
}
pub fn should_break_lua_futures(self) -> bool {
self.should_break_futures() || matches!(self, Self::SpawnedBackgroundFuture)
}
pub fn should_break_background_futures(self) -> bool {
self.should_break_futures() || matches!(self, Self::SpawnedLuaFuture)
}
}
/**
A message sender for the scheduler.
As long as this sender is not dropped, the scheduler
will be kept alive, waiting for more messages to arrive.
*/
pub(crate) struct SchedulerMessageSender(UnboundedSender<SchedulerMessage>);
impl SchedulerMessageSender {
/**
Creates a new message sender for the scheduler.
*/
pub fn new(state: &SchedulerState) -> Self {
Self(
state
.message_sender
.lock()
.expect("Scheduler state was poisoned")
.clone(),
)
}
pub fn send_exit_code_set(&self) {
self.0.send(SchedulerMessage::ExitCodeSet).ok();
}
pub fn send_pushed_lua_thread(&self) {
self.0.send(SchedulerMessage::PushedLuaThread).ok();
}
pub fn send_spawned_lua_future(&self) {
self.0.send(SchedulerMessage::SpawnedLuaFuture).ok();
}
pub fn send_spawned_background_future(&self) {
self.0.send(SchedulerMessage::SpawnedBackgroundFuture).ok();
}
}
/**
A message receiver for the scheduler.
Only one message receiver may exist per scheduler.
*/
pub(crate) struct SchedulerMessageReceiver<'a>(MutexGuard<'a, UnboundedReceiver<SchedulerMessage>>);
impl<'a> SchedulerMessageReceiver<'a> {
/**
Creates a new message receiver for the scheduler.
Panics if the message receiver is already being used.
*/
pub fn new(state: &'a SchedulerState) -> Self {
Self(match state.message_receiver.try_lock() {
Err(TryLockError::Poisoned(_)) => panic!("Sheduler state was poisoned"),
Err(TryLockError::WouldBlock) => {
panic!("Message receiver may only be borrowed once at a time")
}
Ok(guard) => guard,
})
}
// NOTE: Holding this lock across await points is fine, since we
// can only ever create lock exactly one SchedulerMessageReceiver
// See above constructor for details on this
#[allow(clippy::await_holding_lock)]
pub async fn recv(&mut self) -> Option<SchedulerMessage> {
self.0.recv().await
}
}

116
src/lune/scheduler/mod.rs Normal file
View file

@ -0,0 +1,116 @@
use std::{
cell::RefCell,
collections::{HashMap, VecDeque},
pin::Pin,
sync::Arc,
};
use futures_util::{stream::FuturesUnordered, Future};
use mlua::prelude::*;
use tokio::sync::Mutex as AsyncMutex;
mod message;
mod state;
mod thread;
mod traits;
mod impl_async;
mod impl_runner;
mod impl_threads;
pub use self::thread::SchedulerThreadId;
pub use self::traits::*;
use self::{
state::SchedulerState,
thread::{SchedulerThread, SchedulerThreadSender},
};
type SchedulerFuture<'fut> = Pin<Box<dyn Future<Output = ()> + 'fut>>;
/**
Scheduler for Lua threads and futures.
This scheduler can be cheaply cloned and the underlying state
and data will remain unchanged and accessible from all clones.
*/
#[derive(Debug, Clone)]
pub(crate) struct Scheduler<'fut> {
state: Arc<SchedulerState>,
threads: Arc<RefCell<VecDeque<SchedulerThread>>>,
thread_senders: Arc<RefCell<HashMap<SchedulerThreadId, SchedulerThreadSender>>>,
/*
FUTURE: Get rid of these, let the tokio runtime handle running
and resumption of futures completely, just use our scheduler
state and receiver to know when we have run to completion.
If we have no senders left, we have run to completion.
Once we no longer store futures in our scheduler, we can
get rid of the lifetime on it, store it in our lua app
data as a Weak<Scheduler>, together with a Weak<Lua>.
In our lua async functions we can then get a reference to this,
upgrade it to an Arc<Scheduler> and Arc<Lua> to extend lifetimes,
and hopefully get rid of Box::leak and 'static lifetimes for good.
Relevant comment on the mlua repository:
https://github.com/khvzak/mlua/issues/169#issuecomment-1138863979
*/
futures_lua: Arc<AsyncMutex<FuturesUnordered<SchedulerFuture<'fut>>>>,
futures_background: Arc<AsyncMutex<FuturesUnordered<SchedulerFuture<'static>>>>,
}
impl<'fut> Scheduler<'fut> {
/**
Creates a new scheduler.
*/
pub fn new() -> Self {
Self {
state: Arc::new(SchedulerState::new()),
threads: Arc::new(RefCell::new(VecDeque::new())),
thread_senders: Arc::new(RefCell::new(HashMap::new())),
futures_lua: Arc::new(AsyncMutex::new(FuturesUnordered::new())),
futures_background: Arc::new(AsyncMutex::new(FuturesUnordered::new())),
}
}
/**
Sets the luau interrupt for this scheduler.
This will propagate errors from any lua-spawned
futures back to the lua threads that spawned them.
*/
pub fn set_interrupt_for(&self, lua: &Lua) {
// Propagate errors given to the scheduler back to their lua threads
// FUTURE: Do profiling and anything else we need inside of this interrupt
let state = self.state.clone();
lua.set_interrupt(move |_| {
if let Some(id) = state.get_current_thread_id() {
if let Some(err) = state.get_thread_error(id) {
return Err(err);
}
}
Ok(LuaVmState::Continue)
});
}
/**
Sets the exit code for the scheduler.
This will stop the scheduler from resuming any more lua threads or futures.
Panics if the exit code is set more than once.
*/
pub fn set_exit_code(&self, code: impl Into<u8>) {
assert!(
self.state.exit_code().is_none(),
"Exit code may only be set exactly once"
);
self.state.set_exit_code(code.into());
}
#[doc(hidden)]
pub fn into_static(self) -> &'static Self {
Box::leak(Box::new(self))
}
}

176
src/lune/scheduler/state.rs Normal file
View file

@ -0,0 +1,176 @@
use std::{
collections::HashMap,
sync::{
atomic::{AtomicBool, AtomicU8, AtomicUsize, Ordering},
Arc, Mutex,
},
};
use mlua::Error as LuaError;
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
use super::{
message::{SchedulerMessage, SchedulerMessageReceiver, SchedulerMessageSender},
SchedulerThreadId,
};
/**
Internal state for a [`Scheduler`].
This scheduler state uses atomic operations for everything
except lua error storage, and is completely thread safe.
*/
#[derive(Debug)]
pub(crate) struct SchedulerState {
exit_state: AtomicBool,
exit_code: AtomicU8,
num_resumptions: AtomicUsize,
num_errors: AtomicUsize,
thread_id: Arc<Mutex<Option<SchedulerThreadId>>>,
thread_errors: Arc<Mutex<HashMap<SchedulerThreadId, LuaError>>>,
pub(super) message_sender: Arc<Mutex<UnboundedSender<SchedulerMessage>>>,
pub(super) message_receiver: Arc<Mutex<UnboundedReceiver<SchedulerMessage>>>,
}
impl SchedulerState {
/**
Creates a new scheduler state.
*/
pub fn new() -> Self {
let (message_sender, message_receiver) = unbounded_channel();
Self {
exit_state: AtomicBool::new(false),
exit_code: AtomicU8::new(0),
num_resumptions: AtomicUsize::new(0),
num_errors: AtomicUsize::new(0),
thread_id: Arc::new(Mutex::new(None)),
thread_errors: Arc::new(Mutex::new(HashMap::new())),
message_sender: Arc::new(Mutex::new(message_sender)),
message_receiver: Arc::new(Mutex::new(message_receiver)),
}
}
/**
Increments the total lua error count for the scheduler.
This is used to determine if the scheduler should exit with
a non-zero exit code, when no exit code is explicitly set.
*/
pub fn increment_error_count(&self) {
self.num_errors.fetch_add(1, Ordering::Relaxed);
}
/**
Checks if there have been any lua errors.
This is used to determine if the scheduler should exit with
a non-zero exit code, when no exit code is explicitly set.
*/
pub fn has_errored(&self) -> bool {
self.num_errors.load(Ordering::SeqCst) > 0
}
/**
Gets the currently set exit code for the scheduler, if any.
*/
pub fn exit_code(&self) -> Option<u8> {
if self.exit_state.load(Ordering::SeqCst) {
Some(self.exit_code.load(Ordering::SeqCst))
} else {
None
}
}
/**
Checks if the scheduler has an explicit exit code set.
*/
pub fn has_exit_code(&self) -> bool {
self.exit_state.load(Ordering::SeqCst)
}
/**
Sets the explicit exit code for the scheduler.
*/
pub fn set_exit_code(&self, code: impl Into<u8>) {
self.exit_state.store(true, Ordering::SeqCst);
self.exit_code.store(code.into(), Ordering::SeqCst);
self.message_sender().send_exit_code_set();
}
/**
Gets the currently running lua scheduler thread id, if any.
*/
pub fn get_current_thread_id(&self) -> Option<SchedulerThreadId> {
*self
.thread_id
.lock()
.expect("Failed to lock current thread id")
}
/**
Sets the currently running lua scheduler thread id.
This must be set to `Some(id)` just before resuming a lua thread,
and `None` while no lua thread is being resumed. If set to `Some`
while the current thread id is also `Some`, this will panic.
Must only be set once per thread id, although this
is not checked at runtime for performance reasons.
*/
pub fn set_current_thread_id(&self, id: Option<SchedulerThreadId>) {
self.num_resumptions.fetch_add(1, Ordering::Relaxed);
let mut thread_id = self
.thread_id
.lock()
.expect("Failed to lock current thread id");
assert!(
id.is_none() || thread_id.is_none(),
"Current thread id can not be overwritten"
);
*thread_id = id;
}
/**
Gets the [`LuaError`] (if any) for the given `id`.
Note that this removes the error from the scheduler state completely.
*/
pub fn get_thread_error(&self, id: SchedulerThreadId) -> Option<LuaError> {
let mut thread_errors = self
.thread_errors
.lock()
.expect("Failed to lock thread errors");
thread_errors.remove(&id)
}
/**
Sets a [`LuaError`] for the given `id`.
Note that this will replace any already existing [`LuaError`].
*/
pub fn set_thread_error(&self, id: SchedulerThreadId, err: LuaError) {
let mut thread_errors = self
.thread_errors
.lock()
.expect("Failed to lock thread errors");
thread_errors.insert(id, err);
}
/**
Creates a new message sender for the scheduler.
*/
pub fn message_sender(&self) -> SchedulerMessageSender {
SchedulerMessageSender::new(self)
}
/**
Tries to borrow the message receiver for the scheduler.
Panics if the message receiver is already being used.
*/
pub fn message_receiver(&self) -> SchedulerMessageReceiver {
SchedulerMessageReceiver::new(self)
}
}

View file

@ -0,0 +1,105 @@
use std::sync::Arc;
use mlua::prelude::*;
use tokio::sync::broadcast::Sender;
/**
Type alias for a broadcast [`Sender`], which will
broadcast the result and return values of a lua thread.
The return values are stored in the lua registry as a
`Vec<LuaValue<'_>>`, and the registry key pointing to
those values will be sent using the broadcast sender.
*/
pub type SchedulerThreadSender = Sender<LuaResult<Arc<LuaRegistryKey>>>;
/**
Unique, randomly generated id for a scheduler thread.
*/
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
pub struct SchedulerThreadId(usize);
impl From<&LuaThread<'_>> for SchedulerThreadId {
fn from(value: &LuaThread) -> Self {
// HACK: We rely on the debug format of mlua
// thread refs here, but currently this is the
// only way to get a proper unique id using mlua
let addr_string = format!("{value:?}");
let addr = addr_string
.strip_prefix("Thread(Ref(0x")
.expect("Invalid thread address format - unknown prefix")
.split_once(')')
.map(|(s, _)| s)
.expect("Invalid thread address format - missing ')'");
let id = usize::from_str_radix(addr, 16)
.expect("Failed to parse thread address as hexadecimal into usize");
Self(id)
}
}
/**
Container for registry keys that point to a thread and thread arguments.
*/
#[derive(Debug)]
pub(super) struct SchedulerThread {
thread_id: SchedulerThreadId,
key_thread: LuaRegistryKey,
key_args: LuaRegistryKey,
}
impl SchedulerThread {
/**
Creates a new scheduler thread container from the given thread and arguments.
May fail if an allocation error occurs, is not fallible otherwise.
*/
pub(super) fn new<'lua>(
lua: &'lua Lua,
thread: LuaThread<'lua>,
args: LuaMultiValue<'lua>,
) -> Self {
let args_vec = args.into_vec();
let thread_id = SchedulerThreadId::from(&thread);
let key_thread = lua
.create_registry_value(thread)
.expect("Failed to store thread in registry - out of memory");
let key_args = lua
.create_registry_value(args_vec)
.expect("Failed to store thread args in registry - out of memory");
Self {
thread_id,
key_thread,
key_args,
}
}
/**
Extracts the inner thread and args from the container.
*/
pub(super) fn into_inner(self, lua: &Lua) -> (LuaThread<'_>, LuaMultiValue<'_>) {
let thread = lua
.registry_value(&self.key_thread)
.expect("Failed to get thread from registry");
let args_vec = lua
.registry_value(&self.key_args)
.expect("Failed to get thread args from registry");
let args = LuaMultiValue::from_vec(args_vec);
lua.remove_registry_value(self.key_thread)
.expect("Failed to remove thread from registry");
lua.remove_registry_value(self.key_args)
.expect("Failed to remove thread args from registry");
(thread, args)
}
/**
Retrieves the unique, randomly generated id for this scheduler thread.
*/
pub(super) fn id(&self) -> SchedulerThreadId {
self.thread_id
}
}

View file

@ -0,0 +1,118 @@
use futures_util::Future;
use mlua::prelude::*;
use super::Scheduler;
const ASYNC_IMPL_LUA: &str = r#"
schedule(...)
return yield()
"#;
/**
Trait for extensions to the [`Lua`] struct, allowing
for access to the scheduler without having to import
it or handle registry / app data references manually.
*/
pub(crate) trait LuaSchedulerExt<'lua> {
/**
Sets the scheduler for the [`Lua`] struct.
*/
fn set_scheduler(&'lua self, scheduler: &'lua Scheduler);
/**
Creates a function callable from Lua that runs an async
closure and returns the results of it to the call site.
*/
fn create_async_function<A, R, F, FR>(&'lua self, func: F) -> LuaResult<LuaFunction<'lua>>
where
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
F: Fn(&'lua Lua, A) -> FR + 'lua,
FR: Future<Output = LuaResult<R>> + 'lua;
}
// FIXME: `self` escapes outside of method because we are borrowing `func`
// when we call `schedule_future_thread` in the lua function body below
// For now we solve this by using the 'static lifetime bound in the impl
impl<'lua> LuaSchedulerExt<'lua> for Lua
where
'lua: 'static,
{
fn set_scheduler(&'lua self, scheduler: &'lua Scheduler) {
self.set_app_data(scheduler);
scheduler.set_interrupt_for(self);
}
fn create_async_function<A, R, F, FR>(&'lua self, func: F) -> LuaResult<LuaFunction<'lua>>
where
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
F: Fn(&'lua Lua, A) -> FR + 'lua,
FR: Future<Output = LuaResult<R>> + 'lua,
{
self.app_data_ref::<&Scheduler>()
.expect("Lua must have a scheduler to create async functions");
let async_env = self.create_table_with_capacity(0, 2)?;
async_env.set(
"yield",
self.globals()
.get::<_, LuaTable>("coroutine")?
.get::<_, LuaFunction>("yield")?,
)?;
async_env.set(
"schedule",
LuaFunction::wrap(move |lua: &Lua, args: A| {
let thread = lua.current_thread();
let future = func(lua, args);
let sched = lua
.app_data_ref::<&Scheduler>()
.expect("Lua struct is missing scheduler");
sched.spawn_thread(lua, thread, future)?;
Ok(())
}),
)?;
let async_func = self
.load(ASYNC_IMPL_LUA)
.set_name("async")
.set_environment(async_env)
.into_function()?;
Ok(async_func)
}
}
/**
Trait for any struct that can be turned into an [`LuaThread`]
and given to the scheduler, implemented for the following types:
- Lua threads ([`LuaThread`])
- Lua functions ([`LuaFunction`])
- Lua chunks ([`LuaChunk`])
*/
pub trait IntoLuaThread<'lua> {
/**
Converts the value into a lua thread.
*/
fn into_lua_thread(self, lua: &'lua Lua) -> LuaResult<LuaThread<'lua>>;
}
impl<'lua> IntoLuaThread<'lua> for LuaThread<'lua> {
fn into_lua_thread(self, _: &'lua Lua) -> LuaResult<LuaThread<'lua>> {
Ok(self)
}
}
impl<'lua> IntoLuaThread<'lua> for LuaFunction<'lua> {
fn into_lua_thread(self, lua: &'lua Lua) -> LuaResult<LuaThread<'lua>> {
lua.create_thread(self)
}
}
impl<'lua, 'a> IntoLuaThread<'lua> for LuaChunk<'lua, 'a> {
fn into_lua_thread(self, lua: &'lua Lua) -> LuaResult<LuaThread<'lua>> {
lua.create_thread(self.into_function()?)
}
}

View file

@ -4,8 +4,6 @@ use console::{colors_enabled, set_colors_enabled, style, Style};
use mlua::prelude::*; use mlua::prelude::*;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use crate::lune::lua::task::TaskReference;
const MAX_FORMAT_DEPTH: usize = 4; const MAX_FORMAT_DEPTH: usize = 4;
const INDENT: &str = " "; const INDENT: &str = " ";
@ -174,12 +172,7 @@ pub fn pretty_format_value(
LuaValue::Thread(_) => write!(buffer, "{}", COLOR_PURPLE.apply_to("<thread>"))?, LuaValue::Thread(_) => write!(buffer, "{}", COLOR_PURPLE.apply_to("<thread>"))?,
LuaValue::Function(_) => write!(buffer, "{}", COLOR_PURPLE.apply_to("<function>"))?, LuaValue::Function(_) => write!(buffer, "{}", COLOR_PURPLE.apply_to("<function>"))?,
LuaValue::UserData(u) => { LuaValue::UserData(u) => {
if u.is::<TaskReference>() { if let Some(s) = call_userdata_tostring_metamethod(u) {
// Task references must be transparent
// to lua and pretend to be normal lua
// threads for compatibility purposes
write!(buffer, "{}", COLOR_PURPLE.apply_to("<thread>"))?
} else if let Some(s) = call_userdata_tostring_metamethod(u) {
write!(buffer, "{s}")? write!(buffer, "{s}")?
} else { } else {
write!(buffer, "{}", COLOR_PURPLE.apply_to("<userdata>"))? write!(buffer, "{}", COLOR_PURPLE.apply_to("<userdata>"))?

6
src/lune/util/mod.rs Normal file
View file

@ -0,0 +1,6 @@
mod table_builder;
pub mod formatting;
pub mod traits;
pub use table_builder::TableBuilder;

View file

@ -1,25 +1,26 @@
#![allow(dead_code)]
use std::future::Future; use std::future::Future;
use mlua::prelude::*; use mlua::prelude::*;
use crate::lune::lua::async_ext::LuaAsyncExt; use crate::lune::scheduler::LuaSchedulerExt;
pub struct TableBuilder { pub struct TableBuilder<'lua> {
lua: &'static Lua, lua: &'lua Lua,
tab: LuaTable<'static>, tab: LuaTable<'lua>,
} }
#[allow(dead_code)] impl<'lua> TableBuilder<'lua> {
impl TableBuilder { pub fn new(lua: &'lua Lua) -> LuaResult<Self> {
pub fn new(lua: &'static Lua) -> LuaResult<Self> {
let tab = lua.create_table()?; let tab = lua.create_table()?;
Ok(Self { lua, tab }) Ok(Self { lua, tab })
} }
pub fn with_value<K, V>(self, key: K, value: V) -> LuaResult<Self> pub fn with_value<K, V>(self, key: K, value: V) -> LuaResult<Self>
where where
K: IntoLua<'static>, K: IntoLua<'lua>,
V: IntoLua<'static>, V: IntoLua<'lua>,
{ {
self.tab.raw_set(key, value)?; self.tab.raw_set(key, value)?;
Ok(self) Ok(self)
@ -27,8 +28,8 @@ impl TableBuilder {
pub fn with_values<K, V>(self, values: Vec<(K, V)>) -> LuaResult<Self> pub fn with_values<K, V>(self, values: Vec<(K, V)>) -> LuaResult<Self>
where where
K: IntoLua<'static>, K: IntoLua<'lua>,
V: IntoLua<'static>, V: IntoLua<'lua>,
{ {
for (key, value) in values { for (key, value) in values {
self.tab.raw_set(key, value)?; self.tab.raw_set(key, value)?;
@ -38,7 +39,7 @@ impl TableBuilder {
pub fn with_sequential_value<V>(self, value: V) -> LuaResult<Self> pub fn with_sequential_value<V>(self, value: V) -> LuaResult<Self>
where where
V: IntoLua<'static>, V: IntoLua<'lua>,
{ {
self.tab.raw_push(value)?; self.tab.raw_push(value)?;
Ok(self) Ok(self)
@ -46,7 +47,7 @@ impl TableBuilder {
pub fn with_sequential_values<V>(self, values: Vec<V>) -> LuaResult<Self> pub fn with_sequential_values<V>(self, values: Vec<V>) -> LuaResult<Self>
where where
V: IntoLua<'static>, V: IntoLua<'lua>,
{ {
for value in values { for value in values {
self.tab.raw_push(value)?; self.tab.raw_push(value)?;
@ -54,40 +55,47 @@ impl TableBuilder {
Ok(self) Ok(self)
} }
pub fn with_metatable(self, table: LuaTable) -> LuaResult<Self> {
self.tab.set_metatable(Some(table));
Ok(self)
}
pub fn with_function<K, A, R, F>(self, key: K, func: F) -> LuaResult<Self> pub fn with_function<K, A, R, F>(self, key: K, func: F) -> LuaResult<Self>
where where
K: IntoLua<'static>, K: IntoLua<'lua>,
A: FromLuaMulti<'static>, A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'static>, R: IntoLuaMulti<'lua>,
F: 'static + Fn(&'static Lua, A) -> LuaResult<R>, F: Fn(&'lua Lua, A) -> LuaResult<R> + 'static,
{ {
let f = self.lua.create_function(func)?; let f = self.lua.create_function(func)?;
self.with_value(key, LuaValue::Function(f)) self.with_value(key, LuaValue::Function(f))
} }
pub fn with_async_function<K, A, R, F, FR>(self, key: K, func: F) -> LuaResult<Self> pub fn with_metatable(self, table: LuaTable) -> LuaResult<Self> {
where self.tab.set_metatable(Some(table));
K: IntoLua<'static>, Ok(self)
A: FromLuaMulti<'static>,
R: IntoLuaMulti<'static>,
F: 'static + Fn(&'static Lua, A) -> FR,
FR: 'static + Future<Output = LuaResult<R>>,
{
let f = self.lua.create_async_function(func)?;
self.with_value(key, LuaValue::Function(f))
} }
pub fn build_readonly(self) -> LuaResult<LuaTable<'static>> { pub fn build_readonly(self) -> LuaResult<LuaTable<'lua>> {
self.tab.set_readonly(true); self.tab.set_readonly(true);
Ok(self.tab) Ok(self.tab)
} }
pub fn build(self) -> LuaResult<LuaTable<'static>> { pub fn build(self) -> LuaResult<LuaTable<'lua>> {
Ok(self.tab) Ok(self.tab)
} }
} }
// FIXME: Remove static lifetime bound here when `create_async_function`
// no longer needs it to compile, then move this into the above impl
impl<'lua> TableBuilder<'lua>
where
'lua: 'static,
{
pub fn with_async_function<K, A, R, F, FR>(self, key: K, func: F) -> LuaResult<Self>
where
K: IntoLua<'lua>,
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
F: Fn(&'lua Lua, A) -> FR + 'lua,
FR: Future<Output = LuaResult<R>> + 'lua,
{
let f = self.lua.create_async_function(func)?;
self.with_value(key, LuaValue::Function(f))
}
}

15
src/lune/util/traits.rs Normal file
View file

@ -0,0 +1,15 @@
use mlua::prelude::*;
use super::formatting::format_label;
use crate::LuneError;
pub trait LuaEmitErrorExt {
fn emit_error(&self, err: LuaError);
}
impl LuaEmitErrorExt for Lua {
fn emit_error(&self, err: LuaError) {
// NOTE: LuneError will pretty-format this error
eprintln!("{}\n{}", format_label("error"), LuneError::from(err));
}
}

View file

@ -19,9 +19,12 @@ use console::style;
#[tokio::main(flavor = "multi_thread")] #[tokio::main(flavor = "multi_thread")]
async fn main() -> ExitCode { async fn main() -> ExitCode {
let logger_env = env_logger::Env::default().default_filter_or("error"); tracing_subscriber::fmt()
env_logger::Builder::from_env(logger_env) .compact()
.format_timestamp(None) .with_env_filter(tracing_subscriber::filter::EnvFilter::from_default_env())
.with_target(true)
.with_timer(tracing_subscriber::fmt::time::uptime())
.with_level(true)
.init(); .init();
match Cli::parse().run().await { match Cli::parse().run().await {
Ok(code) => code, Ok(code) => code,

View file

@ -3,6 +3,8 @@ use core::fmt;
use mlua::prelude::*; use mlua::prelude::*;
use rbx_dom_weak::types::Axes as DomAxes; use rbx_dom_weak::types::Axes as DomAxes;
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
use super::{super::*, EnumItem}; use super::{super::*, EnumItem};
/** /**
@ -17,14 +19,15 @@ pub struct Axes {
pub(crate) z: bool, pub(crate) z: bool,
} }
impl Axes { impl LuaExportsTable<'_> for Axes {
pub(crate) fn make_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> { const EXPORT_NAME: &'static str = "Axes";
datatype_table.set(
"new", fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
lua.create_function(|_, args: LuaMultiValue| { let axes_new = |_, args: LuaMultiValue| {
let mut x = false; let mut x = false;
let mut y = false; let mut y = false;
let mut z = false; let mut z = false;
let mut check = |e: &EnumItem| { let mut check = |e: &EnumItem| {
if e.parent.desc.name == "Axis" { if e.parent.desc.name == "Axis" {
match &e.name { match &e.name {
@ -42,6 +45,7 @@ impl Axes {
} }
} }
}; };
for (index, arg) in args.into_iter().enumerate() { for (index, arg) in args.into_iter().enumerate() {
if let LuaValue::UserData(u) = arg { if let LuaValue::UserData(u) = arg {
if let Ok(e) = u.borrow::<EnumItem>() { if let Ok(e) = u.borrow::<EnumItem>() {
@ -60,10 +64,13 @@ impl Axes {
))); )));
} }
} }
Ok(Axes { x, y, z }) Ok(Axes { x, y, z })
})?, };
)?;
Ok(()) TableBuilder::new(lua)?
.with_function("new", axes_new)?
.build_readonly()
} }
} }

View file

@ -4,6 +4,8 @@ use mlua::prelude::*;
use rand::seq::SliceRandom; use rand::seq::SliceRandom;
use rbx_dom_weak::types::BrickColor as DomBrickColor; use rbx_dom_weak::types::BrickColor as DomBrickColor;
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
use super::{super::*, Color3}; use super::{super::*, Color3};
/** /**
@ -20,15 +22,16 @@ pub struct BrickColor {
pub(crate) rgb: (u8, u8, u8), pub(crate) rgb: (u8, u8, u8),
} }
impl BrickColor { impl LuaExportsTable<'_> for BrickColor {
pub(crate) fn make_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> { const EXPORT_NAME: &'static str = "BrickColor";
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
type ArgsNumber = u16; type ArgsNumber = u16;
type ArgsName = String; type ArgsName = String;
type ArgsRgb = (u8, u8, u8); type ArgsRgb = (u8, u8, u8);
type ArgsColor3<'lua> = LuaUserDataRef<'lua, Color3>; type ArgsColor3<'lua> = LuaUserDataRef<'lua, Color3>;
datatype_table.set(
"new", let brick_color_new = |lua, args: LuaMultiValue| {
lua.create_function(|lua, args: LuaMultiValue| {
if let Ok(number) = ArgsNumber::from_lua_multi(args.clone(), lua) { if let Ok(number) = ArgsNumber::from_lua_multi(args.clone(), lua) {
Ok(color_from_number(number)) Ok(color_from_number(number))
} else if let Ok(name) = ArgsName::from_lua_multi(args.clone(), lua) { } else if let Ok(name) = ArgsName::from_lua_multi(args.clone(), lua) {
@ -43,11 +46,9 @@ impl BrickColor {
"Invalid arguments to constructor".to_string(), "Invalid arguments to constructor".to_string(),
)) ))
} }
})?, };
)?;
datatype_table.set( let brick_color_palette = |_, index: u16| {
"palette",
lua.create_function(|_, index: u16| {
if index == 0 { if index == 0 {
Err(LuaError::RuntimeError("Invalid index".to_string())) Err(LuaError::RuntimeError("Invalid index".to_string()))
} else if let Some(number) = BRICK_COLOR_PALETTE.get((index - 1) as usize) { } else if let Some(number) = BRICK_COLOR_PALETTE.get((index - 1) as usize) {
@ -55,22 +56,24 @@ impl BrickColor {
} else { } else {
Err(LuaError::RuntimeError("Invalid index".to_string())) Err(LuaError::RuntimeError("Invalid index".to_string()))
} }
})?, };
)?;
datatype_table.set( let brick_color_random = |_, ()| {
"random",
lua.create_function(|_, ()| {
let number = BRICK_COLOR_PALETTE.choose(&mut rand::thread_rng()); let number = BRICK_COLOR_PALETTE.choose(&mut rand::thread_rng());
Ok(color_from_number(*number.unwrap())) Ok(color_from_number(*number.unwrap()))
})?, };
)?;
let mut builder = TableBuilder::new(lua)?
.with_function("new", brick_color_new)?
.with_function("palette", brick_color_palette)?
.with_function("random", brick_color_random)?;
for (name, number) in BRICK_COLOR_CONSTRUCTORS { for (name, number) in BRICK_COLOR_CONSTRUCTORS {
datatype_table.set( let f = |_, ()| Ok(color_from_number(*number));
*name, builder = builder.with_function(*name, f)?;
lua.create_function(|_, ()| Ok(color_from_number(*number)))?,
)?;
} }
Ok(())
builder.build_readonly()
} }
} }

View file

@ -2,9 +2,11 @@ use core::fmt;
use std::ops; use std::ops;
use glam::{EulerRot, Mat4, Quat, Vec3}; use glam::{EulerRot, Mat4, Quat, Vec3};
use mlua::{prelude::*, Variadic}; use mlua::prelude::*;
use rbx_dom_weak::types::{CFrame as DomCFrame, Matrix3 as DomMatrix3, Vector3 as DomVector3}; use rbx_dom_weak::types::{CFrame as DomCFrame, Matrix3 as DomMatrix3, Vector3 as DomVector3};
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
use super::{super::*, Vector3}; use super::{super::*, Vector3};
/** /**
@ -35,62 +37,28 @@ impl CFrame {
fn inverse(&self) -> Self { fn inverse(&self) -> Self {
Self(self.0.inverse()) Self(self.0.inverse())
} }
}
pub(crate) fn make_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> { impl LuaExportsTable<'_> for CFrame {
// Constants const EXPORT_NAME: &'static str = "CFrame";
datatype_table.set("identity", CFrame(Mat4::IDENTITY))?;
// Strict args constructors fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
datatype_table.set( let cframe_angles = |_, (rx, ry, rz): (f32, f32, f32)| {
"lookAt",
lua.create_function(
|_,
(from, to, up): (
LuaUserDataRef<Vector3>,
LuaUserDataRef<Vector3>,
Option<LuaUserDataRef<Vector3>>,
)| {
Ok(CFrame(look_at(
from.0,
to.0,
up.as_deref().unwrap_or(&Vector3(Vec3::Y)).0,
)))
},
)?,
)?;
datatype_table.set(
"fromEulerAnglesXYZ",
lua.create_function(|_, (rx, ry, rz): (f32, f32, f32)| {
Ok(CFrame(Mat4::from_euler(EulerRot::XYZ, rx, ry, rz))) Ok(CFrame(Mat4::from_euler(EulerRot::XYZ, rx, ry, rz)))
})?, };
)?;
datatype_table.set( let cframe_from_axis_angle =
"fromEulerAnglesYXZ", |_, (v, r): (LuaUserDataRef<Vector3>, f32)| Ok(CFrame(Mat4::from_axis_angle(v.0, r)));
lua.create_function(|_, (rx, ry, rz): (f32, f32, f32)| {
Ok(CFrame(Mat4::from_euler(EulerRot::YXZ, ry, rx, rz))) let cframe_from_euler_angles_xyz = |_, (rx, ry, rz): (f32, f32, f32)| {
})?,
)?;
datatype_table.set(
"Angles",
lua.create_function(|_, (rx, ry, rz): (f32, f32, f32)| {
Ok(CFrame(Mat4::from_euler(EulerRot::XYZ, rx, ry, rz))) Ok(CFrame(Mat4::from_euler(EulerRot::XYZ, rx, ry, rz)))
})?, };
)?;
datatype_table.set( let cframe_from_euler_angles_yxz = |_, (rx, ry, rz): (f32, f32, f32)| {
"fromOrientation",
lua.create_function(|_, (rx, ry, rz): (f32, f32, f32)| {
Ok(CFrame(Mat4::from_euler(EulerRot::YXZ, ry, rx, rz))) Ok(CFrame(Mat4::from_euler(EulerRot::YXZ, ry, rx, rz)))
})?, };
)?;
datatype_table.set( let cframe_from_matrix = |_,
"fromAxisAngle",
lua.create_function(|_, (v, r): (LuaUserDataRef<Vector3>, f32)| {
Ok(CFrame(Mat4::from_axis_angle(v.0, r)))
})?,
)?;
datatype_table.set(
"fromMatrix",
lua.create_function(
|_,
(pos, rx, ry, rz): ( (pos, rx, ry, rz): (
LuaUserDataRef<Vector3>, LuaUserDataRef<Vector3>,
LuaUserDataRef<Vector3>, LuaUserDataRef<Vector3>,
@ -105,9 +73,25 @@ impl CFrame {
.extend(0.0), .extend(0.0),
pos.0.extend(1.0), pos.0.extend(1.0),
))) )))
}, };
)?,
)?; let cframe_from_orientation = |_, (rx, ry, rz): (f32, f32, f32)| {
Ok(CFrame(Mat4::from_euler(EulerRot::YXZ, ry, rx, rz)))
};
let cframe_look_at = |_,
(from, to, up): (
LuaUserDataRef<Vector3>,
LuaUserDataRef<Vector3>,
Option<LuaUserDataRef<Vector3>>,
)| {
Ok(CFrame(look_at(
from.0,
to.0,
up.as_deref().unwrap_or(&Vector3(Vec3::Y)).0,
)))
};
// Dynamic args constructor // Dynamic args constructor
type ArgsPos<'lua> = LuaUserDataRef<'lua, Vector3>; type ArgsPos<'lua> = LuaUserDataRef<'lua, Vector3>;
type ArgsLook<'lua> = ( type ArgsLook<'lua> = (
@ -115,12 +99,12 @@ impl CFrame {
LuaUserDataRef<'lua, Vector3>, LuaUserDataRef<'lua, Vector3>,
Option<LuaUserDataRef<'lua, Vector3>>, Option<LuaUserDataRef<'lua, Vector3>>,
); );
type ArgsPosXYZ = (f32, f32, f32); type ArgsPosXYZ = (f32, f32, f32);
type ArgsPosXYZQuat = (f32, f32, f32, f32, f32, f32, f32); type ArgsPosXYZQuat = (f32, f32, f32, f32, f32, f32, f32);
type ArgsMatrix = (f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32); type ArgsMatrix = (f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32);
datatype_table.set(
"new", let cframe_new = |lua, args: LuaMultiValue| {
lua.create_function(|lua, args: LuaMultiValue| {
if args.clone().into_vec().is_empty() { if args.clone().into_vec().is_empty() {
Ok(CFrame(Mat4::IDENTITY)) Ok(CFrame(Mat4::IDENTITY))
} else if let Ok(pos) = ArgsPos::from_lua_multi(args.clone(), lua) { } else if let Ok(pos) = ArgsPos::from_lua_multi(args.clone(), lua) {
@ -155,8 +139,19 @@ impl CFrame {
"Invalid arguments to constructor".to_string(), "Invalid arguments to constructor".to_string(),
)) ))
} }
})?, };
)
TableBuilder::new(lua)?
.with_function("Angles", cframe_angles)?
.with_value("identity", CFrame(Mat4::IDENTITY))?
.with_function("fromAxisAngle", cframe_from_axis_angle)?
.with_function("fromEulerAnglesXYZ", cframe_from_euler_angles_xyz)?
.with_function("fromEulerAnglesYXZ", cframe_from_euler_angles_yxz)?
.with_function("fromMatrix", cframe_from_matrix)?
.with_function("fromOrientation", cframe_from_orientation)?
.with_function("lookAt", cframe_look_at)?
.with_function("new", cframe_new)?
.build_readonly()
} }
} }
@ -210,46 +205,38 @@ impl LuaUserData for CFrame {
translation, translation,
))) )))
}); });
methods.add_method( methods.add_method("ToWorldSpace", |_, this, rhs: LuaUserDataRef<CFrame>| {
"ToWorldSpace", Ok(*this * *rhs)
|_, this, rhs: Variadic<LuaUserDataRef<CFrame>>| { });
Ok(Variadic::from_iter(rhs.into_iter().map(|cf| *this * *cf))) methods.add_method("ToObjectSpace", |_, this, rhs: LuaUserDataRef<CFrame>| {
},
);
methods.add_method(
"ToObjectSpace",
|_, this, rhs: Variadic<LuaUserDataRef<CFrame>>| {
let inverse = this.inverse(); let inverse = this.inverse();
Ok(Variadic::from_iter(rhs.into_iter().map(|cf| inverse * *cf))) Ok(inverse * *rhs)
}, });
);
methods.add_method( methods.add_method(
"PointToWorldSpace", "PointToWorldSpace",
|_, this, rhs: Variadic<LuaUserDataRef<Vector3>>| { |_, this, rhs: LuaUserDataRef<Vector3>| Ok(*this * *rhs),
Ok(Variadic::from_iter(rhs.into_iter().map(|v3| *this * *v3)))
},
); );
methods.add_method( methods.add_method(
"PointToObjectSpace", "PointToObjectSpace",
|_, this, rhs: Variadic<LuaUserDataRef<Vector3>>| { |_, this, rhs: LuaUserDataRef<Vector3>| {
let inverse = this.inverse(); let inverse = this.inverse();
Ok(Variadic::from_iter(rhs.into_iter().map(|v3| inverse * *v3))) Ok(inverse * *rhs)
}, },
); );
methods.add_method( methods.add_method(
"VectorToWorldSpace", "VectorToWorldSpace",
|_, this, rhs: Variadic<LuaUserDataRef<Vector3>>| { |_, this, rhs: LuaUserDataRef<Vector3>| {
let result = *this - Vector3(this.position()); let result = *this - Vector3(this.position());
Ok(Variadic::from_iter(rhs.into_iter().map(|v3| result * *v3))) Ok(result * *rhs)
}, },
); );
methods.add_method( methods.add_method(
"VectorToObjectSpace", "VectorToObjectSpace",
|_, this, rhs: Variadic<LuaUserDataRef<Vector3>>| { |_, this, rhs: LuaUserDataRef<Vector3>| {
let inverse = this.inverse(); let inverse = this.inverse();
let result = inverse - Vector3(inverse.position()); let result = inverse - Vector3(inverse.position());
Ok(Variadic::from_iter(rhs.into_iter().map(|v3| result * *v3))) Ok(result * *rhs)
}, },
); );
#[rustfmt::skip] #[rustfmt::skip]

View file

@ -5,6 +5,8 @@ use glam::Vec3;
use mlua::prelude::*; use mlua::prelude::*;
use rbx_dom_weak::types::{Color3 as DomColor3, Color3uint8 as DomColor3uint8}; use rbx_dom_weak::types::{Color3 as DomColor3, Color3uint8 as DomColor3uint8};
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
use super::super::*; use super::super::*;
/** /**
@ -22,31 +24,19 @@ pub struct Color3 {
pub(crate) b: f32, pub(crate) b: f32,
} }
impl Color3 { impl LuaExportsTable<'_> for Color3 {
pub(crate) fn make_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> { const EXPORT_NAME: &'static str = "Color3";
datatype_table.set(
"new", fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
lua.create_function(|_, (r, g, b): (Option<f32>, Option<f32>, Option<f32>)| { let color3_from_rgb = |_, (r, g, b): (Option<u8>, Option<u8>, Option<u8>)| {
Ok(Color3 {
r: r.unwrap_or_default(),
g: g.unwrap_or_default(),
b: b.unwrap_or_default(),
})
})?,
)?;
datatype_table.set(
"fromRGB",
lua.create_function(|_, (r, g, b): (Option<u8>, Option<u8>, Option<u8>)| {
Ok(Color3 { Ok(Color3 {
r: (r.unwrap_or_default() as f32) / 255f32, r: (r.unwrap_or_default() as f32) / 255f32,
g: (g.unwrap_or_default() as f32) / 255f32, g: (g.unwrap_or_default() as f32) / 255f32,
b: (b.unwrap_or_default() as f32) / 255f32, b: (b.unwrap_or_default() as f32) / 255f32,
}) })
})?, };
)?;
datatype_table.set( let color3_from_hsv = |_, (h, s, v): (f32, f32, f32)| {
"fromHSV",
lua.create_function(|_, (h, s, v): (f32, f32, f32)| {
// https://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c // https://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c
let i = (h * 6.0).floor(); let i = (h * 6.0).floor();
let f = h * 6.0 - i; let f = h * 6.0 - i;
@ -65,11 +55,9 @@ impl Color3 {
}; };
Ok(Color3 { r, g, b }) Ok(Color3 { r, g, b })
})?, };
)?;
datatype_table.set( let color3_from_hex = |_, hex: String| {
"fromHex",
lua.create_function(|_, hex: String| {
let trimmed = hex.trim_start_matches('#').to_ascii_uppercase(); let trimmed = hex.trim_start_matches('#').to_ascii_uppercase();
let chars = if trimmed.len() == 3 { let chars = if trimmed.len() == 3 {
( (
@ -101,9 +89,22 @@ impl Color3 {
trimmed trimmed
))), ))),
} }
})?, };
)?;
Ok(()) let color3_new = |_, (r, g, b): (Option<f32>, Option<f32>, Option<f32>)| {
Ok(Color3 {
r: r.unwrap_or_default(),
g: g.unwrap_or_default(),
b: b.unwrap_or_default(),
})
};
TableBuilder::new(lua)?
.with_function("fromRGB", color3_from_rgb)?
.with_function("fromHSV", color3_from_hsv)?
.with_function("fromHex", color3_from_hex)?
.with_function("new", color3_new)?
.build_readonly()
} }
} }

View file

@ -5,6 +5,8 @@ use rbx_dom_weak::types::{
ColorSequence as DomColorSequence, ColorSequenceKeypoint as DomColorSequenceKeypoint, ColorSequence as DomColorSequence, ColorSequenceKeypoint as DomColorSequenceKeypoint,
}; };
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
use super::{super::*, Color3, ColorSequenceKeypoint}; use super::{super::*, Color3, ColorSequenceKeypoint};
/** /**
@ -17,14 +19,15 @@ pub struct ColorSequence {
pub(crate) keypoints: Vec<ColorSequenceKeypoint>, pub(crate) keypoints: Vec<ColorSequenceKeypoint>,
} }
impl ColorSequence { impl LuaExportsTable<'_> for ColorSequence {
pub(crate) fn make_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> { const EXPORT_NAME: &'static str = "ColorSequence";
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
type ArgsColor<'lua> = LuaUserDataRef<'lua, Color3>; type ArgsColor<'lua> = LuaUserDataRef<'lua, Color3>;
type ArgsColors<'lua> = (LuaUserDataRef<'lua, Color3>, LuaUserDataRef<'lua, Color3>); type ArgsColors<'lua> = (LuaUserDataRef<'lua, Color3>, LuaUserDataRef<'lua, Color3>);
type ArgsKeypoints<'lua> = Vec<LuaUserDataRef<'lua, ColorSequenceKeypoint>>; type ArgsKeypoints<'lua> = Vec<LuaUserDataRef<'lua, ColorSequenceKeypoint>>;
datatype_table.set(
"new", let color_sequence_new = |lua, args: LuaMultiValue| {
lua.create_function(|lua, args: LuaMultiValue| {
if let Ok(color) = ArgsColor::from_lua_multi(args.clone(), lua) { if let Ok(color) = ArgsColor::from_lua_multi(args.clone(), lua) {
Ok(ColorSequence { Ok(ColorSequence {
keypoints: vec![ keypoints: vec![
@ -61,8 +64,11 @@ impl ColorSequence {
"Invalid arguments to constructor".to_string(), "Invalid arguments to constructor".to_string(),
)) ))
} }
})?, };
)
TableBuilder::new(lua)?
.with_function("new", color_sequence_new)?
.build_readonly()
} }
} }

View file

@ -3,6 +3,8 @@ use core::fmt;
use mlua::prelude::*; use mlua::prelude::*;
use rbx_dom_weak::types::ColorSequenceKeypoint as DomColorSequenceKeypoint; use rbx_dom_weak::types::ColorSequenceKeypoint as DomColorSequenceKeypoint;
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
use super::{super::*, Color3}; use super::{super::*, Color3};
/** /**
@ -16,18 +18,20 @@ pub struct ColorSequenceKeypoint {
pub(crate) color: Color3, pub(crate) color: Color3,
} }
impl ColorSequenceKeypoint { impl LuaExportsTable<'_> for ColorSequenceKeypoint {
pub(crate) fn make_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> { const EXPORT_NAME: &'static str = "ColorSequenceKeypoint";
datatype_table.set(
"new", fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
lua.create_function(|_, (time, color): (f32, LuaUserDataRef<Color3>)| { let color_sequence_keypoint_new = |_, (time, color): (f32, LuaUserDataRef<Color3>)| {
Ok(ColorSequenceKeypoint { Ok(ColorSequenceKeypoint {
time, time,
color: *color, color: *color,
}) })
})?, };
)?;
Ok(()) TableBuilder::new(lua)?
.with_function("new", color_sequence_keypoint_new)?
.build_readonly()
} }
} }

View file

@ -3,6 +3,8 @@ use core::fmt;
use mlua::prelude::*; use mlua::prelude::*;
use rbx_dom_weak::types::Faces as DomFaces; use rbx_dom_weak::types::Faces as DomFaces;
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
use super::{super::*, EnumItem}; use super::{super::*, EnumItem};
/** /**
@ -20,17 +22,18 @@ pub struct Faces {
pub(crate) front: bool, pub(crate) front: bool,
} }
impl Faces { impl LuaExportsTable<'_> for Faces {
pub(crate) fn make_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> { const EXPORT_NAME: &'static str = "Faces";
datatype_table.set(
"new", fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
lua.create_function(|_, args: LuaMultiValue| { let faces_new = |_, args: LuaMultiValue| {
let mut right = false; let mut right = false;
let mut top = false; let mut top = false;
let mut back = false; let mut back = false;
let mut left = false; let mut left = false;
let mut bottom = false; let mut bottom = false;
let mut front = false; let mut front = false;
let mut check = |e: &EnumItem| { let mut check = |e: &EnumItem| {
if e.parent.desc.name == "NormalId" { if e.parent.desc.name == "NormalId" {
match &e.name { match &e.name {
@ -44,6 +47,7 @@ impl Faces {
} }
} }
}; };
for (index, arg) in args.into_iter().enumerate() { for (index, arg) in args.into_iter().enumerate() {
if let LuaValue::UserData(u) = arg { if let LuaValue::UserData(u) = arg {
if let Ok(e) = u.borrow::<EnumItem>() { if let Ok(e) = u.borrow::<EnumItem>() {
@ -62,6 +66,7 @@ impl Faces {
))); )));
} }
} }
Ok(Faces { Ok(Faces {
right, right,
top, top,
@ -70,9 +75,11 @@ impl Faces {
bottom, bottom,
front, front,
}) })
})?, };
)?;
Ok(()) TableBuilder::new(lua)?
.with_function("new", faces_new)?
.build_readonly()
} }
} }

View file

@ -6,6 +6,8 @@ use rbx_dom_weak::types::{
Font as DomFont, FontStyle as DomFontStyle, FontWeight as DomFontWeight, Font as DomFont, FontStyle as DomFontStyle, FontWeight as DomFontWeight,
}; };
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
use super::{super::*, EnumItem}; use super::{super::*, EnumItem};
/** /**
@ -34,24 +36,13 @@ impl Font {
cached_id: None, cached_id: None,
}) })
} }
}
pub(crate) fn make_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> { impl LuaExportsTable<'_> for Font {
datatype_table.set( const EXPORT_NAME: &'static str = "Font";
"new",
lua.create_function( fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
|_, (family, weight, style): (String, Option<FontWeight>, Option<FontStyle>)| { let font_from_enum = |_, value: LuaUserDataRef<EnumItem>| {
Ok(Font {
family,
weight: weight.unwrap_or_default(),
style: style.unwrap_or_default(),
cached_id: None,
})
},
)?,
)?;
datatype_table.set(
"fromEnum",
lua.create_function(|_, value: LuaUserDataRef<EnumItem>| {
if value.parent.desc.name == "Font" { if value.parent.desc.name == "Font" {
match Font::from_enum_item(&value) { match Font::from_enum_item(&value) {
Some(props) => Ok(props), Some(props) => Ok(props),
@ -66,11 +57,9 @@ impl Font {
value.parent.desc.name value.parent.desc.name
))) )))
} }
})?, };
)?;
datatype_table.set( let font_from_name =
"fromName",
lua.create_function(
|_, (file, weight, style): (String, Option<FontWeight>, Option<FontStyle>)| { |_, (file, weight, style): (String, Option<FontWeight>, Option<FontStyle>)| {
Ok(Font { Ok(Font {
family: format!("rbxasset://fonts/families/{}.json", file), family: format!("rbxasset://fonts/families/{}.json", file),
@ -78,12 +67,9 @@ impl Font {
style: style.unwrap_or_default(), style: style.unwrap_or_default(),
cached_id: None, cached_id: None,
}) })
}, };
)?,
)?; let font_from_id =
datatype_table.set(
"fromId",
lua.create_function(
|_, (id, weight, style): (i32, Option<FontWeight>, Option<FontStyle>)| { |_, (id, weight, style): (i32, Option<FontWeight>, Option<FontStyle>)| {
Ok(Font { Ok(Font {
family: format!("rbxassetid://{}", id), family: format!("rbxassetid://{}", id),
@ -91,9 +77,24 @@ impl Font {
style: style.unwrap_or_default(), style: style.unwrap_or_default(),
cached_id: None, cached_id: None,
}) })
}, };
)?,
) let font_new =
|_, (family, weight, style): (String, Option<FontWeight>, Option<FontStyle>)| {
Ok(Font {
family,
weight: weight.unwrap_or_default(),
style: style.unwrap_or_default(),
cached_id: None,
})
};
TableBuilder::new(lua)?
.with_function("fromEnum", font_from_enum)?
.with_function("fromName", font_from_name)?
.with_function("fromId", font_from_id)?
.with_function("new", font_new)?
.build_readonly()
} }
} }

View file

@ -3,6 +3,8 @@ use core::fmt;
use mlua::prelude::*; use mlua::prelude::*;
use rbx_dom_weak::types::NumberRange as DomNumberRange; use rbx_dom_weak::types::NumberRange as DomNumberRange;
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
use super::super::*; use super::super::*;
/** /**
@ -16,11 +18,11 @@ pub struct NumberRange {
pub(crate) max: f32, pub(crate) max: f32,
} }
impl NumberRange { impl LuaExportsTable<'_> for NumberRange {
pub(crate) fn make_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> { const EXPORT_NAME: &'static str = "NumberRange";
datatype_table.set(
"new", fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
lua.create_function(|_, (min, max): (f32, Option<f32>)| { let number_range_new = |_, (min, max): (f32, Option<f32>)| {
Ok(match max { Ok(match max {
Some(max) => NumberRange { Some(max) => NumberRange {
min: min.min(max), min: min.min(max),
@ -28,8 +30,11 @@ impl NumberRange {
}, },
None => NumberRange { min, max: min }, None => NumberRange { min, max: min },
}) })
})?, };
)
TableBuilder::new(lua)?
.with_function("new", number_range_new)?
.build_readonly()
} }
} }

View file

@ -5,6 +5,8 @@ use rbx_dom_weak::types::{
NumberSequence as DomNumberSequence, NumberSequenceKeypoint as DomNumberSequenceKeypoint, NumberSequence as DomNumberSequence, NumberSequenceKeypoint as DomNumberSequenceKeypoint,
}; };
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
use super::{super::*, NumberSequenceKeypoint}; use super::{super::*, NumberSequenceKeypoint};
/** /**
@ -17,14 +19,15 @@ pub struct NumberSequence {
pub(crate) keypoints: Vec<NumberSequenceKeypoint>, pub(crate) keypoints: Vec<NumberSequenceKeypoint>,
} }
impl NumberSequence { impl LuaExportsTable<'_> for NumberSequence {
pub(crate) fn make_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> { const EXPORT_NAME: &'static str = "NumberSequence";
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
type ArgsColor = f32; type ArgsColor = f32;
type ArgsColors = (f32, f32); type ArgsColors = (f32, f32);
type ArgsKeypoints<'lua> = Vec<LuaUserDataRef<'lua, NumberSequenceKeypoint>>; type ArgsKeypoints<'lua> = Vec<LuaUserDataRef<'lua, NumberSequenceKeypoint>>;
datatype_table.set(
"new", let number_sequence_new = |lua, args: LuaMultiValue| {
lua.create_function(|lua, args: LuaMultiValue| {
if let Ok(value) = ArgsColor::from_lua_multi(args.clone(), lua) { if let Ok(value) = ArgsColor::from_lua_multi(args.clone(), lua) {
Ok(NumberSequence { Ok(NumberSequence {
keypoints: vec![ keypoints: vec![
@ -65,8 +68,11 @@ impl NumberSequence {
"Invalid arguments to constructor".to_string(), "Invalid arguments to constructor".to_string(),
)) ))
} }
})?, };
)
TableBuilder::new(lua)?
.with_function("new", number_sequence_new)?
.build_readonly()
} }
} }

View file

@ -3,6 +3,8 @@ use core::fmt;
use mlua::prelude::*; use mlua::prelude::*;
use rbx_dom_weak::types::NumberSequenceKeypoint as DomNumberSequenceKeypoint; use rbx_dom_weak::types::NumberSequenceKeypoint as DomNumberSequenceKeypoint;
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
use super::super::*; use super::super::*;
/** /**
@ -17,19 +19,21 @@ pub struct NumberSequenceKeypoint {
pub(crate) envelope: f32, pub(crate) envelope: f32,
} }
impl NumberSequenceKeypoint { impl LuaExportsTable<'_> for NumberSequenceKeypoint {
pub(crate) fn make_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> { const EXPORT_NAME: &'static str = "NumberSequenceKeypoint";
datatype_table.set(
"new", fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
lua.create_function(|_, (time, value, envelope): (f32, f32, Option<f32>)| { let number_sequence_keypoint_new = |_, (time, value, envelope): (f32, f32, Option<f32>)| {
Ok(NumberSequenceKeypoint { Ok(NumberSequenceKeypoint {
time, time,
value, value,
envelope: envelope.unwrap_or_default(), envelope: envelope.unwrap_or_default(),
}) })
})?, };
)?;
Ok(()) TableBuilder::new(lua)?
.with_function("new", number_sequence_keypoint_new)?
.build_readonly()
} }
} }

View file

@ -3,6 +3,8 @@ use core::fmt;
use mlua::prelude::*; use mlua::prelude::*;
use rbx_dom_weak::types::CustomPhysicalProperties as DomCustomPhysicalProperties; use rbx_dom_weak::types::CustomPhysicalProperties as DomCustomPhysicalProperties;
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
use super::{super::*, EnumItem}; use super::{super::*, EnumItem};
/** /**
@ -32,13 +34,16 @@ impl PhysicalProperties {
elasticity_weight: props.5, elasticity_weight: props.5,
}) })
} }
}
pub(crate) fn make_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> { impl LuaExportsTable<'_> for PhysicalProperties {
const EXPORT_NAME: &'static str = "PhysicalProperties";
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
type ArgsMaterial<'lua> = LuaUserDataRef<'lua, EnumItem>; type ArgsMaterial<'lua> = LuaUserDataRef<'lua, EnumItem>;
type ArgsNumbers = (f32, f32, f32, Option<f32>, Option<f32>); type ArgsNumbers = (f32, f32, f32, Option<f32>, Option<f32>);
datatype_table.set(
"new", let physical_properties_new = |lua, args: LuaMultiValue| {
lua.create_function(|lua, args: LuaMultiValue| {
if let Ok(value) = ArgsMaterial::from_lua_multi(args.clone(), lua) { if let Ok(value) = ArgsMaterial::from_lua_multi(args.clone(), lua) {
if value.parent.desc.name == "Material" { if value.parent.desc.name == "Material" {
match PhysicalProperties::from_material(&value) { match PhysicalProperties::from_material(&value) {
@ -54,13 +59,8 @@ impl PhysicalProperties {
value.parent.desc.name value.parent.desc.name
))) )))
} }
} else if let Ok(( } else if let Ok((density, friction, elasticity, friction_weight, elasticity_weight)) =
density, ArgsNumbers::from_lua_multi(args, lua)
friction,
elasticity,
friction_weight,
elasticity_weight,
)) = ArgsNumbers::from_lua_multi(args, lua)
{ {
Ok(PhysicalProperties { Ok(PhysicalProperties {
density, density,
@ -75,8 +75,11 @@ impl PhysicalProperties {
"Invalid arguments to constructor".to_string(), "Invalid arguments to constructor".to_string(),
)) ))
} }
})?, };
)
TableBuilder::new(lua)?
.with_function("new", physical_properties_new)?
.build_readonly()
} }
} }

View file

@ -4,6 +4,8 @@ use glam::Vec3;
use mlua::prelude::*; use mlua::prelude::*;
use rbx_dom_weak::types::Ray as DomRay; use rbx_dom_weak::types::Ray as DomRay;
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
use super::{super::*, Vector3}; use super::{super::*, Vector3};
/** /**
@ -26,19 +28,23 @@ impl Ray {
let dot_product = lhs.dot(norm).max(0.0); let dot_product = lhs.dot(norm).max(0.0);
self.origin + norm * dot_product self.origin + norm * dot_product
} }
}
pub(crate) fn make_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> { impl LuaExportsTable<'_> for Ray {
datatype_table.set( const EXPORT_NAME: &'static str = "Ray";
"new",
lua.create_function( fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
let ray_new =
|_, (origin, direction): (LuaUserDataRef<Vector3>, LuaUserDataRef<Vector3>)| { |_, (origin, direction): (LuaUserDataRef<Vector3>, LuaUserDataRef<Vector3>)| {
Ok(Ray { Ok(Ray {
origin: origin.0, origin: origin.0,
direction: direction.0, direction: direction.0,
}) })
}, };
)?,
) TableBuilder::new(lua)?
.with_function("new", ray_new)?
.build_readonly()
} }
} }

View file

@ -5,6 +5,8 @@ use glam::Vec2;
use mlua::prelude::*; use mlua::prelude::*;
use rbx_dom_weak::types::Rect as DomRect; use rbx_dom_weak::types::Rect as DomRect;
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
use super::{super::*, Vector2}; use super::{super::*, Vector2};
/** /**
@ -28,16 +30,17 @@ impl Rect {
} }
} }
impl Rect { impl LuaExportsTable<'_> for Rect {
pub(crate) fn make_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> { const EXPORT_NAME: &'static str = "Rect";
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
type ArgsVector2s<'lua> = ( type ArgsVector2s<'lua> = (
Option<LuaUserDataRef<'lua, Vector2>>, Option<LuaUserDataRef<'lua, Vector2>>,
Option<LuaUserDataRef<'lua, Vector2>>, Option<LuaUserDataRef<'lua, Vector2>>,
); );
type ArgsNums = (Option<f32>, Option<f32>, Option<f32>, Option<f32>); type ArgsNums = (Option<f32>, Option<f32>, Option<f32>, Option<f32>);
datatype_table.set(
"new", let rect_new = |lua, args: LuaMultiValue| {
lua.create_function(|lua, args: LuaMultiValue| {
if let Ok((min, max)) = ArgsVector2s::from_lua_multi(args.clone(), lua) { if let Ok((min, max)) = ArgsVector2s::from_lua_multi(args.clone(), lua) {
Ok(Rect::new( Ok(Rect::new(
min.map(|m| *m).unwrap_or_default().0, min.map(|m| *m).unwrap_or_default().0,
@ -53,8 +56,11 @@ impl Rect {
"Invalid arguments to constructor".to_string(), "Invalid arguments to constructor".to_string(),
)) ))
} }
})?, };
)
TableBuilder::new(lua)?
.with_function("new", rect_new)?
.build_readonly()
} }
} }

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