mirror of
https://github.com/lune-org/lune.git
synced 2025-05-04 10:43:57 +01:00
merge: origin/main -> feature/datetime
This commit is contained in:
commit
a5ed13e62b
124 changed files with 4577 additions and 4268 deletions
55
CHANGELOG.md
55
CHANGELOG.md
|
@ -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/Read–eval–print_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
406
Cargo.lock
generated
|
@ -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"
|
||||||
|
|
13
Cargo.toml
13
Cargo.toml
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)>
|
|
@ -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)?
|
|
@ -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)?
|
|
@ -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}'")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
|
@ -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>(
|
101
src/lune/builtins/net/processing.rs
Normal file
101
src/lune/builtins/net/processing.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -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))
|
|
||||||
}
|
|
||||||
}
|
|
203
src/lune/builtins/net/server.rs
Normal file
203
src/lune/builtins/net/server.rs
Normal 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()
|
||||||
|
}
|
|
@ -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)
|
||||||
}
|
}
|
|
@ -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()
|
|
||||||
}
|
|
219
src/lune/builtins/process/mod.rs
Normal file
219
src/lune/builtins/process/mod.rs
Normal 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))
|
||||||
|
}
|
||||||
|
}
|
177
src/lune/builtins/process/options.rs
Normal file
177
src/lune/builtins/process/options.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,
|
|
@ -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::{
|
||||||
self,
|
lune::util::TableBuilder,
|
||||||
document::{Document, DocumentError, DocumentFormat, DocumentKind},
|
roblox::{
|
||||||
instance::Instance,
|
self,
|
||||||
reflection::Database as ReflectionDatabase,
|
document::{Document, DocumentError, DocumentFormat, DocumentKind},
|
||||||
|
instance::Instance,
|
||||||
|
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)?
|
|
@ -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);
|
||||||
|
|
|
@ -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)?
|
|
@ -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},
|
|
||||||
},
|
},
|
||||||
table::TableBuilder,
|
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)?
|
||||||
let ansi_string = format_style(style_from_color_str(&color)?);
|
.with_function("style", stdio_style)?
|
||||||
Ok(ansi_string)
|
.with_function("format", stdio_format)?
|
||||||
})?
|
.with_async_function("write", stdio_write)?
|
||||||
.with_function("style", |_, style: String| {
|
.with_async_function("ewrite", stdio_ewrite)?
|
||||||
let ansi_string = format_style(style_from_style_str(&style)?);
|
.with_async_function("prompt", stdio_prompt)?
|
||||||
Ok(ansi_string)
|
|
||||||
})?
|
|
||||||
.with_function("format", |_, args: LuaMultiValue| {
|
|
||||||
pretty_format_multi_value(&args)
|
|
||||||
})?
|
|
||||||
.with_async_function("write", |_, s: LuaString| async move {
|
|
||||||
let mut stdout = io::stdout();
|
|
||||||
stdout.write_all(s.as_bytes()).await?;
|
|
||||||
stdout.flush().await?;
|
|
||||||
Ok(())
|
|
||||||
})?
|
|
||||||
.with_async_function("ewrite", |_, s: LuaString| async move {
|
|
||||||
let mut stderr = io::stderr();
|
|
||||||
stderr.write_all(s.as_bytes()).await?;
|
|
||||||
stderr.flush().await?;
|
|
||||||
Ok(())
|
|
||||||
})?
|
|
||||||
.with_async_function("prompt", |_, options: PromptOptions| async move {
|
|
||||||
task::spawn_blocking(move || prompt(options))
|
|
||||||
.await
|
|
||||||
.into_lua_err()?
|
|
||||||
})?
|
|
||||||
.build_readonly()
|
.build_readonly()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prompt_theme() -> ColorfulTheme {
|
fn stdio_color(_: &Lua, color: String) -> LuaResult<String> {
|
||||||
ColorfulTheme::default()
|
let ansi_string = format_style(style_from_color_str(&color)?);
|
||||||
|
Ok(ansi_string)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stdio_style(_: &Lua, color: String) -> LuaResult<String> {
|
||||||
|
let ansi_string = format_style(style_from_style_str(&color)?);
|
||||||
|
Ok(ansi_string)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stdio_format(_: &Lua, args: LuaMultiValue) -> LuaResult<String> {
|
||||||
|
pretty_format_multi_value(&args)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn stdio_write(_: &Lua, s: LuaString<'_>) -> LuaResult<()> {
|
||||||
|
let mut stdout = io::stdout();
|
||||||
|
stdout.write_all(s.as_bytes()).await?;
|
||||||
|
stdout.flush().await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn stdio_ewrite(_: &Lua, s: LuaString<'_>) -> LuaResult<()> {
|
||||||
|
let mut stderr = io::stderr();
|
||||||
|
stderr.write_all(s.as_bytes()).await?;
|
||||||
|
stderr.flush().await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn stdio_prompt(_: &Lua, options: PromptOptions) -> LuaResult<PromptResult> {
|
||||||
|
task::spawn_blocking(move || prompt(options))
|
||||||
|
.await
|
||||||
|
.into_lua_err()?
|
||||||
}
|
}
|
||||||
|
|
||||||
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()?;
|
|
@ -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),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
126
src/lune/builtins/task/mod.rs
Normal file
126
src/lune/builtins/task/mod.rs
Normal 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())
|
||||||
|
}
|
30
src/lune/builtins/task/tof.rs
Normal file
30
src/lune/builtins/task/tof.rs
Normal 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()),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
|
|
@ -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!(
|
||||||
|
|
5
src/lune/globals/g_table.rs
Normal file
5
src/lune/globals/g_table.rs
Normal 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
28
src/lune/globals/mod.rs
Normal 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
14
src/lune/globals/print.rs
Normal 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(())
|
||||||
|
})
|
||||||
|
}
|
16
src/lune/globals/require/alias.rs
Normal file
16
src/lune/globals/require/alias.rs
Normal 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}')"
|
||||||
|
)))
|
||||||
|
}
|
14
src/lune/globals/require/builtin.rs
Normal file
14
src/lune/globals/require/builtin.rs
Normal 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)
|
||||||
|
}
|
310
src/lune/globals/require/context.rs
Normal file
310
src/lune/globals/require/context.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
98
src/lune/globals/require/mod.rs
Normal file
98
src/lune/globals/require/mod.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
98
src/lune/globals/require/path.rs
Normal file
98
src/lune/globals/require/path.rs
Normal 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
|
||||||
|
}
|
27
src/lune/globals/typeof.rs
Normal file
27
src/lune/globals/typeof.rs
Normal 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
|
||||||
|
}
|
24
src/lune/globals/version.rs
Normal file
24
src/lune/globals/version.rs
Normal 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
21
src/lune/globals/warn.rs
Normal 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(())
|
||||||
|
})
|
||||||
|
}
|
|
@ -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(())
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
mod copy;
|
|
||||||
mod metadata;
|
|
||||||
mod options;
|
|
||||||
|
|
||||||
pub use copy::copy;
|
|
||||||
pub use metadata::FsMetadata;
|
|
||||||
pub use options::FsWriteOptions;
|
|
|
@ -1,3 +0,0 @@
|
||||||
mod options;
|
|
||||||
|
|
||||||
pub use options::{LuauCompileOptions, LuauLoadOptions};
|
|
|
@ -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;
|
|
|
@ -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;
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
mod compress_decompress;
|
|
||||||
mod encode_decode;
|
|
||||||
|
|
||||||
pub use compress_decompress::{compress, decompress, CompressDecompressFormat};
|
|
||||||
pub use encode_decode::{EncodeDecodeConfig, EncodeDecodeFormat};
|
|
|
@ -1,2 +0,0 @@
|
||||||
pub mod formatting;
|
|
||||||
pub mod prompt;
|
|
|
@ -1,3 +0,0 @@
|
||||||
mod builder;
|
|
||||||
|
|
||||||
pub use builder::TableBuilder;
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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::*;
|
|
|
@ -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)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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")
|
|
||||||
)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<()>),
|
|
||||||
}
|
|
|
@ -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(())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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}")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 {}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
115
src/lune/mod.rs
115
src/lune/mod.rs
|
@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
138
src/lune/scheduler/impl_async.rs
Normal file
138
src/lune/scheduler/impl_async.rs
Normal 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(())
|
||||||
|
}
|
||||||
|
}
|
260
src/lune/scheduler/impl_runner.rs
Normal file
260
src/lune/scheduler/impl_runner.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
181
src/lune/scheduler/impl_threads.rs
Normal file
181
src/lune/scheduler/impl_threads.rs
Normal 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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
98
src/lune/scheduler/message.rs
Normal file
98
src/lune/scheduler/message.rs
Normal 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
116
src/lune/scheduler/mod.rs
Normal 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
176
src/lune/scheduler/state.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
105
src/lune/scheduler/thread.rs
Normal file
105
src/lune/scheduler/thread.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
118
src/lune/scheduler/traits.rs
Normal file
118
src/lune/scheduler/traits.rs
Normal 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()?)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
6
src/lune/util/mod.rs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
mod table_builder;
|
||||||
|
|
||||||
|
pub mod formatting;
|
||||||
|
pub mod traits;
|
||||||
|
|
||||||
|
pub use table_builder::TableBuilder;
|
|
@ -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
15
src/lune/util/traits.rs
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,
|
||||||
|
|
|
@ -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,53 +19,58 @@ 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| {
|
|
||||||
if e.parent.desc.name == "Axis" {
|
let mut check = |e: &EnumItem| {
|
||||||
match &e.name {
|
if e.parent.desc.name == "Axis" {
|
||||||
name if name == "X" => x = true,
|
match &e.name {
|
||||||
name if name == "Y" => y = true,
|
name if name == "X" => x = true,
|
||||||
name if name == "Z" => z = true,
|
name if name == "Y" => y = true,
|
||||||
_ => {}
|
name if name == "Z" => z = true,
|
||||||
}
|
_ => {}
|
||||||
} else if e.parent.desc.name == "NormalId" {
|
|
||||||
match &e.name {
|
|
||||||
name if name == "Left" || name == "Right" => x = true,
|
|
||||||
name if name == "Top" || name == "Bottom" => y = true,
|
|
||||||
name if name == "Front" || name == "Back" => z = true,
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
} else if e.parent.desc.name == "NormalId" {
|
||||||
for (index, arg) in args.into_iter().enumerate() {
|
match &e.name {
|
||||||
if let LuaValue::UserData(u) = arg {
|
name if name == "Left" || name == "Right" => x = true,
|
||||||
if let Ok(e) = u.borrow::<EnumItem>() {
|
name if name == "Top" || name == "Bottom" => y = true,
|
||||||
check(&e);
|
name if name == "Front" || name == "Back" => z = true,
|
||||||
} else {
|
_ => {}
|
||||||
return Err(LuaError::RuntimeError(format!(
|
|
||||||
"Expected argument #{} to be an EnumItem, got userdata",
|
|
||||||
index
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(LuaError::RuntimeError(format!(
|
|
||||||
"Expected argument #{} to be an EnumItem, got {}",
|
|
||||||
index,
|
|
||||||
arg.type_name()
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(Axes { x, y, z })
|
};
|
||||||
})?,
|
|
||||||
)?;
|
for (index, arg) in args.into_iter().enumerate() {
|
||||||
Ok(())
|
if let LuaValue::UserData(u) = arg {
|
||||||
|
if let Ok(e) = u.borrow::<EnumItem>() {
|
||||||
|
check(&e);
|
||||||
|
} else {
|
||||||
|
return Err(LuaError::RuntimeError(format!(
|
||||||
|
"Expected argument #{} to be an EnumItem, got userdata",
|
||||||
|
index
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(LuaError::RuntimeError(format!(
|
||||||
|
"Expected argument #{} to be an EnumItem, got {}",
|
||||||
|
index,
|
||||||
|
arg.type_name()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Axes { x, y, z })
|
||||||
|
};
|
||||||
|
|
||||||
|
TableBuilder::new(lua)?
|
||||||
|
.with_function("new", axes_new)?
|
||||||
|
.build_readonly()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,57 +22,58 @@ 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) {
|
Ok(color_from_name(name))
|
||||||
Ok(color_from_name(name))
|
} else if let Ok((r, g, b)) = ArgsRgb::from_lua_multi(args.clone(), lua) {
|
||||||
} else if let Ok((r, g, b)) = ArgsRgb::from_lua_multi(args.clone(), lua) {
|
Ok(color_from_rgb(r, g, b))
|
||||||
Ok(color_from_rgb(r, g, b))
|
} else if let Ok(color) = ArgsColor3::from_lua_multi(args.clone(), lua) {
|
||||||
} else if let Ok(color) = ArgsColor3::from_lua_multi(args.clone(), lua) {
|
Ok(Self::from(*color))
|
||||||
Ok(Self::from(*color))
|
} else {
|
||||||
} else {
|
// FUTURE: Better error message here using given arg types
|
||||||
// FUTURE: Better error message here using given arg types
|
Err(LuaError::RuntimeError(
|
||||||
Err(LuaError::RuntimeError(
|
"Invalid arguments to constructor".to_string(),
|
||||||
"Invalid arguments to constructor".to_string(),
|
))
|
||||||
))
|
}
|
||||||
}
|
};
|
||||||
})?,
|
|
||||||
)?;
|
let brick_color_palette = |_, index: u16| {
|
||||||
datatype_table.set(
|
if index == 0 {
|
||||||
"palette",
|
Err(LuaError::RuntimeError("Invalid index".to_string()))
|
||||||
lua.create_function(|_, index: u16| {
|
} else if let Some(number) = BRICK_COLOR_PALETTE.get((index - 1) as usize) {
|
||||||
if index == 0 {
|
Ok(color_from_number(*number))
|
||||||
Err(LuaError::RuntimeError("Invalid index".to_string()))
|
} else {
|
||||||
} else if let Some(number) = BRICK_COLOR_PALETTE.get((index - 1) as usize) {
|
Err(LuaError::RuntimeError("Invalid index".to_string()))
|
||||||
Ok(color_from_number(*number))
|
}
|
||||||
} else {
|
};
|
||||||
Err(LuaError::RuntimeError("Invalid index".to_string()))
|
|
||||||
}
|
let brick_color_random = |_, ()| {
|
||||||
})?,
|
let number = BRICK_COLOR_PALETTE.choose(&mut rand::thread_rng());
|
||||||
)?;
|
Ok(color_from_number(*number.unwrap()))
|
||||||
datatype_table.set(
|
};
|
||||||
"random",
|
|
||||||
lua.create_function(|_, ()| {
|
let mut builder = TableBuilder::new(lua)?
|
||||||
let number = BRICK_COLOR_PALETTE.choose(&mut rand::thread_rng());
|
.with_function("new", brick_color_new)?
|
||||||
Ok(color_from_number(*number.unwrap()))
|
.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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,79 +37,61 @@ impl CFrame {
|
||||||
fn inverse(&self) -> Self {
|
fn inverse(&self) -> Self {
|
||||||
Self(self.0.inverse())
|
Self(self.0.inverse())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LuaExportsTable<'_> for CFrame {
|
||||||
|
const EXPORT_NAME: &'static str = "CFrame";
|
||||||
|
|
||||||
|
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
|
||||||
|
let cframe_angles = |_, (rx, ry, rz): (f32, f32, f32)| {
|
||||||
|
Ok(CFrame(Mat4::from_euler(EulerRot::XYZ, rx, ry, rz)))
|
||||||
|
};
|
||||||
|
|
||||||
|
let cframe_from_axis_angle =
|
||||||
|
|_, (v, r): (LuaUserDataRef<Vector3>, f32)| Ok(CFrame(Mat4::from_axis_angle(v.0, r)));
|
||||||
|
|
||||||
|
let cframe_from_euler_angles_xyz = |_, (rx, ry, rz): (f32, f32, f32)| {
|
||||||
|
Ok(CFrame(Mat4::from_euler(EulerRot::XYZ, rx, ry, rz)))
|
||||||
|
};
|
||||||
|
|
||||||
|
let cframe_from_euler_angles_yxz = |_, (rx, ry, rz): (f32, f32, f32)| {
|
||||||
|
Ok(CFrame(Mat4::from_euler(EulerRot::YXZ, ry, rx, rz)))
|
||||||
|
};
|
||||||
|
|
||||||
|
let cframe_from_matrix = |_,
|
||||||
|
(pos, rx, ry, rz): (
|
||||||
|
LuaUserDataRef<Vector3>,
|
||||||
|
LuaUserDataRef<Vector3>,
|
||||||
|
LuaUserDataRef<Vector3>,
|
||||||
|
Option<LuaUserDataRef<Vector3>>,
|
||||||
|
)| {
|
||||||
|
Ok(CFrame(Mat4::from_cols(
|
||||||
|
rx.0.extend(0.0),
|
||||||
|
ry.0.extend(0.0),
|
||||||
|
rz.map(|r| r.0)
|
||||||
|
.unwrap_or_else(|| rx.0.cross(ry.0).normalize())
|
||||||
|
.extend(0.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,
|
||||||
|
)))
|
||||||
|
};
|
||||||
|
|
||||||
pub(crate) fn make_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> {
|
|
||||||
// Constants
|
|
||||||
datatype_table.set("identity", CFrame(Mat4::IDENTITY))?;
|
|
||||||
// Strict args constructors
|
|
||||||
datatype_table.set(
|
|
||||||
"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)))
|
|
||||||
})?,
|
|
||||||
)?;
|
|
||||||
datatype_table.set(
|
|
||||||
"fromEulerAnglesYXZ",
|
|
||||||
lua.create_function(|_, (rx, ry, rz): (f32, f32, f32)| {
|
|
||||||
Ok(CFrame(Mat4::from_euler(EulerRot::YXZ, ry, rx, rz)))
|
|
||||||
})?,
|
|
||||||
)?;
|
|
||||||
datatype_table.set(
|
|
||||||
"Angles",
|
|
||||||
lua.create_function(|_, (rx, ry, rz): (f32, f32, f32)| {
|
|
||||||
Ok(CFrame(Mat4::from_euler(EulerRot::XYZ, rx, ry, rz)))
|
|
||||||
})?,
|
|
||||||
)?;
|
|
||||||
datatype_table.set(
|
|
||||||
"fromOrientation",
|
|
||||||
lua.create_function(|_, (rx, ry, rz): (f32, f32, f32)| {
|
|
||||||
Ok(CFrame(Mat4::from_euler(EulerRot::YXZ, ry, rx, rz)))
|
|
||||||
})?,
|
|
||||||
)?;
|
|
||||||
datatype_table.set(
|
|
||||||
"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): (
|
|
||||||
LuaUserDataRef<Vector3>,
|
|
||||||
LuaUserDataRef<Vector3>,
|
|
||||||
LuaUserDataRef<Vector3>,
|
|
||||||
Option<LuaUserDataRef<Vector3>>,
|
|
||||||
)| {
|
|
||||||
Ok(CFrame(Mat4::from_cols(
|
|
||||||
rx.0.extend(0.0),
|
|
||||||
ry.0.extend(0.0),
|
|
||||||
rz.map(|r| r.0)
|
|
||||||
.unwrap_or_else(|| rx.0.cross(ry.0).normalize())
|
|
||||||
.extend(0.0),
|
|
||||||
pos.0.extend(1.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,48 +99,59 @@ 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) {
|
Ok(CFrame(Mat4::from_translation(pos.0)))
|
||||||
Ok(CFrame(Mat4::from_translation(pos.0)))
|
} else if let Ok((from, to, up)) = ArgsLook::from_lua_multi(args.clone(), lua) {
|
||||||
} else if let Ok((from, to, up)) = ArgsLook::from_lua_multi(args.clone(), lua) {
|
Ok(CFrame(look_at(
|
||||||
Ok(CFrame(look_at(
|
from.0,
|
||||||
from.0,
|
to.0,
|
||||||
to.0,
|
up.as_deref().unwrap_or(&Vector3(Vec3::Y)).0,
|
||||||
up.as_deref().unwrap_or(&Vector3(Vec3::Y)).0,
|
)))
|
||||||
)))
|
} else if let Ok((x, y, z)) = ArgsPosXYZ::from_lua_multi(args.clone(), lua) {
|
||||||
} else if let Ok((x, y, z)) = ArgsPosXYZ::from_lua_multi(args.clone(), lua) {
|
Ok(CFrame(Mat4::from_translation(Vec3::new(x, y, z))))
|
||||||
Ok(CFrame(Mat4::from_translation(Vec3::new(x, y, z))))
|
} else if let Ok((x, y, z, qx, qy, qz, qw)) =
|
||||||
} else if let Ok((x, y, z, qx, qy, qz, qw)) =
|
ArgsPosXYZQuat::from_lua_multi(args.clone(), lua)
|
||||||
ArgsPosXYZQuat::from_lua_multi(args.clone(), lua)
|
{
|
||||||
{
|
Ok(CFrame(Mat4::from_rotation_translation(
|
||||||
Ok(CFrame(Mat4::from_rotation_translation(
|
Quat::from_array([qx, qy, qz, qw]),
|
||||||
Quat::from_array([qx, qy, qz, qw]),
|
Vec3::new(x, y, z),
|
||||||
Vec3::new(x, y, z),
|
)))
|
||||||
)))
|
} else if let Ok((x, y, z, r00, r01, r02, r10, r11, r12, r20, r21, r22)) =
|
||||||
} else if let Ok((x, y, z, r00, r01, r02, r10, r11, r12, r20, r21, r22)) =
|
ArgsMatrix::from_lua_multi(args, lua)
|
||||||
ArgsMatrix::from_lua_multi(args, lua)
|
{
|
||||||
{
|
Ok(CFrame(Mat4::from_cols_array_2d(&[
|
||||||
Ok(CFrame(Mat4::from_cols_array_2d(&[
|
[r00, r01, r02, 0.0],
|
||||||
[r00, r01, r02, 0.0],
|
[r10, r11, r12, 0.0],
|
||||||
[r10, r11, r12, 0.0],
|
[r20, r21, r22, 0.0],
|
||||||
[r20, r21, r22, 0.0],
|
[x, y, z, 1.0],
|
||||||
[x, y, z, 1.0],
|
])))
|
||||||
])))
|
} else {
|
||||||
} else {
|
// FUTURE: Better error message here using given arg types
|
||||||
// FUTURE: Better error message here using given arg types
|
Err(LuaError::RuntimeError(
|
||||||
Err(LuaError::RuntimeError(
|
"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>| {
|
||||||
},
|
let inverse = this.inverse();
|
||||||
);
|
Ok(inverse * *rhs)
|
||||||
methods.add_method(
|
});
|
||||||
"ToObjectSpace",
|
|
||||||
|_, this, rhs: Variadic<LuaUserDataRef<CFrame>>| {
|
|
||||||
let inverse = this.inverse();
|
|
||||||
Ok(Variadic::from_iter(rhs.into_iter().map(|cf| inverse * *cf)))
|
|
||||||
},
|
|
||||||
);
|
|
||||||
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]
|
||||||
|
|
|
@ -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,88 +24,87 @@ 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",
|
|
||||||
lua.create_function(|_, (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(),
|
|
||||||
})
|
|
||||||
})?,
|
|
||||||
)?;
|
|
||||||
datatype_table.set(
|
|
||||||
"fromRGB",
|
|
||||||
lua.create_function(|_, (r, g, b): (Option<u8>, Option<u8>, Option<u8>)| {
|
|
||||||
Ok(Color3 {
|
|
||||||
r: (r.unwrap_or_default() as f32) / 255f32,
|
|
||||||
g: (g.unwrap_or_default() as f32) / 255f32,
|
|
||||||
b: (b.unwrap_or_default() as f32) / 255f32,
|
|
||||||
})
|
|
||||||
})?,
|
|
||||||
)?;
|
|
||||||
datatype_table.set(
|
|
||||||
"fromHSV",
|
|
||||||
lua.create_function(|_, (h, s, v): (f32, f32, f32)| {
|
|
||||||
// https://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c
|
|
||||||
let i = (h * 6.0).floor();
|
|
||||||
let f = h * 6.0 - i;
|
|
||||||
let p = v * (1.0 - s);
|
|
||||||
let q = v * (1.0 - f * s);
|
|
||||||
let t = v * (1.0 - (1.0 - f) * s);
|
|
||||||
|
|
||||||
let (r, g, b) = match (i % 6.0) as u8 {
|
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
|
||||||
0 => (v, t, p),
|
let color3_from_rgb = |_, (r, g, b): (Option<u8>, Option<u8>, Option<u8>)| {
|
||||||
1 => (q, v, p),
|
Ok(Color3 {
|
||||||
2 => (p, v, t),
|
r: (r.unwrap_or_default() as f32) / 255f32,
|
||||||
3 => (p, q, v),
|
g: (g.unwrap_or_default() as f32) / 255f32,
|
||||||
4 => (t, p, v),
|
b: (b.unwrap_or_default() as f32) / 255f32,
|
||||||
5 => (v, p, q),
|
})
|
||||||
_ => unreachable!(),
|
};
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Color3 { r, g, b })
|
let color3_from_hsv = |_, (h, s, v): (f32, f32, f32)| {
|
||||||
})?,
|
// https://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c
|
||||||
)?;
|
let i = (h * 6.0).floor();
|
||||||
datatype_table.set(
|
let f = h * 6.0 - i;
|
||||||
"fromHex",
|
let p = v * (1.0 - s);
|
||||||
lua.create_function(|_, hex: String| {
|
let q = v * (1.0 - f * s);
|
||||||
let trimmed = hex.trim_start_matches('#').to_ascii_uppercase();
|
let t = v * (1.0 - (1.0 - f) * s);
|
||||||
let chars = if trimmed.len() == 3 {
|
|
||||||
(
|
let (r, g, b) = match (i % 6.0) as u8 {
|
||||||
u8::from_str_radix(&trimmed[..1].repeat(2), 16),
|
0 => (v, t, p),
|
||||||
u8::from_str_radix(&trimmed[1..2].repeat(2), 16),
|
1 => (q, v, p),
|
||||||
u8::from_str_radix(&trimmed[2..3].repeat(2), 16),
|
2 => (p, v, t),
|
||||||
)
|
3 => (p, q, v),
|
||||||
} else if trimmed.len() == 6 {
|
4 => (t, p, v),
|
||||||
(
|
5 => (v, p, q),
|
||||||
u8::from_str_radix(&trimmed[..2], 16),
|
_ => unreachable!(),
|
||||||
u8::from_str_radix(&trimmed[2..4], 16),
|
};
|
||||||
u8::from_str_radix(&trimmed[4..6], 16),
|
|
||||||
)
|
Ok(Color3 { r, g, b })
|
||||||
} else {
|
};
|
||||||
return Err(LuaError::RuntimeError(format!(
|
|
||||||
"Hex color string must be 3 or 6 characters long, got {} character{}",
|
let color3_from_hex = |_, hex: String| {
|
||||||
trimmed.len(),
|
let trimmed = hex.trim_start_matches('#').to_ascii_uppercase();
|
||||||
if trimmed.len() == 1 { "" } else { "s" }
|
let chars = if trimmed.len() == 3 {
|
||||||
)));
|
(
|
||||||
};
|
u8::from_str_radix(&trimmed[..1].repeat(2), 16),
|
||||||
match chars {
|
u8::from_str_radix(&trimmed[1..2].repeat(2), 16),
|
||||||
(Ok(r), Ok(g), Ok(b)) => Ok(Color3 {
|
u8::from_str_radix(&trimmed[2..3].repeat(2), 16),
|
||||||
r: (r as f32) / 255f32,
|
)
|
||||||
g: (g as f32) / 255f32,
|
} else if trimmed.len() == 6 {
|
||||||
b: (b as f32) / 255f32,
|
(
|
||||||
}),
|
u8::from_str_radix(&trimmed[..2], 16),
|
||||||
_ => Err(LuaError::RuntimeError(format!(
|
u8::from_str_radix(&trimmed[2..4], 16),
|
||||||
"Hex color string '{}' contains invalid character",
|
u8::from_str_radix(&trimmed[4..6], 16),
|
||||||
trimmed
|
)
|
||||||
))),
|
} else {
|
||||||
}
|
return Err(LuaError::RuntimeError(format!(
|
||||||
})?,
|
"Hex color string must be 3 or 6 characters long, got {} character{}",
|
||||||
)?;
|
trimmed.len(),
|
||||||
Ok(())
|
if trimmed.len() == 1 { "" } else { "s" }
|
||||||
|
)));
|
||||||
|
};
|
||||||
|
match chars {
|
||||||
|
(Ok(r), Ok(g), Ok(b)) => Ok(Color3 {
|
||||||
|
r: (r as f32) / 255f32,
|
||||||
|
g: (g as f32) / 255f32,
|
||||||
|
b: (b as f32) / 255f32,
|
||||||
|
}),
|
||||||
|
_ => Err(LuaError::RuntimeError(format!(
|
||||||
|
"Hex color string '{}' contains invalid character",
|
||||||
|
trimmed
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,52 +19,56 @@ 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![
|
ColorSequenceKeypoint {
|
||||||
ColorSequenceKeypoint {
|
time: 0.0,
|
||||||
time: 0.0,
|
color: *color,
|
||||||
color: *color,
|
},
|
||||||
},
|
ColorSequenceKeypoint {
|
||||||
ColorSequenceKeypoint {
|
time: 1.0,
|
||||||
time: 1.0,
|
color: *color,
|
||||||
color: *color,
|
},
|
||||||
},
|
],
|
||||||
],
|
})
|
||||||
})
|
} else if let Ok((c0, c1)) = ArgsColors::from_lua_multi(args.clone(), lua) {
|
||||||
} else if let Ok((c0, c1)) = ArgsColors::from_lua_multi(args.clone(), lua) {
|
Ok(ColorSequence {
|
||||||
Ok(ColorSequence {
|
keypoints: vec![
|
||||||
keypoints: vec![
|
ColorSequenceKeypoint {
|
||||||
ColorSequenceKeypoint {
|
time: 0.0,
|
||||||
time: 0.0,
|
color: *c0,
|
||||||
color: *c0,
|
},
|
||||||
},
|
ColorSequenceKeypoint {
|
||||||
ColorSequenceKeypoint {
|
time: 1.0,
|
||||||
time: 1.0,
|
color: *c1,
|
||||||
color: *c1,
|
},
|
||||||
},
|
],
|
||||||
],
|
})
|
||||||
})
|
} else if let Ok(keypoints) = ArgsKeypoints::from_lua_multi(args, lua) {
|
||||||
} else if let Ok(keypoints) = ArgsKeypoints::from_lua_multi(args, lua) {
|
Ok(ColorSequence {
|
||||||
Ok(ColorSequence {
|
keypoints: keypoints.iter().map(|k| **k).collect(),
|
||||||
keypoints: keypoints.iter().map(|k| **k).collect(),
|
})
|
||||||
})
|
} else {
|
||||||
} else {
|
// FUTURE: Better error message here using given arg types
|
||||||
// FUTURE: Better error message here using given arg types
|
Err(LuaError::RuntimeError(
|
||||||
Err(LuaError::RuntimeError(
|
"Invalid arguments to constructor".to_string(),
|
||||||
"Invalid arguments to constructor".to_string(),
|
))
|
||||||
))
|
}
|
||||||
}
|
};
|
||||||
})?,
|
|
||||||
)
|
TableBuilder::new(lua)?
|
||||||
|
.with_function("new", color_sequence_new)?
|
||||||
|
.build_readonly()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,59 +22,64 @@ 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| {
|
|
||||||
if e.parent.desc.name == "NormalId" {
|
let mut check = |e: &EnumItem| {
|
||||||
match &e.name {
|
if e.parent.desc.name == "NormalId" {
|
||||||
name if name == "Right" => right = true,
|
match &e.name {
|
||||||
name if name == "Top" => top = true,
|
name if name == "Right" => right = true,
|
||||||
name if name == "Back" => back = true,
|
name if name == "Top" => top = true,
|
||||||
name if name == "Left" => left = true,
|
name if name == "Back" => back = true,
|
||||||
name if name == "Bottom" => bottom = true,
|
name if name == "Left" => left = true,
|
||||||
name if name == "Front" => front = true,
|
name if name == "Bottom" => bottom = true,
|
||||||
_ => {}
|
name if name == "Front" => front = true,
|
||||||
}
|
_ => {}
|
||||||
}
|
|
||||||
};
|
|
||||||
for (index, arg) in args.into_iter().enumerate() {
|
|
||||||
if let LuaValue::UserData(u) = arg {
|
|
||||||
if let Ok(e) = u.borrow::<EnumItem>() {
|
|
||||||
check(&e);
|
|
||||||
} else {
|
|
||||||
return Err(LuaError::RuntimeError(format!(
|
|
||||||
"Expected argument #{} to be an EnumItem, got userdata",
|
|
||||||
index
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err(LuaError::RuntimeError(format!(
|
|
||||||
"Expected argument #{} to be an EnumItem, got {}",
|
|
||||||
index,
|
|
||||||
arg.type_name()
|
|
||||||
)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(Faces {
|
};
|
||||||
right,
|
|
||||||
top,
|
for (index, arg) in args.into_iter().enumerate() {
|
||||||
back,
|
if let LuaValue::UserData(u) = arg {
|
||||||
left,
|
if let Ok(e) = u.borrow::<EnumItem>() {
|
||||||
bottom,
|
check(&e);
|
||||||
front,
|
} else {
|
||||||
})
|
return Err(LuaError::RuntimeError(format!(
|
||||||
})?,
|
"Expected argument #{} to be an EnumItem, got userdata",
|
||||||
)?;
|
index
|
||||||
Ok(())
|
)));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(LuaError::RuntimeError(format!(
|
||||||
|
"Expected argument #{} to be an EnumItem, got {}",
|
||||||
|
index,
|
||||||
|
arg.type_name()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Faces {
|
||||||
|
right,
|
||||||
|
top,
|
||||||
|
back,
|
||||||
|
left,
|
||||||
|
bottom,
|
||||||
|
front,
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
TableBuilder::new(lua)?
|
||||||
|
.with_function("new", faces_new)?
|
||||||
|
.build_readonly()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,66 +36,65 @@ 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 {
|
if value.parent.desc.name == "Font" {
|
||||||
family,
|
match Font::from_enum_item(&value) {
|
||||||
weight: weight.unwrap_or_default(),
|
Some(props) => Ok(props),
|
||||||
style: style.unwrap_or_default(),
|
None => Err(LuaError::RuntimeError(format!(
|
||||||
cached_id: None,
|
"Found unknown Font '{}'",
|
||||||
})
|
value.name
|
||||||
},
|
))),
|
||||||
)?,
|
|
||||||
)?;
|
|
||||||
datatype_table.set(
|
|
||||||
"fromEnum",
|
|
||||||
lua.create_function(|_, value: LuaUserDataRef<EnumItem>| {
|
|
||||||
if value.parent.desc.name == "Font" {
|
|
||||||
match Font::from_enum_item(&value) {
|
|
||||||
Some(props) => Ok(props),
|
|
||||||
None => Err(LuaError::RuntimeError(format!(
|
|
||||||
"Found unknown Font '{}'",
|
|
||||||
value.name
|
|
||||||
))),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(LuaError::RuntimeError(format!(
|
|
||||||
"Expected argument #1 to be a Font, got {}",
|
|
||||||
value.parent.desc.name
|
|
||||||
)))
|
|
||||||
}
|
}
|
||||||
})?,
|
} else {
|
||||||
)?;
|
Err(LuaError::RuntimeError(format!(
|
||||||
datatype_table.set(
|
"Expected argument #1 to be a Font, got {}",
|
||||||
"fromName",
|
value.parent.desc.name
|
||||||
lua.create_function(
|
)))
|
||||||
|_, (file, weight, style): (String, Option<FontWeight>, Option<FontStyle>)| {
|
}
|
||||||
Ok(Font {
|
};
|
||||||
family: format!("rbxasset://fonts/families/{}.json", file),
|
|
||||||
weight: weight.unwrap_or_default(),
|
let font_from_name =
|
||||||
style: style.unwrap_or_default(),
|
|_, (file, weight, style): (String, Option<FontWeight>, Option<FontStyle>)| {
|
||||||
cached_id: None,
|
Ok(Font {
|
||||||
})
|
family: format!("rbxasset://fonts/families/{}.json", file),
|
||||||
},
|
weight: weight.unwrap_or_default(),
|
||||||
)?,
|
style: style.unwrap_or_default(),
|
||||||
)?;
|
cached_id: None,
|
||||||
datatype_table.set(
|
})
|
||||||
"fromId",
|
};
|
||||||
lua.create_function(
|
|
||||||
|_, (id, weight, style): (i32, Option<FontWeight>, Option<FontStyle>)| {
|
let font_from_id =
|
||||||
Ok(Font {
|
|_, (id, weight, style): (i32, Option<FontWeight>, Option<FontStyle>)| {
|
||||||
family: format!("rbxassetid://{}", id),
|
Ok(Font {
|
||||||
weight: weight.unwrap_or_default(),
|
family: format!("rbxassetid://{}", id),
|
||||||
style: style.unwrap_or_default(),
|
weight: weight.unwrap_or_default(),
|
||||||
cached_id: None,
|
style: style.unwrap_or_default(),
|
||||||
})
|
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,20 +18,23 @@ 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),
|
||||||
max: min.max(max),
|
max: min.max(max),
|
||||||
},
|
},
|
||||||
None => NumberRange { min, max: min },
|
None => NumberRange { min, max: min },
|
||||||
})
|
})
|
||||||
})?,
|
};
|
||||||
)
|
|
||||||
|
TableBuilder::new(lua)?
|
||||||
|
.with_function("new", number_range_new)?
|
||||||
|
.build_readonly()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,56 +19,60 @@ 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![
|
NumberSequenceKeypoint {
|
||||||
NumberSequenceKeypoint {
|
time: 0.0,
|
||||||
time: 0.0,
|
value,
|
||||||
value,
|
envelope: 0.0,
|
||||||
envelope: 0.0,
|
},
|
||||||
},
|
NumberSequenceKeypoint {
|
||||||
NumberSequenceKeypoint {
|
time: 1.0,
|
||||||
time: 1.0,
|
value,
|
||||||
value,
|
envelope: 0.0,
|
||||||
envelope: 0.0,
|
},
|
||||||
},
|
],
|
||||||
],
|
})
|
||||||
})
|
} else if let Ok((v0, v1)) = ArgsColors::from_lua_multi(args.clone(), lua) {
|
||||||
} else if let Ok((v0, v1)) = ArgsColors::from_lua_multi(args.clone(), lua) {
|
Ok(NumberSequence {
|
||||||
Ok(NumberSequence {
|
keypoints: vec![
|
||||||
keypoints: vec![
|
NumberSequenceKeypoint {
|
||||||
NumberSequenceKeypoint {
|
time: 0.0,
|
||||||
time: 0.0,
|
value: v0,
|
||||||
value: v0,
|
envelope: 0.0,
|
||||||
envelope: 0.0,
|
},
|
||||||
},
|
NumberSequenceKeypoint {
|
||||||
NumberSequenceKeypoint {
|
time: 1.0,
|
||||||
time: 1.0,
|
value: v1,
|
||||||
value: v1,
|
envelope: 0.0,
|
||||||
envelope: 0.0,
|
},
|
||||||
},
|
],
|
||||||
],
|
})
|
||||||
})
|
} else if let Ok(keypoints) = ArgsKeypoints::from_lua_multi(args, lua) {
|
||||||
} else if let Ok(keypoints) = ArgsKeypoints::from_lua_multi(args, lua) {
|
Ok(NumberSequence {
|
||||||
Ok(NumberSequence {
|
keypoints: keypoints.iter().map(|k| **k).collect(),
|
||||||
keypoints: keypoints.iter().map(|k| **k).collect(),
|
})
|
||||||
})
|
} else {
|
||||||
} else {
|
// FUTURE: Better error message here using given arg types
|
||||||
// FUTURE: Better error message here using given arg types
|
Err(LuaError::RuntimeError(
|
||||||
Err(LuaError::RuntimeError(
|
"Invalid arguments to constructor".to_string(),
|
||||||
"Invalid arguments to constructor".to_string(),
|
))
|
||||||
))
|
}
|
||||||
}
|
};
|
||||||
})?,
|
|
||||||
)
|
TableBuilder::new(lua)?
|
||||||
|
.with_function("new", number_sequence_new)?
|
||||||
|
.build_readonly()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,51 +34,52 @@ 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) {
|
Some(props) => Ok(props),
|
||||||
Some(props) => Ok(props),
|
None => Err(LuaError::RuntimeError(format!(
|
||||||
None => Err(LuaError::RuntimeError(format!(
|
"Found unknown Material '{}'",
|
||||||
"Found unknown Material '{}'",
|
value.name
|
||||||
value.name
|
))),
|
||||||
))),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err(LuaError::RuntimeError(format!(
|
|
||||||
"Expected argument #1 to be a Material, got {}",
|
|
||||||
value.parent.desc.name
|
|
||||||
)))
|
|
||||||
}
|
}
|
||||||
} else if let Ok((
|
} else {
|
||||||
|
Err(LuaError::RuntimeError(format!(
|
||||||
|
"Expected argument #1 to be a Material, got {}",
|
||||||
|
value.parent.desc.name
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
} else if let Ok((density, friction, elasticity, friction_weight, elasticity_weight)) =
|
||||||
|
ArgsNumbers::from_lua_multi(args, lua)
|
||||||
|
{
|
||||||
|
Ok(PhysicalProperties {
|
||||||
density,
|
density,
|
||||||
friction,
|
friction,
|
||||||
|
friction_weight: friction_weight.unwrap_or(1.0),
|
||||||
elasticity,
|
elasticity,
|
||||||
friction_weight,
|
elasticity_weight: elasticity_weight.unwrap_or(1.0),
|
||||||
elasticity_weight,
|
})
|
||||||
)) = ArgsNumbers::from_lua_multi(args, lua)
|
} else {
|
||||||
{
|
// FUTURE: Better error message here using given arg types
|
||||||
Ok(PhysicalProperties {
|
Err(LuaError::RuntimeError(
|
||||||
density,
|
"Invalid arguments to constructor".to_string(),
|
||||||
friction,
|
))
|
||||||
friction_weight: friction_weight.unwrap_or(1.0),
|
}
|
||||||
elasticity,
|
};
|
||||||
elasticity_weight: elasticity_weight.unwrap_or(1.0),
|
|
||||||
})
|
TableBuilder::new(lua)?
|
||||||
} else {
|
.with_function("new", physical_properties_new)?
|
||||||
// FUTURE: Better error message here using given arg types
|
.build_readonly()
|
||||||
Err(LuaError::RuntimeError(
|
|
||||||
"Invalid arguments to constructor".to_string(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
})?,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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> {
|
||||||
|_, (origin, direction): (LuaUserDataRef<Vector3>, LuaUserDataRef<Vector3>)| {
|
let ray_new =
|
||||||
Ok(Ray {
|
|_, (origin, direction): (LuaUserDataRef<Vector3>, LuaUserDataRef<Vector3>)| {
|
||||||
origin: origin.0,
|
Ok(Ray {
|
||||||
direction: direction.0,
|
origin: origin.0,
|
||||||
})
|
direction: direction.0,
|
||||||
},
|
})
|
||||||
)?,
|
};
|
||||||
)
|
|
||||||
|
TableBuilder::new(lua)?
|
||||||
|
.with_function("new", ray_new)?
|
||||||
|
.build_readonly()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,33 +30,37 @@ 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,
|
max.map(|m| *m).unwrap_or_default().0,
|
||||||
max.map(|m| *m).unwrap_or_default().0,
|
))
|
||||||
))
|
} else if let Ok((x0, y0, x1, y1)) = ArgsNums::from_lua_multi(args, lua) {
|
||||||
} else if let Ok((x0, y0, x1, y1)) = ArgsNums::from_lua_multi(args, lua) {
|
let min = Vec2::new(x0.unwrap_or_default(), y0.unwrap_or_default());
|
||||||
let min = Vec2::new(x0.unwrap_or_default(), y0.unwrap_or_default());
|
let max = Vec2::new(x1.unwrap_or_default(), y1.unwrap_or_default());
|
||||||
let max = Vec2::new(x1.unwrap_or_default(), y1.unwrap_or_default());
|
Ok(Rect::new(min, max))
|
||||||
Ok(Rect::new(min, max))
|
} else {
|
||||||
} else {
|
// FUTURE: Better error message here using given arg types
|
||||||
// FUTURE: Better error message here using given arg types
|
Err(LuaError::RuntimeError(
|
||||||
Err(LuaError::RuntimeError(
|
"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
Loading…
Add table
Reference in a new issue