mirror of
https://github.com/lune-org/lune.git
synced 2025-04-08 12:30:54 +01:00
Compare commits
84 commits
Author | SHA1 | Date | |
---|---|---|---|
|
27e3efca97 | ||
|
8bd1a9b77d | ||
|
bb8c4bce82 | ||
|
6902ecaa7c | ||
|
dc08b91314 | ||
|
822dd19393 | ||
6cd0234a5f | |||
|
19e7f57284 | ||
|
5d1401cdf6 | ||
|
91af86cca2 | ||
|
c935149c1e | ||
|
e5bda57665 | ||
|
ef294f207c | ||
|
f89d02a60d | ||
|
d090cd2420 | ||
|
99c17795c1 | ||
|
138221b93e | ||
|
8abfc21181 | ||
309c461e11 | |||
|
93fa14d832 | ||
df4fb9be91 | |||
eaac9ff53a | |||
|
0d2f5539b6 | ||
|
0f4cac29aa | ||
|
010cd36375 | ||
|
c17da72815 | ||
|
ff83c401b8 | ||
|
a007fa94a6 | ||
|
1d4d1635eb | ||
|
56f08a88aa | ||
|
833d0e244b | ||
|
3e09807638 | ||
|
ea7013322f | ||
|
98b31b9f67 | ||
|
8364a8e4de | ||
|
473ad80e8f | ||
|
180d20ce4a | ||
|
b585234b08 | ||
|
5379c79488 | ||
|
8aefe88104 | ||
|
cb552af660 | ||
|
95c2ca0965 | ||
|
5167a71e6f | ||
|
eac34d2e7e | ||
|
ff80981282 | ||
|
45493dc23b | ||
|
359f28133f | ||
|
c7cbda98fe | ||
|
a7ac864ca5 | ||
|
9993e03f04 | ||
|
997653eb4a | ||
|
f94fbc685a | ||
|
e2e8beb45c | ||
6b38a21454 | |||
|
430d5683f0 | ||
|
59a7955132 | ||
|
1fb1d3e7b5 | ||
|
0efc2c565b | ||
|
c94ab0cde1 | ||
|
d3b9a4b9e8 | ||
|
3cf2be51bc | ||
|
a3f0f279a8 | ||
|
a94c9d6d54 | ||
|
63493e78de | ||
|
8cb7b8a13a | ||
|
9d9f1685d8 | ||
|
91ac6b00c1 | ||
|
2a85532448 | ||
|
5a292aabc5 | ||
|
cf513c6724 | ||
|
b628601cc8 | ||
|
649bdc4c31 | ||
|
3030158159 | ||
|
23456ae041 | ||
|
4f6f1835d2 | ||
|
636d0bf277 | ||
|
adc74f47c0 | ||
|
1fd17ca0b3 | ||
|
9498620e03 | ||
|
0850f41617 | ||
|
f2c40a4bd5 | ||
|
bfb89dec01 | ||
|
395c36fa8b | ||
|
7e784ba361 |
145 changed files with 5894 additions and 1217 deletions
4
.gitattributes
vendored
4
.gitattributes
vendored
|
@ -1,9 +1,5 @@
|
||||||
* text=auto
|
* text=auto
|
||||||
|
|
||||||
# Temporarily highlight luau as normal lua files
|
|
||||||
# until we get native linguist support for Luau
|
|
||||||
*.luau linguist-language=Lua
|
|
||||||
|
|
||||||
# Ensure all lua files use LF
|
# Ensure all lua files use LF
|
||||||
*.lua eol=lf
|
*.lua eol=lf
|
||||||
*.luau eol=lf
|
*.luau eol=lf
|
||||||
|
|
13
.github/workflows/ci.yaml
vendored
13
.github/workflows/ci.yaml
vendored
|
@ -23,11 +23,8 @@ jobs:
|
||||||
with:
|
with:
|
||||||
components: rustfmt
|
components: rustfmt
|
||||||
|
|
||||||
- name: Install Just
|
|
||||||
uses: extractions/setup-just@v2
|
|
||||||
|
|
||||||
- name: Install Tooling
|
- name: Install Tooling
|
||||||
uses: ok-nick/setup-aftman@v0.4.2
|
uses: CompeyDev/setup-rokit@v0.1.2
|
||||||
|
|
||||||
- name: Check Formatting
|
- name: Check Formatting
|
||||||
run: just fmt-check
|
run: just fmt-check
|
||||||
|
@ -40,11 +37,8 @@ jobs:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install Just
|
|
||||||
uses: extractions/setup-just@v2
|
|
||||||
|
|
||||||
- name: Install Tooling
|
- name: Install Tooling
|
||||||
uses: ok-nick/setup-aftman@v0.4.2
|
uses: CompeyDev/setup-rokit@v0.1.2
|
||||||
|
|
||||||
- name: Analyze
|
- name: Analyze
|
||||||
run: just analyze
|
run: just analyze
|
||||||
|
@ -88,17 +82,20 @@ jobs:
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
cargo build \
|
cargo build \
|
||||||
|
--workspace \
|
||||||
--locked --all-features \
|
--locked --all-features \
|
||||||
--target ${{ matrix.cargo-target }}
|
--target ${{ matrix.cargo-target }}
|
||||||
|
|
||||||
- name: Lint
|
- name: Lint
|
||||||
run: |
|
run: |
|
||||||
cargo clippy \
|
cargo clippy \
|
||||||
|
--workspace \
|
||||||
--locked --all-features \
|
--locked --all-features \
|
||||||
--target ${{ matrix.cargo-target }}
|
--target ${{ matrix.cargo-target }}
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: |
|
run: |
|
||||||
cargo test \
|
cargo test \
|
||||||
|
--lib --workspace \
|
||||||
--locked --all-features \
|
--locked --all-features \
|
||||||
--target ${{ matrix.cargo-target }}
|
--target ${{ matrix.cargo-target }}
|
||||||
|
|
64
.github/workflows/release.yaml
vendored
64
.github/workflows/release.yaml
vendored
|
@ -27,26 +27,26 @@ jobs:
|
||||||
file: crates/lune/Cargo.toml
|
file: crates/lune/Cargo.toml
|
||||||
field: package.version
|
field: package.version
|
||||||
|
|
||||||
dry-run:
|
# dry-run:
|
||||||
name: Dry-run
|
# name: Dry-run
|
||||||
needs: ["init"]
|
# needs: ["init"]
|
||||||
runs-on: ubuntu-latest
|
# runs-on: ubuntu-latest
|
||||||
steps:
|
# steps:
|
||||||
- name: Checkout repository
|
# - name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
# uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install Rust
|
# - name: Install Rust
|
||||||
uses: dtolnay/rust-toolchain@stable
|
# uses: dtolnay/rust-toolchain@stable
|
||||||
|
|
||||||
- name: Publish (dry-run)
|
# - name: Publish (dry-run)
|
||||||
uses: katyo/publish-crates@v2
|
# uses: katyo/publish-crates@v2
|
||||||
with:
|
# with:
|
||||||
dry-run: true
|
# dry-run: true
|
||||||
check-repo: true
|
# check-repo: true
|
||||||
registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
# registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||||
|
|
||||||
build:
|
build:
|
||||||
needs: ["init", "dry-run"]
|
needs: ["init"] # , "dry-run"]
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
|
@ -112,7 +112,7 @@ jobs:
|
||||||
release-github:
|
release-github:
|
||||||
name: Release (GitHub)
|
name: Release (GitHub)
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: ["init", "dry-run", "build"]
|
needs: ["init", "build"] # , "dry-run", "build"]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
@ -139,20 +139,20 @@ jobs:
|
||||||
files: ./releases/*.zip
|
files: ./releases/*.zip
|
||||||
draft: true
|
draft: true
|
||||||
|
|
||||||
release-crates:
|
# release-crates:
|
||||||
name: Release (crates.io)
|
# name: Release (crates.io)
|
||||||
runs-on: ubuntu-latest
|
# runs-on: ubuntu-latest
|
||||||
needs: ["init", "dry-run", "build"]
|
# needs: ["init", "dry-run", "build"]
|
||||||
steps:
|
# steps:
|
||||||
- name: Checkout repository
|
# - name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
# uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install Rust
|
# - name: Install Rust
|
||||||
uses: dtolnay/rust-toolchain@stable
|
# uses: dtolnay/rust-toolchain@stable
|
||||||
|
|
||||||
- name: Publish crates
|
# - name: Publish crates
|
||||||
uses: katyo/publish-crates@v2
|
# uses: katyo/publish-crates@v2
|
||||||
with:
|
# with:
|
||||||
dry-run: false
|
# dry-run: false
|
||||||
check-repo: true
|
# check-repo: true
|
||||||
registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
# registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||||
|
|
|
@ -129,7 +129,7 @@ end
|
||||||
]]
|
]]
|
||||||
|
|
||||||
print("Sending 4 pings to google 🌏")
|
print("Sending 4 pings to google 🌏")
|
||||||
local result = process.spawn("ping", {
|
local result = process.exec("ping", {
|
||||||
"google.com",
|
"google.com",
|
||||||
"-c 4",
|
"-c 4",
|
||||||
})
|
})
|
||||||
|
|
|
@ -28,8 +28,8 @@ end)
|
||||||
|
|
||||||
for _ = 1, 5 do
|
for _ = 1, 5 do
|
||||||
local start = os.clock()
|
local start = os.clock()
|
||||||
socket.send(tostring(1))
|
socket:send(tostring(1))
|
||||||
local response = socket.next()
|
local response = socket:next()
|
||||||
local elapsed = os.clock() - start
|
local elapsed = os.clock() - start
|
||||||
print(`Got response '{response}' in {elapsed * 1_000} milliseconds`)
|
print(`Got response '{response}' in {elapsed * 1_000} milliseconds`)
|
||||||
task.wait(1 - elapsed)
|
task.wait(1 - elapsed)
|
||||||
|
@ -38,7 +38,7 @@ end
|
||||||
-- Everything went well, and we are done with the socket, so we can close it
|
-- Everything went well, and we are done with the socket, so we can close it
|
||||||
|
|
||||||
print("Closing web socket...")
|
print("Closing web socket...")
|
||||||
socket.close()
|
socket:close()
|
||||||
|
|
||||||
task.cancel(forceExit)
|
task.cancel(forceExit)
|
||||||
print("Done! 🌙")
|
print("Done! 🌙")
|
||||||
|
|
|
@ -15,9 +15,9 @@ local handle = net.serve(PORT, {
|
||||||
handleWebSocket = function(socket)
|
handleWebSocket = function(socket)
|
||||||
print("Got new web socket connection!")
|
print("Got new web socket connection!")
|
||||||
repeat
|
repeat
|
||||||
local message = socket.next()
|
local message = socket:next()
|
||||||
if message ~= nil then
|
if message ~= nil then
|
||||||
socket.send("Echo - " .. message)
|
socket:send("Echo - " .. message)
|
||||||
end
|
end
|
||||||
until message == nil
|
until message == nil
|
||||||
print("Web socket disconnected.")
|
print("Web socket disconnected.")
|
||||||
|
|
143
CHANGELOG.md
143
CHANGELOG.md
|
@ -8,10 +8,146 @@ 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).
|
||||||
|
|
||||||
## `0.8.4` - May 12th, 2024
|
## `0.9.0`
|
||||||
|
|
||||||
|
### Breaking changes
|
||||||
|
|
||||||
|
- Added two new process spawning functions - `process.create` and `process.exec`, removing the previous `process.spawn` API completely. ([#211])
|
||||||
|
|
||||||
|
To migrate from `process.spawn`, use the new `process.exec` API which retains the same behavior as the old function.
|
||||||
|
|
||||||
|
The new `process.create` function is a non-blocking process creation API and can be used to interactively
|
||||||
|
read and write stdio of the process.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local child = process.create("program", {
|
||||||
|
"cli-argument",
|
||||||
|
"other-cli-argument"
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Writing to stdin
|
||||||
|
child.stdin:write("Hello from Lune!")
|
||||||
|
|
||||||
|
-- Reading from stdout
|
||||||
|
local data = child.stdout:read()
|
||||||
|
print(buffer.tostring(data))
|
||||||
|
```
|
||||||
|
|
||||||
|
- WebSocket methods in `net.socket` and `net.serve` now use standard Lua method calling convention and colon syntax.
|
||||||
|
This means `socket.send(...)` is now `socket:send(...)`, `socket.close(...)` is now `socket:close(...)`, and so on.
|
||||||
|
|
||||||
|
- `Runtime::run` now returns a more useful value instead of an `ExitCode` ([#178])
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
- Documentation comments for several standard library properties have been improved ([#248], [#250])
|
||||||
|
- Error messages no longer contain redundant or duplicate stack trace information
|
||||||
|
|
||||||
|
[#178]: https://github.com/lune-org/lune/pull/178
|
||||||
|
[#211]: https://github.com/lune-org/lune/pull/211
|
||||||
|
[#248]: https://github.com/lune-org/lune/pull/248
|
||||||
|
[#250]: https://github.com/lune-org/lune/pull/250
|
||||||
|
|
||||||
|
## `0.8.9` - October 7th, 2024
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Updated to Luau version `0.640`
|
||||||
|
|
||||||
|
## `0.8.8` - August 22nd, 2024
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed errors when deserializing `Lighting.AttributesSerialize` by updating `rbx-dom` dependencies ([#245])
|
||||||
|
|
||||||
|
[#245]: https://github.com/lune-org/lune/pull/245
|
||||||
|
|
||||||
|
## `0.8.7` - August 10th, 2024
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added a compression level option to `serde.compress` ([#224])
|
||||||
|
- Added missing vector methods to the `roblox` library ([#228])
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Updated to Luau version `0.635`
|
||||||
|
- Updated to rbx-dom database version `0.634`
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed `fs.readDir` with trailing forward-slash on Windows ([#220])
|
||||||
|
- Fixed `__type` and `__tostring` metamethods not always being respected when formatting tables
|
||||||
|
|
||||||
|
[#220]: https://github.com/lune-org/lune/pull/220
|
||||||
|
[#224]: https://github.com/lune-org/lune/pull/224
|
||||||
|
[#228]: https://github.com/lune-org/lune/pull/228
|
||||||
|
|
||||||
|
## `0.8.6` - June 23rd, 2024
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added a builtin API for hashing and calculating HMACs as part of the `serde` library ([#193])
|
||||||
|
|
||||||
|
Basic usage:
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local serde = require("@lune/serde")
|
||||||
|
local hash = serde.hash("sha256", "a message to hash")
|
||||||
|
local hmac = serde.hmac("sha256", "a message to hash", "a secret string")
|
||||||
|
|
||||||
|
print(hash)
|
||||||
|
print(hmac)
|
||||||
|
```
|
||||||
|
|
||||||
|
The returned hashes are sequences of lowercase hexadecimal digits. The following algorithms are supported:
|
||||||
|
`md5`, `sha1`, `sha224`, `sha256`, `sha384`, `sha512`, `sha3-224`, `sha3-256`, `sha3-384`, `sha3-512`, `blake3`
|
||||||
|
|
||||||
|
- Added two new options to `luau.load`:
|
||||||
|
|
||||||
|
- `codegenEnabled` - whether or not codegen should be enabled for the loaded chunk.
|
||||||
|
- `injectGlobals` - whether or not to inject globals into a passed `environment`.
|
||||||
|
|
||||||
|
By default, globals are injected and codegen is disabled.
|
||||||
|
Check the documentation for the `luau` standard library for more information.
|
||||||
|
|
||||||
|
- Implemented support for floor division operator / `__idiv` for the `Vector2` and `Vector3` types in the `roblox` standard library ([#196])
|
||||||
|
- Fixed the `_VERSION` global containing an incorrect Lune version string.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Sandboxing and codegen in the Luau VM is now fully enabled, resulting in up to 2x or faster code execution.
|
||||||
|
This should not result in any behavior differences in Lune, but if it does, please open an issue.
|
||||||
|
- Improved formatting of custom error objects (such as when `fs.readFile` returns an error) when printed or formatted using `stdio.format`.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed `__type` and `__tostring` metamethods on userdatas and tables not being respected when printed or formatted using `stdio.format`.
|
||||||
|
|
||||||
|
[#193]: https://github.com/lune-org/lune/pull/193
|
||||||
|
[#196]: https://github.com/lune-org/lune/pull/196
|
||||||
|
|
||||||
|
## `0.8.5` - June 1st, 2024
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Improved table pretty formatting when using `print`, `warn`, and `stdio.format`:
|
||||||
|
|
||||||
|
- Keys are sorted numerically / alphabetically when possible.
|
||||||
|
- Keys of different types are put in distinct sections for mixed tables.
|
||||||
|
- Tables that are arrays no longer display their keys.
|
||||||
|
- Empty tables are no longer spread across lines.
|
||||||
|
|
||||||
|
## Fixed
|
||||||
|
|
||||||
|
- Fixed formatted values in tables not being separated by newlines.
|
||||||
|
- Fixed panicking (crashing) when using `process.spawn` with a program that does not exist.
|
||||||
|
- Fixed `instance:SetAttribute("name", nil)` throwing an error and not removing the attribute.
|
||||||
|
|
||||||
|
## `0.8.4` - May 12th, 2024
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
- Added a builtin API for regular expressions.
|
- Added a builtin API for regular expressions.
|
||||||
|
|
||||||
Example basic usage:
|
Example basic usage:
|
||||||
|
@ -51,6 +187,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
Currently supported targets are the same as the ones included with each
|
Currently supported targets are the same as the ones included with each
|
||||||
release of Lune on GitHub. Check releases for a full list of targets.
|
release of Lune on GitHub. Check releases for a full list of targets.
|
||||||
|
|
||||||
|
- Added `stdio.readToEnd()` for reading the entire stdin passed to Lune
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
- Split the repository into modular crates instead of a monolith. ([#188])
|
- Split the repository into modular crates instead of a monolith. ([#188])
|
||||||
|
|
||||||
If you previously depended on Lune as a crate, nothing about it has changed for version `0.8.4`, but now each individual sub-crate has also been published and is available for use:
|
If you previously depended on Lune as a crate, nothing about it has changed for version `0.8.4`, but now each individual sub-crate has also been published and is available for use:
|
||||||
|
@ -64,7 +204,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
In general, this should mean that it is now much easier to make your own Lune builtin, publish your own flavor of a Lune CLI, or take advantage of all the work that has been done for Lune as a runtime when making your own Rust programs.
|
In general, this should mean that it is now much easier to make your own Lune builtin, publish your own flavor of a Lune CLI, or take advantage of all the work that has been done for Lune as a runtime when making your own Rust programs.
|
||||||
|
|
||||||
- Added `stdio.readToEnd()` for reading the entire stdin passed to Lune
|
|
||||||
- Changed the `User-Agent` header in `net.request` to be more descriptive ([#186])
|
- Changed the `User-Agent` header in `net.request` to be more descriptive ([#186])
|
||||||
- Updated to Luau version `0.622`.
|
- Updated to Luau version `0.622`.
|
||||||
|
|
||||||
|
|
1360
Cargo.lock
generated
1360
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -16,6 +16,7 @@ members = [
|
||||||
"crates/lune-std-stdio",
|
"crates/lune-std-stdio",
|
||||||
"crates/lune-std-task",
|
"crates/lune-std-task",
|
||||||
"crates/lune-utils",
|
"crates/lune-utils",
|
||||||
|
"crates/mlua-luau-scheduler",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Profile for building the release binary, with the following options set:
|
# Profile for building the release binary, with the following options set:
|
||||||
|
|
|
@ -33,7 +33,7 @@ Lune provides fully asynchronous APIs wherever possible, and is built in Rust
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- 🌙 Strictly minimal but powerful interface that is easy to read and remember, just like Luau itself
|
- 🌙 Strictly minimal but powerful interface that is easy to read and remember, just like Luau itself
|
||||||
- 🧰 Fully featured APIs for the filesystem, networking, stdio, all included in the small (~5mb) executable
|
- 🧰 Fully featured APIs for the filesystem, networking, stdio, all included in the small (~5mb zipped) executable
|
||||||
- 📚 World-class documentation, on the web _or_ directly in your editor, no network connection necessary
|
- 📚 World-class documentation, on the web _or_ directly in your editor, no network connection necessary
|
||||||
- 🏡 Familiar runtime environment for Roblox developers, with an included 1-to-1 task scheduler port
|
- 🏡 Familiar runtime environment for Roblox developers, with an included 1-to-1 task scheduler port
|
||||||
- ✏️ Optional built-in library for manipulating Roblox place & model files, and their instances
|
- ✏️ Optional built-in library for manipulating Roblox place & model files, and their instances
|
||||||
|
|
|
@ -1,4 +0,0 @@
|
||||||
[tools]
|
|
||||||
luau-lsp = "JohnnyMorganz/luau-lsp@1.29.0"
|
|
||||||
selene = "Kampfkarren/selene@0.27.1"
|
|
||||||
stylua = "JohnnyMorganz/StyLua@0.20.0"
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "lune-roblox"
|
name = "lune-roblox"
|
||||||
version = "0.1.0"
|
version = "0.1.4"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
repository = "https://github.com/lune-org/lune"
|
repository = "https://github.com/lune-org/lune"
|
||||||
|
@ -13,17 +13,17 @@ path = "src/lib.rs"
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
mlua = { version = "0.9.7", features = ["luau"] }
|
mlua = { version = "0.9.9", features = ["luau"] }
|
||||||
|
|
||||||
glam = "0.27"
|
glam = "0.27"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
once_cell = "1.17"
|
once_cell = "1.17"
|
||||||
|
|
||||||
rbx_binary = "0.7.3"
|
rbx_binary = "1.0.0"
|
||||||
rbx_dom_weak = "2.6.0"
|
rbx_dom_weak = "3.0.0"
|
||||||
rbx_reflection = "4.4.0"
|
rbx_reflection = "5.0.0"
|
||||||
rbx_reflection_database = "0.2.9"
|
rbx_reflection_database = "1.0.0"
|
||||||
rbx_xml = "0.13.2"
|
rbx_xml = "1.0.0"
|
||||||
|
|
||||||
lune-utils = { version = "0.1.0", path = "../lune-utils" }
|
lune-utils = { version = "0.1.3", path = "../lune-utils" }
|
||||||
|
|
|
@ -47,6 +47,7 @@ pub fn ensure_valid_attribute_value(value: &DomValue) -> LuaResult<()> {
|
||||||
| DomType::CFrame
|
| DomType::CFrame
|
||||||
| DomType::Color3
|
| DomType::Color3
|
||||||
| DomType::ColorSequence
|
| DomType::ColorSequence
|
||||||
|
| DomType::EnumItem
|
||||||
| DomType::Float32
|
| DomType::Float32
|
||||||
| DomType::Float64
|
| DomType::Float64
|
||||||
| DomType::Font
|
| DomType::Font
|
||||||
|
|
|
@ -51,7 +51,7 @@ impl<'lua> DomValueToLua<'lua> for LuaValue<'lua> {
|
||||||
DomValue::Float32(n) => Ok(LuaValue::Number(*n as f64)),
|
DomValue::Float32(n) => Ok(LuaValue::Number(*n as f64)),
|
||||||
DomValue::String(s) => Ok(LuaValue::String(lua.create_string(s)?)),
|
DomValue::String(s) => Ok(LuaValue::String(lua.create_string(s)?)),
|
||||||
DomValue::BinaryString(s) => Ok(LuaValue::String(lua.create_string(s)?)),
|
DomValue::BinaryString(s) => Ok(LuaValue::String(lua.create_string(s)?)),
|
||||||
DomValue::Content(s) => Ok(LuaValue::String(
|
DomValue::ContentId(s) => Ok(LuaValue::String(
|
||||||
lua.create_string(AsRef::<str>::as_ref(s))?,
|
lua.create_string(AsRef::<str>::as_ref(s))?,
|
||||||
)),
|
)),
|
||||||
|
|
||||||
|
@ -104,8 +104,8 @@ impl<'lua> LuaToDomValue<'lua> for LuaValue<'lua> {
|
||||||
(LuaValue::String(s), DomType::BinaryString) => {
|
(LuaValue::String(s), DomType::BinaryString) => {
|
||||||
Ok(DomValue::BinaryString(s.as_ref().into()))
|
Ok(DomValue::BinaryString(s.as_ref().into()))
|
||||||
}
|
}
|
||||||
(LuaValue::String(s), DomType::Content) => {
|
(LuaValue::String(s), DomType::ContentId) => {
|
||||||
Ok(DomValue::Content(s.to_str()?.to_string().into()))
|
Ok(DomValue::ContentId(s.to_str()?.to_string().into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Some values are either optional or default and we
|
// NOTE: Some values are either optional or default and we
|
||||||
|
@ -200,6 +200,8 @@ impl<'lua> DomValueToLua<'lua> for LuaAnyUserData<'lua> {
|
||||||
DomValue::Color3(value) => dom_to_userdata!(lua, value => Color3),
|
DomValue::Color3(value) => dom_to_userdata!(lua, value => Color3),
|
||||||
DomValue::Color3uint8(value) => dom_to_userdata!(lua, value => Color3),
|
DomValue::Color3uint8(value) => dom_to_userdata!(lua, value => Color3),
|
||||||
DomValue::ColorSequence(value) => dom_to_userdata!(lua, value => ColorSequence),
|
DomValue::ColorSequence(value) => dom_to_userdata!(lua, value => ColorSequence),
|
||||||
|
DomValue::Content(value) => dom_to_userdata!(lua, value => Content),
|
||||||
|
DomValue::EnumItem(value) => dom_to_userdata!(lua, value => EnumItem),
|
||||||
DomValue::Faces(value) => dom_to_userdata!(lua, value => Faces),
|
DomValue::Faces(value) => dom_to_userdata!(lua, value => Faces),
|
||||||
DomValue::Font(value) => dom_to_userdata!(lua, value => Font),
|
DomValue::Font(value) => dom_to_userdata!(lua, value => Font),
|
||||||
DomValue::NumberRange(value) => dom_to_userdata!(lua, value => NumberRange),
|
DomValue::NumberRange(value) => dom_to_userdata!(lua, value => NumberRange),
|
||||||
|
@ -256,7 +258,8 @@ impl<'lua> LuaToDomValue<'lua> for LuaAnyUserData<'lua> {
|
||||||
DomType::Color3 => userdata_to_dom!(self as Color3 => dom::Color3),
|
DomType::Color3 => userdata_to_dom!(self as Color3 => dom::Color3),
|
||||||
DomType::Color3uint8 => userdata_to_dom!(self as Color3 => dom::Color3uint8),
|
DomType::Color3uint8 => userdata_to_dom!(self as Color3 => dom::Color3uint8),
|
||||||
DomType::ColorSequence => userdata_to_dom!(self as ColorSequence => dom::ColorSequence),
|
DomType::ColorSequence => userdata_to_dom!(self as ColorSequence => dom::ColorSequence),
|
||||||
DomType::Enum => userdata_to_dom!(self as EnumItem => dom::Enum),
|
DomType::Content => userdata_to_dom!(self as Content => dom::Content),
|
||||||
|
DomType::EnumItem => userdata_to_dom!(self as EnumItem => dom::EnumItem),
|
||||||
DomType::Faces => userdata_to_dom!(self as Faces => dom::Faces),
|
DomType::Faces => userdata_to_dom!(self as Faces => dom::Faces),
|
||||||
DomType::Font => userdata_to_dom!(self as Font => dom::Font),
|
DomType::Font => userdata_to_dom!(self as Font => dom::Font),
|
||||||
DomType::NumberRange => userdata_to_dom!(self as NumberRange => dom::NumberRange),
|
DomType::NumberRange => userdata_to_dom!(self as NumberRange => dom::NumberRange),
|
||||||
|
@ -314,7 +317,7 @@ impl<'lua> LuaToDomValue<'lua> for LuaAnyUserData<'lua> {
|
||||||
value if value.is::<CFrame>() => userdata_to_dom!(value as CFrame => dom::CFrame),
|
value if value.is::<CFrame>() => userdata_to_dom!(value as CFrame => dom::CFrame),
|
||||||
value if value.is::<Color3>() => userdata_to_dom!(value as Color3 => dom::Color3),
|
value if value.is::<Color3>() => userdata_to_dom!(value as Color3 => dom::Color3),
|
||||||
value if value.is::<ColorSequence>() => userdata_to_dom!(value as ColorSequence => dom::ColorSequence),
|
value if value.is::<ColorSequence>() => userdata_to_dom!(value as ColorSequence => dom::ColorSequence),
|
||||||
value if value.is::<Enum>() => userdata_to_dom!(value as EnumItem => dom::Enum),
|
value if value.is::<EnumItem>() => userdata_to_dom!(value as EnumItem => dom::EnumItem),
|
||||||
value if value.is::<Faces>() => userdata_to_dom!(value as Faces => dom::Faces),
|
value if value.is::<Faces>() => userdata_to_dom!(value as Faces => dom::Faces),
|
||||||
value if value.is::<Font>() => userdata_to_dom!(value as Font => dom::Font),
|
value if value.is::<Font>() => userdata_to_dom!(value as Font => dom::Font),
|
||||||
value if value.is::<Instance>() => userdata_to_dom!(value as Instance => dom::Ref),
|
value if value.is::<Instance>() => userdata_to_dom!(value as Instance => dom::Ref),
|
||||||
|
|
|
@ -19,7 +19,9 @@ impl DomValueExt for DomType {
|
||||||
Color3uint8 => "Color3uint8",
|
Color3uint8 => "Color3uint8",
|
||||||
ColorSequence => "ColorSequence",
|
ColorSequence => "ColorSequence",
|
||||||
Content => "Content",
|
Content => "Content",
|
||||||
|
ContentId => "ContentId",
|
||||||
Enum => "Enum",
|
Enum => "Enum",
|
||||||
|
EnumItem => "EnumItem",
|
||||||
Faces => "Faces",
|
Faces => "Faces",
|
||||||
Float32 => "Float32",
|
Float32 => "Float32",
|
||||||
Float64 => "Float64",
|
Float64 => "Float64",
|
||||||
|
|
120
crates/lune-roblox/src/datatypes/types/content.rs
Normal file
120
crates/lune-roblox/src/datatypes/types/content.rs
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
use core::fmt;
|
||||||
|
|
||||||
|
use mlua::prelude::*;
|
||||||
|
use rbx_dom_weak::types::{Content as DomContent, ContentType};
|
||||||
|
|
||||||
|
use lune_utils::TableBuilder;
|
||||||
|
|
||||||
|
use crate::{exports::LuaExportsTable, instance::Instance};
|
||||||
|
|
||||||
|
use super::{super::*, EnumItem};
|
||||||
|
|
||||||
|
/**
|
||||||
|
An implementation of the [Content](https://create.roblox.com/docs/reference/engine/datatypes/Content) Roblox datatype.
|
||||||
|
|
||||||
|
This implements all documented properties, methods & constructors of the Content type as of April 2025.
|
||||||
|
*/
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct Content(ContentType);
|
||||||
|
|
||||||
|
impl LuaExportsTable<'_> for Content {
|
||||||
|
const EXPORT_NAME: &'static str = "Content";
|
||||||
|
|
||||||
|
fn create_exports_table(lua: &'_ Lua) -> LuaResult<LuaTable<'_>> {
|
||||||
|
let from_uri = |_, uri: String| Ok(Self(ContentType::Uri(uri)));
|
||||||
|
|
||||||
|
let from_object = |_, obj: LuaUserDataRef<Instance>| {
|
||||||
|
let database = rbx_reflection_database::get();
|
||||||
|
let instance_descriptor = database
|
||||||
|
.classes
|
||||||
|
.get("Instance")
|
||||||
|
.expect("the reflection database should always have Instance in it");
|
||||||
|
let param_descriptor = database.classes.get(obj.get_class_name()).expect(
|
||||||
|
"you should not be able to construct an Instance that is not known to Lune",
|
||||||
|
);
|
||||||
|
if database.has_superclass(param_descriptor, instance_descriptor) {
|
||||||
|
Err(LuaError::runtime("the provided object is a descendant class of 'Instance', expected one that was only an 'Object'"))
|
||||||
|
} else {
|
||||||
|
Ok(Content(ContentType::Object(obj.dom_ref)))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TableBuilder::new(lua)?
|
||||||
|
.with_value("none", Content(ContentType::None))?
|
||||||
|
.with_function("fromUri", from_uri)?
|
||||||
|
.with_function("fromObject", from_object)?
|
||||||
|
.build_readonly()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LuaUserData for Content {
|
||||||
|
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
||||||
|
fields.add_field_method_get("SourceType", |_, this| {
|
||||||
|
let variant_name = match &this.0 {
|
||||||
|
ContentType::None => "None",
|
||||||
|
ContentType::Uri(_) => "Uri",
|
||||||
|
ContentType::Object(_) => "Object",
|
||||||
|
other => {
|
||||||
|
return Err(LuaError::runtime(format!(
|
||||||
|
"cannot get SourceType: unknown ContentType variant '{other:?}'"
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(EnumItem::from_enum_name_and_name(
|
||||||
|
"ContentSourceType",
|
||||||
|
variant_name,
|
||||||
|
))
|
||||||
|
});
|
||||||
|
fields.add_field_method_get("Uri", |_, this| {
|
||||||
|
if let ContentType::Uri(uri) = &this.0 {
|
||||||
|
Ok(Some(uri.to_owned()))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
fields.add_field_method_get("Object", |_, this| {
|
||||||
|
if let ContentType::Object(referent) = &this.0 {
|
||||||
|
Ok(Instance::new_opt(*referent))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||||
|
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
||||||
|
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Content {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
// Regardless of the actual content of the Content, Roblox just emits
|
||||||
|
// `Content` when casting it to a string. We do not do that.
|
||||||
|
write!(f, "Content(")?;
|
||||||
|
match &self.0 {
|
||||||
|
ContentType::None => write!(f, "None")?,
|
||||||
|
ContentType::Uri(uri) => write!(f, "Uri={uri}")?,
|
||||||
|
ContentType::Object(_) => write!(f, "Object")?,
|
||||||
|
other => write!(f, "UnknownType({other:?})")?,
|
||||||
|
}
|
||||||
|
write!(f, ")")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DomContent> for Content {
|
||||||
|
fn from(value: DomContent) -> Self {
|
||||||
|
Self(value.value().clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Content> for DomContent {
|
||||||
|
fn from(value: Content) -> Self {
|
||||||
|
match value.0 {
|
||||||
|
ContentType::None => Self::none(),
|
||||||
|
ContentType::Uri(uri) => Self::from_uri(uri),
|
||||||
|
ContentType::Object(referent) => Self::from_referent(referent),
|
||||||
|
other => unimplemented!("unknown variant of ContentType: {other:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
|
|
||||||
use mlua::prelude::*;
|
use mlua::prelude::*;
|
||||||
use rbx_dom_weak::types::Enum as DomEnum;
|
use rbx_dom_weak::types::EnumItem as DomEnumItem;
|
||||||
|
|
||||||
use super::{super::*, Enum};
|
use super::{super::*, Enum};
|
||||||
|
|
||||||
|
@ -100,8 +100,18 @@ impl PartialEq for EnumItem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<EnumItem> for DomEnum {
|
impl From<EnumItem> for DomEnumItem {
|
||||||
fn from(v: EnumItem) -> Self {
|
fn from(v: EnumItem) -> Self {
|
||||||
DomEnum::from_u32(v.value)
|
DomEnumItem {
|
||||||
|
ty: v.parent.desc.name.to_string(),
|
||||||
|
value: v.value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DomEnumItem> for EnumItem {
|
||||||
|
fn from(value: DomEnumItem) -> Self {
|
||||||
|
EnumItem::from_enum_name_and_value(value.ty, value.value)
|
||||||
|
.expect("cannot convert rbx_type::EnumItem with unknown type into EnumItem")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ mod cframe;
|
||||||
mod color3;
|
mod color3;
|
||||||
mod color_sequence;
|
mod color_sequence;
|
||||||
mod color_sequence_keypoint;
|
mod color_sequence_keypoint;
|
||||||
|
mod content;
|
||||||
mod r#enum;
|
mod r#enum;
|
||||||
mod r#enum_item;
|
mod r#enum_item;
|
||||||
mod r#enums;
|
mod r#enums;
|
||||||
|
@ -30,6 +31,7 @@ pub use cframe::CFrame;
|
||||||
pub use color3::Color3;
|
pub use color3::Color3;
|
||||||
pub use color_sequence::ColorSequence;
|
pub use color_sequence::ColorSequence;
|
||||||
pub use color_sequence_keypoint::ColorSequenceKeypoint;
|
pub use color_sequence_keypoint::ColorSequenceKeypoint;
|
||||||
|
pub use content::Content;
|
||||||
pub use faces::Faces;
|
pub use faces::Faces;
|
||||||
pub use font::Font;
|
pub use font::Font;
|
||||||
pub use number_range::NumberRange;
|
pub use number_range::NumberRange;
|
||||||
|
|
|
@ -52,6 +52,9 @@ impl LuaUserData for Vector2 {
|
||||||
|
|
||||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||||
// Methods
|
// Methods
|
||||||
|
methods.add_method("Angle", |_, this, rhs: LuaUserDataRef<Vector2>| {
|
||||||
|
Ok(this.0.angle_between(rhs.0))
|
||||||
|
});
|
||||||
methods.add_method("Cross", |_, this, rhs: LuaUserDataRef<Vector2>| {
|
methods.add_method("Cross", |_, this, rhs: LuaUserDataRef<Vector2>| {
|
||||||
let this_v3 = Vec3::new(this.0.x, this.0.y, 0f32);
|
let this_v3 = Vec3::new(this.0.x, this.0.y, 0f32);
|
||||||
let rhs_v3 = Vec3::new(rhs.0.x, rhs.0.y, 0f32);
|
let rhs_v3 = Vec3::new(rhs.0.x, rhs.0.y, 0f32);
|
||||||
|
@ -60,6 +63,14 @@ impl LuaUserData for Vector2 {
|
||||||
methods.add_method("Dot", |_, this, rhs: LuaUserDataRef<Vector2>| {
|
methods.add_method("Dot", |_, this, rhs: LuaUserDataRef<Vector2>| {
|
||||||
Ok(this.0.dot(rhs.0))
|
Ok(this.0.dot(rhs.0))
|
||||||
});
|
});
|
||||||
|
methods.add_method(
|
||||||
|
"FuzzyEq",
|
||||||
|
|_, this, (rhs, epsilon): (LuaUserDataRef<Vector2>, f32)| {
|
||||||
|
let eq_x = (rhs.0.x - this.0.x).abs() <= epsilon;
|
||||||
|
let eq_y = (rhs.0.y - this.0.y).abs() <= epsilon;
|
||||||
|
Ok(eq_x && eq_y)
|
||||||
|
},
|
||||||
|
);
|
||||||
methods.add_method(
|
methods.add_method(
|
||||||
"Lerp",
|
"Lerp",
|
||||||
|_, this, (rhs, alpha): (LuaUserDataRef<Vector2>, f32)| {
|
|_, this, (rhs, alpha): (LuaUserDataRef<Vector2>, f32)| {
|
||||||
|
@ -72,6 +83,10 @@ impl LuaUserData for Vector2 {
|
||||||
methods.add_method("Min", |_, this, rhs: LuaUserDataRef<Vector2>| {
|
methods.add_method("Min", |_, this, rhs: LuaUserDataRef<Vector2>| {
|
||||||
Ok(Vector2(this.0.min(rhs.0)))
|
Ok(Vector2(this.0.min(rhs.0)))
|
||||||
});
|
});
|
||||||
|
methods.add_method("Abs", |_, this, ()| Ok(Vector2(this.0.abs())));
|
||||||
|
methods.add_method("Ceil", |_, this, ()| Ok(Vector2(this.0.ceil())));
|
||||||
|
methods.add_method("Floor", |_, this, ()| Ok(Vector2(this.0.floor())));
|
||||||
|
methods.add_method("Sign", |_, this, ()| Ok(Vector2(this.0.signum())));
|
||||||
// Metamethods
|
// Metamethods
|
||||||
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
||||||
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
||||||
|
@ -80,6 +95,7 @@ impl LuaUserData for Vector2 {
|
||||||
methods.add_meta_method(LuaMetaMethod::Sub, userdata_impl_sub);
|
methods.add_meta_method(LuaMetaMethod::Sub, userdata_impl_sub);
|
||||||
methods.add_meta_method(LuaMetaMethod::Mul, userdata_impl_mul_f32);
|
methods.add_meta_method(LuaMetaMethod::Mul, userdata_impl_mul_f32);
|
||||||
methods.add_meta_method(LuaMetaMethod::Div, userdata_impl_div_f32);
|
methods.add_meta_method(LuaMetaMethod::Div, userdata_impl_div_f32);
|
||||||
|
methods.add_meta_method(LuaMetaMethod::IDiv, userdata_impl_idiv_f32);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,6 +154,20 @@ impl ops::Div<f32> for Vector2 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl IDiv for Vector2 {
|
||||||
|
type Output = Vector2;
|
||||||
|
fn idiv(self, rhs: Self) -> Self::Output {
|
||||||
|
Self((self.0 / rhs.0).floor())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IDiv<f32> for Vector2 {
|
||||||
|
type Output = Vector2;
|
||||||
|
fn idiv(self, rhs: f32) -> Self::Output {
|
||||||
|
Self((self.0 / rhs).floor())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<DomVector2> for Vector2 {
|
impl From<DomVector2> for Vector2 {
|
||||||
fn from(v: DomVector2) -> Self {
|
fn from(v: DomVector2) -> Self {
|
||||||
Vector2(Vec2 { x: v.x, y: v.y })
|
Vector2(Vec2 { x: v.x, y: v.y })
|
||||||
|
|
|
@ -133,6 +133,10 @@ impl LuaUserData for Vector3 {
|
||||||
methods.add_method("Min", |_, this, rhs: LuaUserDataRef<Vector3>| {
|
methods.add_method("Min", |_, this, rhs: LuaUserDataRef<Vector3>| {
|
||||||
Ok(Vector3(this.0.min(rhs.0)))
|
Ok(Vector3(this.0.min(rhs.0)))
|
||||||
});
|
});
|
||||||
|
methods.add_method("Abs", |_, this, ()| Ok(Vector3(this.0.abs())));
|
||||||
|
methods.add_method("Ceil", |_, this, ()| Ok(Vector3(this.0.ceil())));
|
||||||
|
methods.add_method("Floor", |_, this, ()| Ok(Vector3(this.0.floor())));
|
||||||
|
methods.add_method("Sign", |_, this, ()| Ok(Vector3(this.0.signum())));
|
||||||
// Metamethods
|
// Metamethods
|
||||||
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
||||||
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
||||||
|
@ -141,6 +145,7 @@ impl LuaUserData for Vector3 {
|
||||||
methods.add_meta_method(LuaMetaMethod::Sub, userdata_impl_sub);
|
methods.add_meta_method(LuaMetaMethod::Sub, userdata_impl_sub);
|
||||||
methods.add_meta_method(LuaMetaMethod::Mul, userdata_impl_mul_f32);
|
methods.add_meta_method(LuaMetaMethod::Mul, userdata_impl_mul_f32);
|
||||||
methods.add_meta_method(LuaMetaMethod::Div, userdata_impl_div_f32);
|
methods.add_meta_method(LuaMetaMethod::Div, userdata_impl_div_f32);
|
||||||
|
methods.add_meta_method(LuaMetaMethod::IDiv, userdata_impl_idiv_f32);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -199,6 +204,20 @@ impl ops::Div<f32> for Vector3 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl IDiv for Vector3 {
|
||||||
|
type Output = Vector3;
|
||||||
|
fn idiv(self, rhs: Self) -> Self::Output {
|
||||||
|
Self((self.0 / rhs.0).floor())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IDiv<f32> for Vector3 {
|
||||||
|
type Output = Vector3;
|
||||||
|
fn idiv(self, rhs: f32) -> Self::Output {
|
||||||
|
Self((self.0 / rhs).floor())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<DomVector3> for Vector3 {
|
impl From<DomVector3> for Vector3 {
|
||||||
fn from(v: DomVector3) -> Self {
|
fn from(v: DomVector3) -> Self {
|
||||||
Vector3(Vec3 {
|
Vector3(Vec3 {
|
||||||
|
|
|
@ -65,7 +65,7 @@ impl DocumentKind {
|
||||||
for child_ref in dom.root().children() {
|
for child_ref in dom.root().children() {
|
||||||
if let Some(child_inst) = dom.get_by_ref(*child_ref) {
|
if let Some(child_inst) = dom.get_by_ref(*child_ref) {
|
||||||
has_top_level_child = true;
|
has_top_level_child = true;
|
||||||
if class_is_a_service(&child_inst.class).unwrap_or(false) {
|
if class_is_a_service(child_inst.class).unwrap_or(false) {
|
||||||
has_top_level_service = true;
|
has_top_level_service = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use rbx_dom_weak::{
|
use rbx_dom_weak::{
|
||||||
types::{Ref as DomRef, VariantType as DomType},
|
types::{Ref as DomRef, VariantType as DomType},
|
||||||
Instance as DomInstance, WeakDom,
|
ustr, Instance as DomInstance, WeakDom,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::shared::instance::class_is_a;
|
use crate::shared::instance::class_is_a;
|
||||||
|
@ -18,8 +18,8 @@ pub fn postprocess_dom_for_model(dom: &mut WeakDom) {
|
||||||
remove_matching_prop(inst, DomType::UniqueId, "HistoryId");
|
remove_matching_prop(inst, DomType::UniqueId, "HistoryId");
|
||||||
// Similar story with ScriptGuid - this is used
|
// Similar story with ScriptGuid - this is used
|
||||||
// in the studio-only cloud script drafts feature
|
// in the studio-only cloud script drafts feature
|
||||||
if class_is_a(&inst.class, "LuaSourceContainer").unwrap_or(false) {
|
if class_is_a(inst.class, "LuaSourceContainer").unwrap_or(false) {
|
||||||
inst.properties.remove("ScriptGuid");
|
inst.properties.remove(&ustr("ScriptGuid"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,8 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_matching_prop(inst: &mut DomInstance, ty: DomType, name: &'static str) {
|
fn remove_matching_prop(inst: &mut DomInstance, ty: DomType, name: &'static str) {
|
||||||
if inst.properties.get(name).map_or(false, |u| u.ty() == ty) {
|
let name = &ustr(name);
|
||||||
|
if inst.properties.get(name).is_some_and(|u| u.ty() == ty) {
|
||||||
inst.properties.remove(name);
|
inst.properties.remove(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,7 +71,7 @@ pub fn add_methods<'lua, M: LuaUserDataMethods<'lua, Instance>>(m: &mut M) {
|
||||||
"FindFirstAncestorWhichIsA",
|
"FindFirstAncestorWhichIsA",
|
||||||
|lua, this, class_name: String| {
|
|lua, this, class_name: String| {
|
||||||
ensure_not_destroyed(this)?;
|
ensure_not_destroyed(this)?;
|
||||||
this.find_ancestor(|child| class_is_a(&child.class, &class_name).unwrap_or(false))
|
this.find_ancestor(|child| class_is_a(child.class, &class_name).unwrap_or(false))
|
||||||
.into_lua(lua)
|
.into_lua(lua)
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -104,7 +104,7 @@ pub fn add_methods<'lua, M: LuaUserDataMethods<'lua, Instance>>(m: &mut M) {
|
||||||
|lua, this, (class_name, recursive): (String, Option<bool>)| {
|
|lua, this, (class_name, recursive): (String, Option<bool>)| {
|
||||||
ensure_not_destroyed(this)?;
|
ensure_not_destroyed(this)?;
|
||||||
let predicate =
|
let predicate =
|
||||||
|child: &DomInstance| class_is_a(&child.class, &class_name).unwrap_or(false);
|
|child: &DomInstance| class_is_a(child.class, &class_name).unwrap_or(false);
|
||||||
if matches!(recursive, Some(true)) {
|
if matches!(recursive, Some(true)) {
|
||||||
this.find_descendant(predicate).into_lua(lua)
|
this.find_descendant(predicate).into_lua(lua)
|
||||||
} else {
|
} else {
|
||||||
|
@ -113,8 +113,7 @@ pub fn add_methods<'lua, M: LuaUserDataMethods<'lua, Instance>>(m: &mut M) {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
m.add_method("IsA", |_, this, class_name: String| {
|
m.add_method("IsA", |_, this, class_name: String| {
|
||||||
ensure_not_destroyed(this)?;
|
Ok(class_is_a(this.class_name, class_name).unwrap_or(false))
|
||||||
Ok(class_is_a(&this.class_name, class_name).unwrap_or(false))
|
|
||||||
});
|
});
|
||||||
m.add_method(
|
m.add_method(
|
||||||
"IsAncestorOf",
|
"IsAncestorOf",
|
||||||
|
@ -155,13 +154,18 @@ pub fn add_methods<'lua, M: LuaUserDataMethods<'lua, Instance>>(m: &mut M) {
|
||||||
|lua, this, (attribute_name, lua_value): (String, LuaValue)| {
|
|lua, this, (attribute_name, lua_value): (String, LuaValue)| {
|
||||||
ensure_not_destroyed(this)?;
|
ensure_not_destroyed(this)?;
|
||||||
ensure_valid_attribute_name(&attribute_name)?;
|
ensure_valid_attribute_name(&attribute_name)?;
|
||||||
match lua_value.lua_to_dom_value(lua, None) {
|
if lua_value.is_nil() || lua_value.is_null() {
|
||||||
Ok(dom_value) => {
|
this.remove_attribute(attribute_name);
|
||||||
ensure_valid_attribute_value(&dom_value)?;
|
Ok(())
|
||||||
this.set_attribute(attribute_name, dom_value);
|
} else {
|
||||||
Ok(())
|
match lua_value.lua_to_dom_value(lua, None) {
|
||||||
|
Ok(dom_value) => {
|
||||||
|
ensure_valid_attribute_value(&dom_value)?;
|
||||||
|
this.set_attribute(attribute_name, dom_value);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Err(e) => Err(e.into()),
|
||||||
}
|
}
|
||||||
Err(e) => Err(e.into()),
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -212,20 +216,21 @@ fn instance_property_get<'lua>(
|
||||||
this: &Instance,
|
this: &Instance,
|
||||||
prop_name: String,
|
prop_name: String,
|
||||||
) -> LuaResult<LuaValue<'lua>> {
|
) -> LuaResult<LuaValue<'lua>> {
|
||||||
ensure_not_destroyed(this)?;
|
|
||||||
|
|
||||||
match prop_name.as_str() {
|
match prop_name.as_str() {
|
||||||
"ClassName" => return this.get_class_name().into_lua(lua),
|
"ClassName" => return this.get_class_name().into_lua(lua),
|
||||||
"Name" => {
|
|
||||||
return this.get_name().into_lua(lua);
|
|
||||||
}
|
|
||||||
"Parent" => {
|
"Parent" => {
|
||||||
return this.get_parent().into_lua(lua);
|
return this.get_parent().into_lua(lua);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(info) = find_property_info(&this.class_name, &prop_name) {
|
ensure_not_destroyed(this)?;
|
||||||
|
|
||||||
|
if prop_name.as_str() == "Name" {
|
||||||
|
return this.get_name().into_lua(lua);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(info) = find_property_info(this.class_name, &prop_name) {
|
||||||
if let Some(prop) = this.get_property(&prop_name) {
|
if let Some(prop) = this.get_property(&prop_name) {
|
||||||
if let DomValue::Enum(enum_value) = prop {
|
if let DomValue::Enum(enum_value) = prop {
|
||||||
let enum_name = info.enum_name.ok_or_else(|| {
|
let enum_name = info.enum_name.ok_or_else(|| {
|
||||||
|
@ -270,7 +275,7 @@ fn instance_property_get<'lua>(
|
||||||
} else if let Some(inst) = this.find_child(|inst| inst.name == prop_name) {
|
} else if let Some(inst) = this.find_child(|inst| inst.name == prop_name) {
|
||||||
Ok(LuaValue::UserData(lua.create_userdata(inst)?))
|
Ok(LuaValue::UserData(lua.create_userdata(inst)?))
|
||||||
} else if let Some(getter) = InstanceRegistry::find_property_getter(lua, this, &prop_name) {
|
} else if let Some(getter) = InstanceRegistry::find_property_getter(lua, this, &prop_name) {
|
||||||
getter.call(this.clone())
|
getter.call(*this)
|
||||||
} else if let Some(method) = InstanceRegistry::find_method(lua, this, &prop_name) {
|
} else if let Some(method) = InstanceRegistry::find_method(lua, this, &prop_name) {
|
||||||
Ok(LuaValue::Function(method))
|
Ok(LuaValue::Function(method))
|
||||||
} else {
|
} else {
|
||||||
|
@ -316,17 +321,17 @@ fn instance_property_set<'lua>(
|
||||||
}
|
}
|
||||||
type Parent<'lua> = Option<LuaUserDataRef<'lua, Instance>>;
|
type Parent<'lua> = Option<LuaUserDataRef<'lua, Instance>>;
|
||||||
let parent = Parent::from_lua(prop_value, lua)?;
|
let parent = Parent::from_lua(prop_value, lua)?;
|
||||||
this.set_parent(parent.map(|p| p.clone()));
|
this.set_parent(parent.map(|p| *p));
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(info) = find_property_info(&this.class_name, &prop_name) {
|
if let Some(info) = find_property_info(this.class_name, &prop_name) {
|
||||||
if let Some(enum_name) = info.enum_name {
|
if let Some(enum_name) = info.enum_name {
|
||||||
match LuaUserDataRef::<EnumItem>::from_lua(prop_value, lua) {
|
match LuaUserDataRef::<EnumItem>::from_lua(prop_value, lua) {
|
||||||
Ok(given_enum) if given_enum.parent.desc.name == enum_name => {
|
Ok(given_enum) if given_enum.parent.desc.name == enum_name => {
|
||||||
this.set_property(prop_name, DomValue::Enum((*given_enum).clone().into()));
|
this.set_property(prop_name, DomValue::EnumItem((*given_enum).clone().into()));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Ok(given_enum) => Err(LuaError::RuntimeError(format!(
|
Ok(given_enum) => Err(LuaError::RuntimeError(format!(
|
||||||
|
@ -349,7 +354,7 @@ fn instance_property_set<'lua>(
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
} else if let Some(setter) = InstanceRegistry::find_property_setter(lua, this, &prop_name) {
|
} else if let Some(setter) = InstanceRegistry::find_property_setter(lua, this, &prop_name) {
|
||||||
setter.call((this.clone(), prop_value))
|
setter.call((*this, prop_value))
|
||||||
} else {
|
} else {
|
||||||
Err(LuaError::RuntimeError(format!(
|
Err(LuaError::RuntimeError(format!(
|
||||||
"{prop_name} is not a valid member of {this}",
|
"{prop_name} is not a valid member of {this}",
|
||||||
|
|
|
@ -26,7 +26,7 @@ pub fn add_methods<'lua, M: LuaUserDataMethods<'lua, Instance>>(m: &mut M) {
|
||||||
|
|
||||||
### See Also
|
### See Also
|
||||||
* [`Terrain`](https://create.roblox.com/docs/reference/engine/classes/Workspace#Terrain)
|
* [`Terrain`](https://create.roblox.com/docs/reference/engine/classes/Workspace#Terrain)
|
||||||
on the Roblox Developer Hub
|
on the Roblox Developer Hub
|
||||||
*/
|
*/
|
||||||
fn data_model_get_workspace(_: &Lua, this: &Instance) -> LuaResult<Instance> {
|
fn data_model_get_workspace(_: &Lua, this: &Instance) -> LuaResult<Instance> {
|
||||||
get_or_create_property_ref_instance(this, "Workspace", "Workspace")
|
get_or_create_property_ref_instance(this, "Workspace", "Workspace")
|
||||||
|
@ -37,7 +37,7 @@ fn data_model_get_workspace(_: &Lua, this: &Instance) -> LuaResult<Instance> {
|
||||||
|
|
||||||
### See Also
|
### See Also
|
||||||
* [`GetService`](https://create.roblox.com/docs/reference/engine/classes/ServiceProvider#GetService)
|
* [`GetService`](https://create.roblox.com/docs/reference/engine/classes/ServiceProvider#GetService)
|
||||||
on the Roblox Developer Hub
|
on the Roblox Developer Hub
|
||||||
*/
|
*/
|
||||||
fn data_model_get_service(_: &Lua, this: &Instance, service_name: String) -> LuaResult<Instance> {
|
fn data_model_get_service(_: &Lua, this: &Instance, service_name: String) -> LuaResult<Instance> {
|
||||||
if matches!(class_is_a_service(&service_name), None | Some(false)) {
|
if matches!(class_is_a_service(&service_name), None | Some(false)) {
|
||||||
|
@ -48,7 +48,7 @@ fn data_model_get_service(_: &Lua, this: &Instance, service_name: String) -> Lua
|
||||||
Ok(service)
|
Ok(service)
|
||||||
} else {
|
} else {
|
||||||
let service = Instance::new_orphaned(service_name);
|
let service = Instance::new_orphaned(service_name);
|
||||||
service.set_parent(Some(this.clone()));
|
service.set_parent(Some(*this));
|
||||||
Ok(service)
|
Ok(service)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,7 @@ fn data_model_get_service(_: &Lua, this: &Instance, service_name: String) -> Lua
|
||||||
|
|
||||||
### See Also
|
### See Also
|
||||||
* [`FindService`](https://create.roblox.com/docs/reference/engine/classes/ServiceProvider#FindService)
|
* [`FindService`](https://create.roblox.com/docs/reference/engine/classes/ServiceProvider#FindService)
|
||||||
on the Roblox Developer Hub
|
on the Roblox Developer Hub
|
||||||
*/
|
*/
|
||||||
fn data_model_find_service(
|
fn data_model_find_service(
|
||||||
_: &Lua,
|
_: &Lua,
|
||||||
|
|
|
@ -11,7 +11,7 @@ use mlua::prelude::*;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use rbx_dom_weak::{
|
use rbx_dom_weak::{
|
||||||
types::{Attributes as DomAttributes, Ref as DomRef, Variant as DomValue},
|
types::{Attributes as DomAttributes, Ref as DomRef, Variant as DomValue},
|
||||||
Instance as DomInstance, InstanceBuilder as DomInstanceBuilder, WeakDom,
|
ustr, Instance as DomInstance, InstanceBuilder as DomInstanceBuilder, Ustr, WeakDom,
|
||||||
};
|
};
|
||||||
|
|
||||||
use lune_utils::TableBuilder;
|
use lune_utils::TableBuilder;
|
||||||
|
@ -34,10 +34,10 @@ const PROPERTY_NAME_TAGS: &str = "Tags";
|
||||||
static INTERNAL_DOM: Lazy<Mutex<WeakDom>> =
|
static INTERNAL_DOM: Lazy<Mutex<WeakDom>> =
|
||||||
Lazy::new(|| Mutex::new(WeakDom::new(DomInstanceBuilder::new("ROOT"))));
|
Lazy::new(|| Mutex::new(WeakDom::new(DomInstanceBuilder::new("ROOT"))));
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct Instance {
|
pub struct Instance {
|
||||||
pub(crate) dom_ref: DomRef,
|
pub(crate) dom_ref: DomRef,
|
||||||
pub(crate) class_name: String,
|
pub(crate) class_name: Ustr,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Instance {
|
impl Instance {
|
||||||
|
@ -45,38 +45,26 @@ impl Instance {
|
||||||
Creates a new `Instance` from an existing dom object ref.
|
Creates a new `Instance` from an existing dom object ref.
|
||||||
|
|
||||||
Panics if the instance does not exist in the internal dom,
|
Panics if the instance does not exist in the internal dom,
|
||||||
or if the given dom object ref points to the dom root.
|
or if the given dom object ref points to the internal dom root.
|
||||||
|
|
||||||
**WARNING:** Creating a new instance requires locking the internal dom,
|
**WARNING:** Creating a new instance requires locking the internal dom,
|
||||||
any existing lock must first be released to prevent any deadlocking.
|
any existing lock must first be released to prevent any deadlocking.
|
||||||
*/
|
*/
|
||||||
pub(crate) fn new(dom_ref: DomRef) -> Self {
|
#[must_use]
|
||||||
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
pub fn new(dom_ref: DomRef) -> Self {
|
||||||
|
Self::new_opt(dom_ref).expect("Failed to find instance in document")
|
||||||
let instance = dom
|
|
||||||
.get_by_ref(dom_ref)
|
|
||||||
.expect("Failed to find instance in document");
|
|
||||||
|
|
||||||
assert!(
|
|
||||||
!(instance.referent() == dom.root_ref()),
|
|
||||||
"Instances can not be created from dom roots"
|
|
||||||
);
|
|
||||||
|
|
||||||
Self {
|
|
||||||
dom_ref,
|
|
||||||
class_name: instance.class.clone(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Creates a new `Instance` from a dom object ref, if the instance exists.
|
Creates a new `Instance` from a dom object ref, if the instance exists.
|
||||||
|
|
||||||
Panics if the given dom object ref points to the dom root.
|
Panics if the given dom object ref points to the internal dom root.
|
||||||
|
|
||||||
**WARNING:** Creating a new instance requires locking the internal dom,
|
**WARNING:** Creating a new instance requires locking the internal dom,
|
||||||
any existing lock must first be released to prevent any deadlocking.
|
any existing lock must first be released to prevent any deadlocking.
|
||||||
*/
|
*/
|
||||||
pub(crate) fn new_opt(dom_ref: DomRef) -> Option<Self> {
|
#[must_use]
|
||||||
|
pub fn new_opt(dom_ref: DomRef) -> Option<Self> {
|
||||||
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||||
|
|
||||||
if let Some(instance) = dom.get_by_ref(dom_ref) {
|
if let Some(instance) = dom.get_by_ref(dom_ref) {
|
||||||
|
@ -87,7 +75,7 @@ impl Instance {
|
||||||
|
|
||||||
Some(Self {
|
Some(Self {
|
||||||
dom_ref,
|
dom_ref,
|
||||||
class_name: instance.class.clone(),
|
class_name: instance.class,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -97,24 +85,25 @@ impl Instance {
|
||||||
/**
|
/**
|
||||||
Creates a new orphaned `Instance` with a given class name.
|
Creates a new orphaned `Instance` with a given class name.
|
||||||
|
|
||||||
An orphaned instance is an instance at the root of a weak dom.
|
An orphaned instance is an instance at the root of Lune's internal weak dom.
|
||||||
|
|
||||||
**WARNING:** Creating a new instance requires locking the internal dom,
|
**WARNING:** Creating a new instance requires locking the internal dom,
|
||||||
any existing lock must first be released to prevent any deadlocking.
|
any existing lock must first be released to prevent any deadlocking.
|
||||||
*/
|
*/
|
||||||
pub(crate) fn new_orphaned(class_name: impl AsRef<str>) -> Self {
|
#[must_use]
|
||||||
|
pub fn new_orphaned(class_name: impl AsRef<str>) -> Self {
|
||||||
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||||
|
|
||||||
let class_name = class_name.as_ref();
|
let class_name = class_name.as_ref();
|
||||||
|
|
||||||
let instance = DomInstanceBuilder::new(class_name.to_string());
|
let instance = DomInstanceBuilder::new(class_name);
|
||||||
|
|
||||||
let dom_root = dom.root_ref();
|
let dom_root = dom.root_ref();
|
||||||
let dom_ref = dom.insert(dom_root, instance);
|
let dom_ref = dom.insert(dom_root, instance);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
dom_ref,
|
dom_ref,
|
||||||
class_name: class_name.to_string(),
|
class_name: ustr(class_name),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,10 +111,11 @@ impl Instance {
|
||||||
Creates a new orphaned `Instance` by transferring
|
Creates a new orphaned `Instance` by transferring
|
||||||
it from an external weak dom to the internal one.
|
it from an external weak dom to the internal one.
|
||||||
|
|
||||||
An orphaned instance is an instance at the root of a weak dom.
|
An orphaned instance is an instance at the root of Lune's internal weak dom.
|
||||||
|
|
||||||
Panics if the given dom ref is the root dom ref of the external weak dom.
|
Panics if the given dom ref is the root dom ref of the external weak dom.
|
||||||
*/
|
*/
|
||||||
|
#[must_use]
|
||||||
pub fn from_external_dom(external_dom: &mut WeakDom, external_dom_ref: DomRef) -> Self {
|
pub fn from_external_dom(external_dom: &mut WeakDom, external_dom_ref: DomRef) -> Self {
|
||||||
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||||
let dom_root = dom.root_ref();
|
let dom_root = dom.root_ref();
|
||||||
|
@ -151,6 +141,12 @@ impl Instance {
|
||||||
cloned
|
cloned
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Clones multiple instances to an external weak dom.
|
||||||
|
|
||||||
|
This will place the instances as children of the
|
||||||
|
root of the weak dom, and return their referents.
|
||||||
|
*/
|
||||||
pub fn clone_multiple_into_external_dom(
|
pub fn clone_multiple_into_external_dom(
|
||||||
referents: &[DomRef],
|
referents: &[DomRef],
|
||||||
external_dom: &mut WeakDom,
|
external_dom: &mut WeakDom,
|
||||||
|
@ -174,7 +170,7 @@ impl Instance {
|
||||||
|
|
||||||
### See Also
|
### See Also
|
||||||
* [`Clone`](https://create.roblox.com/docs/reference/engine/classes/Instance#Clone)
|
* [`Clone`](https://create.roblox.com/docs/reference/engine/classes/Instance#Clone)
|
||||||
on the Roblox Developer Hub
|
on the Roblox Developer Hub
|
||||||
*/
|
*/
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn clone_instance(&self) -> Self {
|
pub fn clone_instance(&self) -> Self {
|
||||||
|
@ -198,7 +194,7 @@ impl Instance {
|
||||||
|
|
||||||
### See Also
|
### See Also
|
||||||
* [`Destroy`](https://create.roblox.com/docs/reference/engine/classes/Instance#Destroy)
|
* [`Destroy`](https://create.roblox.com/docs/reference/engine/classes/Instance#Destroy)
|
||||||
on the Roblox Developer Hub
|
on the Roblox Developer Hub
|
||||||
*/
|
*/
|
||||||
pub fn destroy(&mut self) -> bool {
|
pub fn destroy(&mut self) -> bool {
|
||||||
if self.is_destroyed() {
|
if self.is_destroyed() {
|
||||||
|
@ -225,7 +221,7 @@ impl Instance {
|
||||||
### See Also
|
### See Also
|
||||||
* [`Instance::Destroy`] for more info about what happens when an instance gets destroyed
|
* [`Instance::Destroy`] for more info about what happens when an instance gets destroyed
|
||||||
* [`ClearAllChildren`](https://create.roblox.com/docs/reference/engine/classes/Instance#ClearAllChildren)
|
* [`ClearAllChildren`](https://create.roblox.com/docs/reference/engine/classes/Instance#ClearAllChildren)
|
||||||
on the Roblox Developer Hub
|
on the Roblox Developer Hub
|
||||||
*/
|
*/
|
||||||
pub fn clear_all_children(&mut self) {
|
pub fn clear_all_children(&mut self) {
|
||||||
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||||
|
@ -245,10 +241,10 @@ impl Instance {
|
||||||
|
|
||||||
### See Also
|
### See Also
|
||||||
* [`IsA`](https://create.roblox.com/docs/reference/engine/classes/Instance#IsA)
|
* [`IsA`](https://create.roblox.com/docs/reference/engine/classes/Instance#IsA)
|
||||||
on the Roblox Developer Hub
|
on the Roblox Developer Hub
|
||||||
*/
|
*/
|
||||||
pub fn is_a(&self, class_name: impl AsRef<str>) -> bool {
|
pub fn is_a(&self, class_name: impl AsRef<str>) -> bool {
|
||||||
class_is_a(&self.class_name, class_name).unwrap_or(false)
|
class_is_a(self.class_name, class_name).unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -258,7 +254,7 @@ impl Instance {
|
||||||
|
|
||||||
### See Also
|
### See Also
|
||||||
* [`ClassName`](https://create.roblox.com/docs/reference/engine/classes/Instance#ClassName)
|
* [`ClassName`](https://create.roblox.com/docs/reference/engine/classes/Instance#ClassName)
|
||||||
on the Roblox Developer Hub
|
on the Roblox Developer Hub
|
||||||
*/
|
*/
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn get_class_name(&self) -> &str {
|
pub fn get_class_name(&self) -> &str {
|
||||||
|
@ -270,7 +266,7 @@ impl Instance {
|
||||||
|
|
||||||
### See Also
|
### See Also
|
||||||
* [`Name`](https://create.roblox.com/docs/reference/engine/classes/Instance#Name)
|
* [`Name`](https://create.roblox.com/docs/reference/engine/classes/Instance#Name)
|
||||||
on the Roblox Developer Hub
|
on the Roblox Developer Hub
|
||||||
*/
|
*/
|
||||||
pub fn get_name(&self) -> String {
|
pub fn get_name(&self) -> String {
|
||||||
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||||
|
@ -286,7 +282,7 @@ impl Instance {
|
||||||
|
|
||||||
### See Also
|
### See Also
|
||||||
* [`Name`](https://create.roblox.com/docs/reference/engine/classes/Instance#Name)
|
* [`Name`](https://create.roblox.com/docs/reference/engine/classes/Instance#Name)
|
||||||
on the Roblox Developer Hub
|
on the Roblox Developer Hub
|
||||||
*/
|
*/
|
||||||
pub fn set_name(&self, name: impl Into<String>) {
|
pub fn set_name(&self, name: impl Into<String>) {
|
||||||
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||||
|
@ -301,15 +297,12 @@ impl Instance {
|
||||||
|
|
||||||
### See Also
|
### See Also
|
||||||
* [`Parent`](https://create.roblox.com/docs/reference/engine/classes/Instance#Parent)
|
* [`Parent`](https://create.roblox.com/docs/reference/engine/classes/Instance#Parent)
|
||||||
on the Roblox Developer Hub
|
on the Roblox Developer Hub
|
||||||
*/
|
*/
|
||||||
pub fn get_parent(&self) -> Option<Instance> {
|
pub fn get_parent(&self) -> Option<Instance> {
|
||||||
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||||
|
|
||||||
let parent_ref = dom
|
let parent_ref = dom.get_by_ref(self.dom_ref)?.parent();
|
||||||
.get_by_ref(self.dom_ref)
|
|
||||||
.expect("Failed to find instance in document")
|
|
||||||
.parent();
|
|
||||||
|
|
||||||
if parent_ref == dom.root_ref() {
|
if parent_ref == dom.root_ref() {
|
||||||
None
|
None
|
||||||
|
@ -324,11 +317,11 @@ impl Instance {
|
||||||
|
|
||||||
If the provided parent is [`None`] the instance will become orphaned.
|
If the provided parent is [`None`] the instance will become orphaned.
|
||||||
|
|
||||||
An orphaned instance is an instance at the root of a weak dom.
|
An orphaned instance is an instance at the root of Lune's internal weak dom.
|
||||||
|
|
||||||
### See Also
|
### See Also
|
||||||
* [`Parent`](https://create.roblox.com/docs/reference/engine/classes/Instance#Parent)
|
* [`Parent`](https://create.roblox.com/docs/reference/engine/classes/Instance#Parent)
|
||||||
on the Roblox Developer Hub
|
on the Roblox Developer Hub
|
||||||
*/
|
*/
|
||||||
pub fn set_parent(&self, parent: Option<Instance>) {
|
pub fn set_parent(&self, parent: Option<Instance>) {
|
||||||
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||||
|
@ -348,7 +341,7 @@ impl Instance {
|
||||||
.get_by_ref(self.dom_ref)
|
.get_by_ref(self.dom_ref)
|
||||||
.expect("Failed to find instance in document")
|
.expect("Failed to find instance in document")
|
||||||
.properties
|
.properties
|
||||||
.get(name.as_ref())
|
.get(&ustr(name.as_ref()))
|
||||||
.cloned()
|
.cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -365,7 +358,7 @@ impl Instance {
|
||||||
.get_by_ref_mut(self.dom_ref)
|
.get_by_ref_mut(self.dom_ref)
|
||||||
.expect("Failed to find instance in document")
|
.expect("Failed to find instance in document")
|
||||||
.properties
|
.properties
|
||||||
.insert(name.as_ref().to_string(), value);
|
.insert(ustr(name.as_ref()), value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -373,7 +366,7 @@ impl Instance {
|
||||||
|
|
||||||
### See Also
|
### See Also
|
||||||
* [`GetAttribute`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetAttribute)
|
* [`GetAttribute`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetAttribute)
|
||||||
on the Roblox Developer Hub
|
on the Roblox Developer Hub
|
||||||
*/
|
*/
|
||||||
pub fn get_attribute(&self, name: impl AsRef<str>) -> Option<DomValue> {
|
pub fn get_attribute(&self, name: impl AsRef<str>) -> Option<DomValue> {
|
||||||
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||||
|
@ -381,7 +374,7 @@ impl Instance {
|
||||||
.get_by_ref(self.dom_ref)
|
.get_by_ref(self.dom_ref)
|
||||||
.expect("Failed to find instance in document");
|
.expect("Failed to find instance in document");
|
||||||
if let Some(DomValue::Attributes(attributes)) =
|
if let Some(DomValue::Attributes(attributes)) =
|
||||||
inst.properties.get(PROPERTY_NAME_ATTRIBUTES)
|
inst.properties.get(&ustr(PROPERTY_NAME_ATTRIBUTES))
|
||||||
{
|
{
|
||||||
attributes.get(name.as_ref()).cloned()
|
attributes.get(name.as_ref()).cloned()
|
||||||
} else {
|
} else {
|
||||||
|
@ -394,7 +387,7 @@ impl Instance {
|
||||||
|
|
||||||
### See Also
|
### See Also
|
||||||
* [`GetAttributes`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetAttributes)
|
* [`GetAttributes`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetAttributes)
|
||||||
on the Roblox Developer Hub
|
on the Roblox Developer Hub
|
||||||
*/
|
*/
|
||||||
pub fn get_attributes(&self) -> BTreeMap<String, DomValue> {
|
pub fn get_attributes(&self) -> BTreeMap<String, DomValue> {
|
||||||
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||||
|
@ -402,7 +395,7 @@ impl Instance {
|
||||||
.get_by_ref(self.dom_ref)
|
.get_by_ref(self.dom_ref)
|
||||||
.expect("Failed to find instance in document");
|
.expect("Failed to find instance in document");
|
||||||
if let Some(DomValue::Attributes(attributes)) =
|
if let Some(DomValue::Attributes(attributes)) =
|
||||||
inst.properties.get(PROPERTY_NAME_ATTRIBUTES)
|
inst.properties.get(&ustr(PROPERTY_NAME_ATTRIBUTES))
|
||||||
{
|
{
|
||||||
attributes.clone().into_iter().collect()
|
attributes.clone().into_iter().collect()
|
||||||
} else {
|
} else {
|
||||||
|
@ -415,7 +408,7 @@ impl Instance {
|
||||||
|
|
||||||
### See Also
|
### See Also
|
||||||
* [`SetAttribute`](https://create.roblox.com/docs/reference/engine/classes/Instance#SetAttribute)
|
* [`SetAttribute`](https://create.roblox.com/docs/reference/engine/classes/Instance#SetAttribute)
|
||||||
on the Roblox Developer Hub
|
on the Roblox Developer Hub
|
||||||
*/
|
*/
|
||||||
pub fn set_attribute(&self, name: impl AsRef<str>, value: DomValue) {
|
pub fn set_attribute(&self, name: impl AsRef<str>, value: DomValue) {
|
||||||
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||||
|
@ -429,36 +422,59 @@ impl Instance {
|
||||||
value => value,
|
value => value,
|
||||||
};
|
};
|
||||||
if let Some(DomValue::Attributes(attributes)) =
|
if let Some(DomValue::Attributes(attributes)) =
|
||||||
inst.properties.get_mut(PROPERTY_NAME_ATTRIBUTES)
|
inst.properties.get_mut(&ustr(PROPERTY_NAME_ATTRIBUTES))
|
||||||
{
|
{
|
||||||
attributes.insert(name.as_ref().to_string(), value);
|
attributes.insert(name.as_ref().to_string(), value);
|
||||||
} else {
|
} else {
|
||||||
let mut attributes = DomAttributes::new();
|
let mut attributes = DomAttributes::new();
|
||||||
attributes.insert(name.as_ref().to_string(), value);
|
attributes.insert(name.as_ref().to_string(), value);
|
||||||
inst.properties.insert(
|
inst.properties.insert(
|
||||||
PROPERTY_NAME_ATTRIBUTES.to_string(),
|
ustr(PROPERTY_NAME_ATTRIBUTES),
|
||||||
DomValue::Attributes(attributes),
|
DomValue::Attributes(attributes),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Removes an attribute from the instance.
|
||||||
|
|
||||||
|
Note that this does not have an equivalent in the Roblox engine API,
|
||||||
|
but separating this from `set_attribute` lets `set_attribute` be more
|
||||||
|
ergonomic and not require an `Option<DomValue>` for the value argument.
|
||||||
|
The equivalent in the Roblox engine API would be `instance:SetAttribute(name, nil)`.
|
||||||
|
*/
|
||||||
|
pub fn remove_attribute(&self, name: impl AsRef<str>) {
|
||||||
|
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||||
|
let inst = dom
|
||||||
|
.get_by_ref_mut(self.dom_ref)
|
||||||
|
.expect("Failed to find instance in document");
|
||||||
|
if let Some(DomValue::Attributes(attributes)) =
|
||||||
|
inst.properties.get_mut(&ustr(PROPERTY_NAME_ATTRIBUTES))
|
||||||
|
{
|
||||||
|
attributes.remove(name.as_ref());
|
||||||
|
if attributes.is_empty() {
|
||||||
|
inst.properties.remove(&ustr(PROPERTY_NAME_ATTRIBUTES));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Adds a tag to the instance.
|
Adds a tag to the instance.
|
||||||
|
|
||||||
### See Also
|
### See Also
|
||||||
* [`AddTag`](https://create.roblox.com/docs/reference/engine/classes/CollectionService#AddTag)
|
* [`AddTag`](https://create.roblox.com/docs/reference/engine/classes/CollectionService#AddTag)
|
||||||
on the Roblox Developer Hub
|
on the Roblox Developer Hub
|
||||||
*/
|
*/
|
||||||
pub fn add_tag(&self, name: impl AsRef<str>) {
|
pub fn add_tag(&self, name: impl AsRef<str>) {
|
||||||
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||||
let inst = dom
|
let inst = dom
|
||||||
.get_by_ref_mut(self.dom_ref)
|
.get_by_ref_mut(self.dom_ref)
|
||||||
.expect("Failed to find instance in document");
|
.expect("Failed to find instance in document");
|
||||||
if let Some(DomValue::Tags(tags)) = inst.properties.get_mut(PROPERTY_NAME_TAGS) {
|
if let Some(DomValue::Tags(tags)) = inst.properties.get_mut(&ustr(PROPERTY_NAME_TAGS)) {
|
||||||
tags.push(name.as_ref());
|
tags.push(name.as_ref());
|
||||||
} else {
|
} else {
|
||||||
inst.properties.insert(
|
inst.properties.insert(
|
||||||
PROPERTY_NAME_TAGS.to_string(),
|
ustr(PROPERTY_NAME_TAGS),
|
||||||
DomValue::Tags(vec![name.as_ref().to_string()].into()),
|
DomValue::Tags(vec![name.as_ref().to_string()].into()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -469,14 +485,14 @@ impl Instance {
|
||||||
|
|
||||||
### See Also
|
### See Also
|
||||||
* [`GetTags`](https://create.roblox.com/docs/reference/engine/classes/CollectionService#GetTags)
|
* [`GetTags`](https://create.roblox.com/docs/reference/engine/classes/CollectionService#GetTags)
|
||||||
on the Roblox Developer Hub
|
on the Roblox Developer Hub
|
||||||
*/
|
*/
|
||||||
pub fn get_tags(&self) -> Vec<String> {
|
pub fn get_tags(&self) -> Vec<String> {
|
||||||
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||||
let inst = dom
|
let inst = dom
|
||||||
.get_by_ref(self.dom_ref)
|
.get_by_ref(self.dom_ref)
|
||||||
.expect("Failed to find instance in document");
|
.expect("Failed to find instance in document");
|
||||||
if let Some(DomValue::Tags(tags)) = inst.properties.get(PROPERTY_NAME_TAGS) {
|
if let Some(DomValue::Tags(tags)) = inst.properties.get(&ustr(PROPERTY_NAME_TAGS)) {
|
||||||
tags.iter().map(ToString::to_string).collect()
|
tags.iter().map(ToString::to_string).collect()
|
||||||
} else {
|
} else {
|
||||||
Vec::new()
|
Vec::new()
|
||||||
|
@ -488,14 +504,14 @@ impl Instance {
|
||||||
|
|
||||||
### See Also
|
### See Also
|
||||||
* [`HasTag`](https://create.roblox.com/docs/reference/engine/classes/CollectionService#HasTag)
|
* [`HasTag`](https://create.roblox.com/docs/reference/engine/classes/CollectionService#HasTag)
|
||||||
on the Roblox Developer Hub
|
on the Roblox Developer Hub
|
||||||
*/
|
*/
|
||||||
pub fn has_tag(&self, name: impl AsRef<str>) -> bool {
|
pub fn has_tag(&self, name: impl AsRef<str>) -> bool {
|
||||||
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||||
let inst = dom
|
let inst = dom
|
||||||
.get_by_ref(self.dom_ref)
|
.get_by_ref(self.dom_ref)
|
||||||
.expect("Failed to find instance in document");
|
.expect("Failed to find instance in document");
|
||||||
if let Some(DomValue::Tags(tags)) = inst.properties.get(PROPERTY_NAME_TAGS) {
|
if let Some(DomValue::Tags(tags)) = inst.properties.get(&ustr(PROPERTY_NAME_TAGS)) {
|
||||||
let name = name.as_ref();
|
let name = name.as_ref();
|
||||||
tags.iter().any(|tag| tag == name)
|
tags.iter().any(|tag| tag == name)
|
||||||
} else {
|
} else {
|
||||||
|
@ -508,21 +524,19 @@ impl Instance {
|
||||||
|
|
||||||
### See Also
|
### See Also
|
||||||
* [`RemoveTag`](https://create.roblox.com/docs/reference/engine/classes/CollectionService#RemoveTag)
|
* [`RemoveTag`](https://create.roblox.com/docs/reference/engine/classes/CollectionService#RemoveTag)
|
||||||
on the Roblox Developer Hub
|
on the Roblox Developer Hub
|
||||||
*/
|
*/
|
||||||
pub fn remove_tag(&self, name: impl AsRef<str>) {
|
pub fn remove_tag(&self, name: impl AsRef<str>) {
|
||||||
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||||
let inst = dom
|
let inst = dom
|
||||||
.get_by_ref_mut(self.dom_ref)
|
.get_by_ref_mut(self.dom_ref)
|
||||||
.expect("Failed to find instance in document");
|
.expect("Failed to find instance in document");
|
||||||
if let Some(DomValue::Tags(tags)) = inst.properties.get_mut(PROPERTY_NAME_TAGS) {
|
if let Some(DomValue::Tags(tags)) = inst.properties.get_mut(&ustr(PROPERTY_NAME_TAGS)) {
|
||||||
let name = name.as_ref();
|
let name = name.as_ref();
|
||||||
let mut new_tags = tags.iter().map(ToString::to_string).collect::<Vec<_>>();
|
let mut new_tags = tags.iter().map(ToString::to_string).collect::<Vec<_>>();
|
||||||
new_tags.retain(|tag| tag != name);
|
new_tags.retain(|tag| tag != name);
|
||||||
inst.properties.insert(
|
inst.properties
|
||||||
PROPERTY_NAME_TAGS.to_string(),
|
.insert(ustr(PROPERTY_NAME_TAGS), DomValue::Tags(new_tags.into()));
|
||||||
DomValue::Tags(new_tags.into()),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -534,7 +548,7 @@ impl Instance {
|
||||||
|
|
||||||
### See Also
|
### See Also
|
||||||
* [`GetChildren`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetChildren)
|
* [`GetChildren`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetChildren)
|
||||||
on the Roblox Developer Hub
|
on the Roblox Developer Hub
|
||||||
*/
|
*/
|
||||||
pub fn get_children(&self) -> Vec<Instance> {
|
pub fn get_children(&self) -> Vec<Instance> {
|
||||||
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||||
|
@ -557,7 +571,7 @@ impl Instance {
|
||||||
|
|
||||||
### See Also
|
### See Also
|
||||||
* [`GetDescendants`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetDescendants)
|
* [`GetDescendants`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetDescendants)
|
||||||
on the Roblox Developer Hub
|
on the Roblox Developer Hub
|
||||||
*/
|
*/
|
||||||
pub fn get_descendants(&self) -> Vec<Instance> {
|
pub fn get_descendants(&self) -> Vec<Instance> {
|
||||||
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||||
|
@ -591,7 +605,7 @@ impl Instance {
|
||||||
|
|
||||||
### See Also
|
### See Also
|
||||||
* [`GetFullName`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetFullName)
|
* [`GetFullName`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetFullName)
|
||||||
on the Roblox Developer Hub
|
on the Roblox Developer Hub
|
||||||
*/
|
*/
|
||||||
pub fn get_full_name(&self) -> String {
|
pub fn get_full_name(&self) -> String {
|
||||||
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||||
|
@ -681,7 +695,7 @@ impl Instance {
|
||||||
|
|
||||||
### See Also
|
### See Also
|
||||||
* [`FindFirstDescendant`](https://create.roblox.com/docs/reference/engine/classes/Instance#FindFirstDescendant)
|
* [`FindFirstDescendant`](https://create.roblox.com/docs/reference/engine/classes/Instance#FindFirstDescendant)
|
||||||
on the Roblox Developer Hub
|
on the Roblox Developer Hub
|
||||||
*/
|
*/
|
||||||
pub fn find_descendant<F>(&self, predicate: F) -> Option<Instance>
|
pub fn find_descendant<F>(&self, predicate: F) -> Option<Instance>
|
||||||
where
|
where
|
||||||
|
|
|
@ -27,9 +27,8 @@ pub fn add_methods<'lua, M: LuaUserDataMethods<'lua, Instance>>(methods: &mut M)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_or_create_material_colors(instance: &Instance) -> MaterialColors {
|
fn get_or_create_material_colors(instance: &Instance) -> MaterialColors {
|
||||||
if let Some(Variant::MaterialColors(material_colors)) = instance.get_property("MaterialColors")
|
if let Some(Variant::MaterialColors(inner)) = instance.get_property("MaterialColors") {
|
||||||
{
|
inner
|
||||||
material_colors
|
|
||||||
} else {
|
} else {
|
||||||
MaterialColors::default()
|
MaterialColors::default()
|
||||||
}
|
}
|
||||||
|
@ -40,7 +39,7 @@ fn get_or_create_material_colors(instance: &Instance) -> MaterialColors {
|
||||||
|
|
||||||
### See Also
|
### See Also
|
||||||
* [`GetMaterialColor`](https://create.roblox.com/docs/reference/engine/classes/Terrain#GetMaterialColor)
|
* [`GetMaterialColor`](https://create.roblox.com/docs/reference/engine/classes/Terrain#GetMaterialColor)
|
||||||
on the Roblox Developer Hub
|
on the Roblox Developer Hub
|
||||||
*/
|
*/
|
||||||
fn terrain_get_material_color(_: &Lua, this: &Instance, material: EnumItem) -> LuaResult<Color3> {
|
fn terrain_get_material_color(_: &Lua, this: &Instance, material: EnumItem) -> LuaResult<Color3> {
|
||||||
let material_colors = get_or_create_material_colors(this);
|
let material_colors = get_or_create_material_colors(this);
|
||||||
|
@ -65,7 +64,7 @@ fn terrain_get_material_color(_: &Lua, this: &Instance, material: EnumItem) -> L
|
||||||
|
|
||||||
### See Also
|
### See Also
|
||||||
* [`SetMaterialColor`](https://create.roblox.com/docs/reference/engine/classes/Terrain#SetMaterialColor)
|
* [`SetMaterialColor`](https://create.roblox.com/docs/reference/engine/classes/Terrain#SetMaterialColor)
|
||||||
on the Roblox Developer Hub
|
on the Roblox Developer Hub
|
||||||
*/
|
*/
|
||||||
fn terrain_set_material_color(
|
fn terrain_set_material_color(
|
||||||
_: &Lua,
|
_: &Lua,
|
||||||
|
|
|
@ -16,7 +16,7 @@ pub fn add_fields<'lua, F: LuaUserDataFields<'lua, Instance>>(f: &mut F) {
|
||||||
|
|
||||||
### See Also
|
### See Also
|
||||||
* [`Terrain`](https://create.roblox.com/docs/reference/engine/classes/Workspace#Terrain)
|
* [`Terrain`](https://create.roblox.com/docs/reference/engine/classes/Workspace#Terrain)
|
||||||
on the Roblox Developer Hub
|
on the Roblox Developer Hub
|
||||||
*/
|
*/
|
||||||
fn workspace_get_terrain(_: &Lua, this: &Instance) -> LuaResult<Instance> {
|
fn workspace_get_terrain(_: &Lua, this: &Instance) -> LuaResult<Instance> {
|
||||||
get_or_create_property_ref_instance(this, "Terrain", "Terrain")
|
get_or_create_property_ref_instance(this, "Terrain", "Terrain")
|
||||||
|
@ -27,7 +27,7 @@ fn workspace_get_terrain(_: &Lua, this: &Instance) -> LuaResult<Instance> {
|
||||||
|
|
||||||
### See Also
|
### See Also
|
||||||
* [`CurrentCamera`](https://create.roblox.com/docs/reference/engine/classes/Workspace#CurrentCamera)
|
* [`CurrentCamera`](https://create.roblox.com/docs/reference/engine/classes/Workspace#CurrentCamera)
|
||||||
on the Roblox Developer Hub
|
on the Roblox Developer Hub
|
||||||
*/
|
*/
|
||||||
fn workspace_get_camera(_: &Lua, this: &Instance) -> LuaResult<Instance> {
|
fn workspace_get_camera(_: &Lua, this: &Instance) -> LuaResult<Instance> {
|
||||||
get_or_create_property_ref_instance(this, "CurrentCamera", "Camera")
|
get_or_create_property_ref_instance(this, "CurrentCamera", "Camera")
|
||||||
|
|
|
@ -25,6 +25,7 @@ fn create_all_exports(lua: &Lua) -> LuaResult<Vec<(&'static str, LuaValue)>> {
|
||||||
export::<Color3>(lua)?,
|
export::<Color3>(lua)?,
|
||||||
export::<ColorSequence>(lua)?,
|
export::<ColorSequence>(lua)?,
|
||||||
export::<ColorSequenceKeypoint>(lua)?,
|
export::<ColorSequenceKeypoint>(lua)?,
|
||||||
|
export::<Content>(lua)?,
|
||||||
export::<Faces>(lua)?,
|
export::<Faces>(lua)?,
|
||||||
export::<Font>(lua)?,
|
export::<Font>(lua)?,
|
||||||
export::<NumberRange>(lua)?,
|
export::<NumberRange>(lua)?,
|
||||||
|
|
|
@ -122,7 +122,7 @@ pub(crate) fn get_or_create_property_ref_instance(
|
||||||
Ok(inst)
|
Ok(inst)
|
||||||
} else {
|
} else {
|
||||||
let inst = Instance::new_orphaned(class_name);
|
let inst = Instance::new_orphaned(class_name);
|
||||||
inst.set_parent(Some(this.clone()));
|
inst.set_parent(Some(*this));
|
||||||
this.set_property(prop_name, DomValue::Ref(inst.dom_ref));
|
this.set_property(prop_name, DomValue::Ref(inst.dom_ref));
|
||||||
Ok(inst)
|
Ok(inst)
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ pub fn make_list_writer() -> Box<ListWriter> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
Userdata metamethod implementations
|
Userdata metamethod implementations
|
||||||
|
|
||||||
Note that many of these return [`LuaResult`] even though they don't
|
Note that many of these return [`LuaResult`] even though they don't
|
||||||
|
@ -149,6 +149,37 @@ where
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait IDiv<Rhs = Self> {
|
||||||
|
type Output;
|
||||||
|
#[must_use]
|
||||||
|
fn idiv(self, rhs: Rhs) -> Self::Output;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn userdata_impl_idiv_f32<D>(_: &Lua, datatype: &D, rhs: LuaValue) -> LuaResult<D>
|
||||||
|
where
|
||||||
|
D: LuaUserData + IDiv<D, Output = D> + IDiv<f32, Output = D> + Copy + 'static,
|
||||||
|
{
|
||||||
|
match &rhs {
|
||||||
|
LuaValue::Number(n) => return Ok(datatype.idiv(*n as f32)),
|
||||||
|
LuaValue::Integer(i) => return Ok(datatype.idiv(*i as f32)),
|
||||||
|
LuaValue::UserData(ud) => {
|
||||||
|
if let Ok(vec) = ud.borrow::<D>() {
|
||||||
|
return Ok(datatype.idiv(*vec));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
Err(LuaError::FromLuaConversionError {
|
||||||
|
from: rhs.type_name(),
|
||||||
|
to: type_name::<D>(),
|
||||||
|
message: Some(format!(
|
||||||
|
"Expected {} or number, got {}",
|
||||||
|
type_name::<D>(),
|
||||||
|
rhs.type_name()
|
||||||
|
)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn userdata_impl_div_i32<D>(_: &Lua, datatype: &D, rhs: LuaValue) -> LuaResult<D>
|
pub fn userdata_impl_div_i32<D>(_: &Lua, datatype: &D, rhs: LuaValue) -> LuaResult<D>
|
||||||
where
|
where
|
||||||
D: LuaUserData + ops::Div<D, Output = D> + ops::Div<i32, Output = D> + Copy + 'static,
|
D: LuaUserData + ops::Div<D, Output = D> + ops::Div<i32, Output = D> + Copy + 'static,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "lune-std-datetime"
|
name = "lune-std-datetime"
|
||||||
version = "0.1.1"
|
version = "0.1.3"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
repository = "https://github.com/lune-org/lune"
|
repository = "https://github.com/lune-org/lune"
|
||||||
|
@ -13,10 +13,10 @@ path = "src/lib.rs"
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
mlua = { version = "0.9.7", features = ["luau"] }
|
mlua = { version = "0.9.9", features = ["luau"] }
|
||||||
|
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
chrono = "0.4.38"
|
chrono = "0.4.38"
|
||||||
chrono_lc = "0.1.6"
|
chrono_lc = "0.1.6"
|
||||||
|
|
||||||
lune-utils = { version = "0.1.0", path = "../lune-utils" }
|
lune-utils = { version = "0.1.3", path = "../lune-utils" }
|
||||||
|
|
|
@ -60,7 +60,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
Conversion methods between `DateTimeValues` and plain lua tables
|
Conversion methods between `DateTimeValues` and plain lua tables
|
||||||
|
|
||||||
Note that the `IntoLua` implementation here uses a read-only table,
|
Note that the `IntoLua` implementation here uses a read-only table,
|
||||||
|
@ -117,7 +117,7 @@ impl IntoLua<'_> for DateTimeValues {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
Conversion methods between chrono's timezone-aware `DateTime` to
|
Conversion methods between chrono's timezone-aware `DateTime` to
|
||||||
and from our non-timezone-aware `DateTimeValues` values struct
|
and from our non-timezone-aware `DateTimeValues` values struct
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "lune-std-fs"
|
name = "lune-std-fs"
|
||||||
version = "0.1.0"
|
version = "0.1.2"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
repository = "https://github.com/lune-org/lune"
|
repository = "https://github.com/lune-org/lune"
|
||||||
|
@ -13,11 +13,11 @@ path = "src/lib.rs"
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
mlua = { version = "0.9.7", features = ["luau"] }
|
mlua = { version = "0.9.9", features = ["luau"] }
|
||||||
|
|
||||||
bstr = "1.9"
|
bstr = "1.9"
|
||||||
|
|
||||||
tokio = { version = "1", default-features = false, features = ["fs"] }
|
tokio = { version = "1", default-features = false, features = ["fs"] }
|
||||||
|
|
||||||
lune-utils = { version = "0.1.0", path = "../lune-utils" }
|
lune-utils = { version = "0.1.3", path = "../lune-utils" }
|
||||||
lune-std-datetime = { version = "0.1.0", path = "../lune-std-datetime" }
|
lune-std-datetime = { version = "0.1.2", path = "../lune-std-datetime" }
|
||||||
|
|
25
crates/lune-std-fs/src/lib.rs
Normal file → Executable file
25
crates/lune-std-fs/src/lib.rs
Normal file → Executable file
|
@ -1,7 +1,7 @@
|
||||||
#![allow(clippy::cargo_common_metadata)]
|
#![allow(clippy::cargo_common_metadata)]
|
||||||
|
|
||||||
use std::io::ErrorKind as IoErrorKind;
|
use std::io::ErrorKind as IoErrorKind;
|
||||||
use std::path::{PathBuf, MAIN_SEPARATOR};
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use bstr::{BString, ByteSlice};
|
use bstr::{BString, ByteSlice};
|
||||||
use mlua::prelude::*;
|
use mlua::prelude::*;
|
||||||
|
@ -50,29 +50,16 @@ async fn fs_read_dir(_: &Lua, path: String) -> LuaResult<Vec<String>> {
|
||||||
let mut dir_strings = Vec::new();
|
let mut dir_strings = Vec::new();
|
||||||
let mut dir = fs::read_dir(&path).await.into_lua_err()?;
|
let mut dir = fs::read_dir(&path).await.into_lua_err()?;
|
||||||
while let Some(dir_entry) = dir.next_entry().await.into_lua_err()? {
|
while let Some(dir_entry) = dir.next_entry().await.into_lua_err()? {
|
||||||
if let Some(dir_path_str) = dir_entry.path().to_str() {
|
if let Some(dir_name_str) = dir_entry.file_name().to_str() {
|
||||||
dir_strings.push(dir_path_str.to_owned());
|
dir_strings.push(dir_name_str.to_owned());
|
||||||
} else {
|
} else {
|
||||||
return Err(LuaError::RuntimeError(format!(
|
return Err(LuaError::RuntimeError(format!(
|
||||||
"File path could not be converted into a string: '{}'",
|
"File name could not be converted into a string: '{}'",
|
||||||
dir_entry.path().display()
|
dir_entry.file_name().to_string_lossy()
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut dir_string_prefix = path;
|
Ok(dir_strings)
|
||||||
if !dir_string_prefix.ends_with(MAIN_SEPARATOR) {
|
|
||||||
dir_string_prefix.push(MAIN_SEPARATOR);
|
|
||||||
}
|
|
||||||
let dir_strings_no_prefix = dir_strings
|
|
||||||
.iter()
|
|
||||||
.map(|inner_path| {
|
|
||||||
inner_path
|
|
||||||
.trim()
|
|
||||||
.trim_start_matches(&dir_string_prefix)
|
|
||||||
.to_owned()
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
Ok(dir_strings_no_prefix)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fs_write_file(_: &Lua, (path, contents): (String, BString)) -> LuaResult<()> {
|
async fn fs_write_file(_: &Lua, (path, contents): (String, BString)) -> LuaResult<()> {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "lune-std-luau"
|
name = "lune-std-luau"
|
||||||
version = "0.1.0"
|
version = "0.1.2"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
repository = "https://github.com/lune-org/lune"
|
repository = "https://github.com/lune-org/lune"
|
||||||
|
@ -13,6 +13,6 @@ path = "src/lib.rs"
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
mlua = { version = "0.9.7", features = ["luau"] }
|
mlua = { version = "0.9.9", features = ["luau", "luau-jit"] }
|
||||||
|
|
||||||
lune-utils = { version = "0.1.0", path = "../lune-utils" }
|
lune-utils = { version = "0.1.3", path = "../lune-utils" }
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
use mlua::prelude::*;
|
use mlua::prelude::*;
|
||||||
|
|
||||||
use lune_utils::TableBuilder;
|
use lune_utils::{jit::JitStatus, TableBuilder};
|
||||||
|
|
||||||
mod options;
|
mod options;
|
||||||
|
|
||||||
|
@ -44,26 +44,47 @@ fn load_source<'lua>(
|
||||||
(source, options): (LuaString<'lua>, LuauLoadOptions),
|
(source, options): (LuaString<'lua>, LuauLoadOptions),
|
||||||
) -> LuaResult<LuaFunction<'lua>> {
|
) -> LuaResult<LuaFunction<'lua>> {
|
||||||
let mut chunk = lua.load(source.as_bytes()).set_name(options.debug_name);
|
let mut chunk = lua.load(source.as_bytes()).set_name(options.debug_name);
|
||||||
|
let env_changed = options.environment.is_some();
|
||||||
|
|
||||||
if let Some(environment) = options.environment {
|
if let Some(custom_environment) = options.environment {
|
||||||
let environment_with_globals = lua.create_table()?;
|
let environment = lua.create_table()?;
|
||||||
|
|
||||||
if let Some(meta) = environment.get_metatable() {
|
// Inject all globals into the environment
|
||||||
environment_with_globals.set_metatable(Some(meta));
|
if options.inject_globals {
|
||||||
|
for pair in lua.globals().pairs() {
|
||||||
|
let (key, value): (LuaValue, LuaValue) = pair?;
|
||||||
|
environment.set(key, value)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(global_metatable) = lua.globals().get_metatable() {
|
||||||
|
environment.set_metatable(Some(global_metatable));
|
||||||
|
}
|
||||||
|
} else if let Some(custom_metatable) = custom_environment.get_metatable() {
|
||||||
|
// Since we don't need to set the global metatable,
|
||||||
|
// we can just set a custom metatable if it exists
|
||||||
|
environment.set_metatable(Some(custom_metatable));
|
||||||
}
|
}
|
||||||
|
|
||||||
for pair in lua.globals().pairs() {
|
// Inject the custom environment
|
||||||
|
for pair in custom_environment.pairs() {
|
||||||
let (key, value): (LuaValue, LuaValue) = pair?;
|
let (key, value): (LuaValue, LuaValue) = pair?;
|
||||||
environment_with_globals.set(key, value)?;
|
environment.set(key, value)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
for pair in environment.pairs() {
|
chunk = chunk.set_environment(environment);
|
||||||
let (key, value): (LuaValue, LuaValue) = pair?;
|
|
||||||
environment_with_globals.set(key, value)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
chunk = chunk.set_environment(environment_with_globals);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
chunk.into_function()
|
// Enable JIT if codegen is enabled and the environment hasn't
|
||||||
|
// changed, otherwise disable JIT since it'll fall back anyways
|
||||||
|
lua.enable_jit(options.codegen_enabled && !env_changed);
|
||||||
|
let function = chunk.into_function()?;
|
||||||
|
lua.enable_jit(
|
||||||
|
lua.app_data_ref::<JitStatus>()
|
||||||
|
.ok_or(LuaError::runtime(
|
||||||
|
"Failed to get current JitStatus ref from AppData",
|
||||||
|
))?
|
||||||
|
.enabled(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(function)
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,13 +79,11 @@ impl<'lua> FromLua<'lua> for LuauCompileOptions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
Options for loading Lua source code.
|
|
||||||
*/
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct LuauLoadOptions<'lua> {
|
pub struct LuauLoadOptions<'lua> {
|
||||||
pub(crate) debug_name: String,
|
pub(crate) debug_name: String,
|
||||||
pub(crate) environment: Option<LuaTable<'lua>>,
|
pub(crate) environment: Option<LuaTable<'lua>>,
|
||||||
|
pub(crate) inject_globals: bool,
|
||||||
|
pub(crate) codegen_enabled: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for LuauLoadOptions<'_> {
|
impl Default for LuauLoadOptions<'_> {
|
||||||
|
@ -93,6 +91,8 @@ impl Default for LuauLoadOptions<'_> {
|
||||||
Self {
|
Self {
|
||||||
debug_name: DEFAULT_DEBUG_NAME.to_string(),
|
debug_name: DEFAULT_DEBUG_NAME.to_string(),
|
||||||
environment: None,
|
environment: None,
|
||||||
|
inject_globals: true,
|
||||||
|
codegen_enabled: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -112,11 +112,21 @@ impl<'lua> FromLua<'lua> for LuauLoadOptions<'lua> {
|
||||||
options.environment = Some(environment);
|
options.environment = Some(environment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(inject_globals) = t.get("injectGlobals")? {
|
||||||
|
options.inject_globals = inject_globals;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(codegen_enabled) = t.get("codegenEnabled")? {
|
||||||
|
options.codegen_enabled = codegen_enabled;
|
||||||
|
}
|
||||||
|
|
||||||
options
|
options
|
||||||
}
|
}
|
||||||
LuaValue::String(s) => Self {
|
LuaValue::String(s) => Self {
|
||||||
debug_name: s.to_string_lossy().to_string(),
|
debug_name: s.to_string_lossy().to_string(),
|
||||||
environment: None,
|
environment: None,
|
||||||
|
inject_globals: true,
|
||||||
|
codegen_enabled: false,
|
||||||
},
|
},
|
||||||
_ => {
|
_ => {
|
||||||
return Err(LuaError::FromLuaConversionError {
|
return Err(LuaError::FromLuaConversionError {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "lune-std-net"
|
name = "lune-std-net"
|
||||||
version = "0.1.0"
|
version = "0.1.2"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
repository = "https://github.com/lune-org/lune"
|
repository = "https://github.com/lune-org/lune"
|
||||||
|
@ -13,8 +13,8 @@ path = "src/lib.rs"
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
mlua = { version = "0.9.7", features = ["luau"] }
|
mlua = { version = "0.9.9", features = ["luau"] }
|
||||||
mlua-luau-scheduler = "0.0.2"
|
mlua-luau-scheduler = { version = "0.0.2", path = "../mlua-luau-scheduler" }
|
||||||
|
|
||||||
bstr = "1.9"
|
bstr = "1.9"
|
||||||
futures-util = "0.3"
|
futures-util = "0.3"
|
||||||
|
@ -35,5 +35,5 @@ tokio = { version = "1", default-features = false, features = [
|
||||||
"macros",
|
"macros",
|
||||||
] }
|
] }
|
||||||
|
|
||||||
lune-utils = { version = "0.1.0", path = "../lune-utils" }
|
lune-utils = { version = "0.1.3", path = "../lune-utils" }
|
||||||
lune-std-serde = { version = "0.1.0", path = "../lune-std-serde" }
|
lune-std-serde = { version = "0.1.2", path = "../lune-std-serde" }
|
||||||
|
|
|
@ -65,9 +65,9 @@ async fn net_request(lua: &Lua, config: RequestConfig) -> LuaResult<LuaTable> {
|
||||||
res.await?.into_lua_table(lua)
|
res.await?.into_lua_table(lua)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn net_socket(lua: &Lua, url: String) -> LuaResult<LuaTable> {
|
async fn net_socket(lua: &Lua, url: String) -> LuaResult<LuaValue> {
|
||||||
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(lua)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn net_serve<'lua>(
|
async fn net_serve<'lua>(
|
||||||
|
|
|
@ -18,6 +18,7 @@ impl LuaRequest {
|
||||||
let path = self.head.uri.path().to_string();
|
let path = self.head.uri.path().to_string();
|
||||||
let body = lua.create_string(&self.body)?;
|
let body = lua.create_string(&self.body)?;
|
||||||
|
|
||||||
|
#[allow(clippy::mutable_key_type)]
|
||||||
let query: HashMap<LuaString, LuaString> = self
|
let query: HashMap<LuaString, LuaString> = self
|
||||||
.head
|
.head
|
||||||
.uri
|
.uri
|
||||||
|
@ -32,6 +33,7 @@ impl LuaRequest {
|
||||||
})
|
})
|
||||||
.collect::<LuaResult<_>>()?;
|
.collect::<LuaResult<_>>()?;
|
||||||
|
|
||||||
|
#[allow(clippy::mutable_key_type)]
|
||||||
let headers: HashMap<LuaString, LuaString> = self
|
let headers: HashMap<LuaString, LuaString> = self
|
||||||
.head
|
.head
|
||||||
.headers
|
.headers
|
||||||
|
|
|
@ -40,13 +40,13 @@ impl Service<Request<Incoming>> for Svc {
|
||||||
lua.spawn_local(async move {
|
lua.spawn_local(async move {
|
||||||
let sock = sock.await.unwrap();
|
let sock = sock.await.unwrap();
|
||||||
let lua_sock = NetWebSocket::new(sock);
|
let lua_sock = NetWebSocket::new(sock);
|
||||||
let lua_tab = lua_sock.into_lua_table(&lua_inner).unwrap();
|
let lua_val = lua_sock.into_lua(&lua_inner).unwrap();
|
||||||
|
|
||||||
let handler_websocket: LuaFunction =
|
let handler_websocket: LuaFunction =
|
||||||
keys.websocket_handler(&lua_inner).unwrap().unwrap();
|
keys.websocket_handler(&lua_inner).unwrap().unwrap();
|
||||||
|
|
||||||
lua_inner
|
lua_inner
|
||||||
.push_thread_back(handler_websocket, lua_tab)
|
.push_thread_back(handler_websocket, lua_val)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -23,29 +23,6 @@ use hyper_tungstenite::{
|
||||||
WebSocketStream,
|
WebSocketStream,
|
||||||
};
|
};
|
||||||
|
|
||||||
use lune_utils::TableBuilder;
|
|
||||||
|
|
||||||
// Wrapper implementation for compatibility and changing colon syntax to dot syntax
|
|
||||||
const WEB_SOCKET_IMPL_LUA: &str = r#"
|
|
||||||
return freeze(setmetatable({
|
|
||||||
close = function(...)
|
|
||||||
return websocket:close(...)
|
|
||||||
end,
|
|
||||||
send = function(...)
|
|
||||||
return websocket:send(...)
|
|
||||||
end,
|
|
||||||
next = function(...)
|
|
||||||
return websocket:next(...)
|
|
||||||
end,
|
|
||||||
}, {
|
|
||||||
__index = function(self, key)
|
|
||||||
if key == "closeCode" then
|
|
||||||
return websocket.closeCode
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
}))
|
|
||||||
"#;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct NetWebSocket<T> {
|
pub struct NetWebSocket<T> {
|
||||||
close_code_exists: Arc<AtomicBool>,
|
close_code_exists: Arc<AtomicBool>,
|
||||||
|
@ -125,25 +102,6 @@ where
|
||||||
let mut ws = self.write_stream.lock().await;
|
let mut ws = self.write_stream.lock().await;
|
||||||
ws.close().await.into_lua_err()
|
ws.close().await.into_lua_err()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn into_lua_table(self, lua: &Lua) -> LuaResult<LuaTable> {
|
|
||||||
let setmetatable = lua.globals().get::<_, LuaFunction>("setmetatable")?;
|
|
||||||
let table_freeze = lua
|
|
||||||
.globals()
|
|
||||||
.get::<_, LuaTable>("table")?
|
|
||||||
.get::<_, LuaFunction>("freeze")?;
|
|
||||||
|
|
||||||
let env = TableBuilder::new(lua)?
|
|
||||||
.with_value("websocket", self.clone())?
|
|
||||||
.with_value("setmetatable", setmetatable)?
|
|
||||||
.with_value("freeze", table_freeze)?
|
|
||||||
.build_readonly()?;
|
|
||||||
|
|
||||||
lua.load(WEB_SOCKET_IMPL_LUA)
|
|
||||||
.set_name("websocket")
|
|
||||||
.set_environment(env)
|
|
||||||
.eval()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> LuaUserData for NetWebSocket<T>
|
impl<T> LuaUserData for NetWebSocket<T>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "lune-std-process"
|
name = "lune-std-process"
|
||||||
version = "0.1.0"
|
version = "0.1.3"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
repository = "https://github.com/lune-org/lune"
|
repository = "https://github.com/lune-org/lune"
|
||||||
|
@ -13,13 +13,16 @@ path = "src/lib.rs"
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
mlua = { version = "0.9.7", features = ["luau"] }
|
mlua = { version = "0.9.9", features = ["luau"] }
|
||||||
mlua-luau-scheduler = "0.0.2"
|
mlua-luau-scheduler = { version = "0.0.2", path = "../mlua-luau-scheduler" }
|
||||||
|
|
||||||
directories = "5.0"
|
directories = "5.0"
|
||||||
pin-project = "1.0"
|
pin-project = "1.0"
|
||||||
os_str_bytes = { version = "7.0", features = ["conversions"] }
|
os_str_bytes = { version = "7.0", features = ["conversions"] }
|
||||||
|
|
||||||
|
bstr = "1.9"
|
||||||
|
bytes = "1.6.0"
|
||||||
|
|
||||||
tokio = { version = "1", default-features = false, features = [
|
tokio = { version = "1", default-features = false, features = [
|
||||||
"io-std",
|
"io-std",
|
||||||
"io-util",
|
"io-util",
|
||||||
|
@ -28,4 +31,4 @@ tokio = { version = "1", default-features = false, features = [
|
||||||
"sync",
|
"sync",
|
||||||
] }
|
] }
|
||||||
|
|
||||||
lune-utils = { version = "0.1.0", path = "../lune-utils" }
|
lune-utils = { version = "0.1.3", path = "../lune-utils" }
|
||||||
|
|
|
@ -1,27 +1,33 @@
|
||||||
#![allow(clippy::cargo_common_metadata)]
|
#![allow(clippy::cargo_common_metadata)]
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
|
cell::RefCell,
|
||||||
env::{
|
env::{
|
||||||
self,
|
self,
|
||||||
consts::{ARCH, OS},
|
consts::{ARCH, OS},
|
||||||
},
|
},
|
||||||
path::MAIN_SEPARATOR,
|
path::MAIN_SEPARATOR,
|
||||||
process::Stdio,
|
process::Stdio,
|
||||||
|
rc::Rc,
|
||||||
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
use mlua::prelude::*;
|
use mlua::prelude::*;
|
||||||
|
|
||||||
use lune_utils::TableBuilder;
|
use lune_utils::TableBuilder;
|
||||||
use mlua_luau_scheduler::{Functions, LuaSpawnExt};
|
use mlua_luau_scheduler::{Functions, LuaSpawnExt};
|
||||||
|
use options::ProcessSpawnOptionsStdio;
|
||||||
use os_str_bytes::RawOsString;
|
use os_str_bytes::RawOsString;
|
||||||
use tokio::io::AsyncWriteExt;
|
use stream::{ChildProcessReader, ChildProcessWriter};
|
||||||
|
use tokio::{io::AsyncWriteExt, process::Child, sync::RwLock};
|
||||||
|
|
||||||
mod options;
|
mod options;
|
||||||
|
mod stream;
|
||||||
mod tee_writer;
|
mod tee_writer;
|
||||||
mod wait_for_child;
|
mod wait_for_child;
|
||||||
|
|
||||||
use self::options::ProcessSpawnOptions;
|
use self::options::ProcessSpawnOptions;
|
||||||
use self::wait_for_child::{wait_for_child, WaitForChildResult};
|
use self::wait_for_child::wait_for_child;
|
||||||
|
|
||||||
use lune_utils::path::get_current_dir;
|
use lune_utils::path::get_current_dir;
|
||||||
|
|
||||||
|
@ -42,8 +48,13 @@ pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
|
||||||
cwd_str.push(MAIN_SEPARATOR);
|
cwd_str.push(MAIN_SEPARATOR);
|
||||||
}
|
}
|
||||||
// Create constants for OS & processor architecture
|
// Create constants for OS & processor architecture
|
||||||
let os = lua.create_string(&OS.to_lowercase())?;
|
let os = lua.create_string(OS.to_lowercase())?;
|
||||||
let arch = lua.create_string(&ARCH.to_lowercase())?;
|
let arch = lua.create_string(ARCH.to_lowercase())?;
|
||||||
|
let endianness = lua.create_string(if cfg!(target_endian = "big") {
|
||||||
|
"big"
|
||||||
|
} else {
|
||||||
|
"little"
|
||||||
|
})?;
|
||||||
// Create readonly args array
|
// Create readonly args array
|
||||||
let args_vec = lua
|
let args_vec = lua
|
||||||
.app_data_ref::<Vec<String>>()
|
.app_data_ref::<Vec<String>>()
|
||||||
|
@ -69,11 +80,13 @@ pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
|
||||||
TableBuilder::new(lua)?
|
TableBuilder::new(lua)?
|
||||||
.with_value("os", os)?
|
.with_value("os", os)?
|
||||||
.with_value("arch", arch)?
|
.with_value("arch", arch)?
|
||||||
|
.with_value("endianness", endianness)?
|
||||||
.with_value("args", args_tab)?
|
.with_value("args", args_tab)?
|
||||||
.with_value("cwd", cwd_str)?
|
.with_value("cwd", cwd_str)?
|
||||||
.with_value("env", env_tab)?
|
.with_value("env", env_tab)?
|
||||||
.with_value("exit", process_exit)?
|
.with_value("exit", process_exit)?
|
||||||
.with_async_function("spawn", process_spawn)?
|
.with_async_function("exec", process_exec)?
|
||||||
|
.with_function("create", process_create)?
|
||||||
.build_readonly()
|
.build_readonly()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,14 +154,16 @@ fn process_env_iter<'lua>(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn process_spawn(
|
async fn process_exec(
|
||||||
lua: &Lua,
|
lua: &Lua,
|
||||||
(program, args, options): (String, Option<Vec<String>>, ProcessSpawnOptions),
|
(program, args, options): (String, Option<Vec<String>>, ProcessSpawnOptions),
|
||||||
) -> LuaResult<LuaTable> {
|
) -> LuaResult<LuaTable> {
|
||||||
let res = lua
|
let res = lua
|
||||||
.spawn(spawn_command(program, args, options))
|
.spawn(async move {
|
||||||
.await
|
let cmd = spawn_command_with_stdin(program, args, options.clone()).await?;
|
||||||
.expect("Failed to receive result of spawned process");
|
wait_for_child(cmd, options.stdio.stdout, options.stdio.stderr).await
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
NOTE: If an exit code was not given by the child process,
|
NOTE: If an exit code was not given by the child process,
|
||||||
|
@ -171,30 +186,104 @@ async fn process_spawn(
|
||||||
.build_readonly()
|
.build_readonly()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn spawn_command(
|
#[allow(clippy::await_holding_refcell_ref)]
|
||||||
|
fn process_create(
|
||||||
|
lua: &Lua,
|
||||||
|
(program, args, options): (String, Option<Vec<String>>, ProcessSpawnOptions),
|
||||||
|
) -> LuaResult<LuaTable> {
|
||||||
|
// We do not want the user to provide stdio options for process.create,
|
||||||
|
// so we reset the options, regardless of what the user provides us
|
||||||
|
let mut spawn_options = options.clone();
|
||||||
|
spawn_options.stdio = ProcessSpawnOptionsStdio::default();
|
||||||
|
|
||||||
|
let (code_tx, code_rx) = tokio::sync::broadcast::channel(4);
|
||||||
|
let code_rx_rc = Rc::new(RefCell::new(code_rx));
|
||||||
|
|
||||||
|
let child = spawn_command(program, args, spawn_options)?;
|
||||||
|
|
||||||
|
let child_arc = Arc::new(RwLock::new(child));
|
||||||
|
|
||||||
|
let child_arc_clone = Arc::clone(&child_arc);
|
||||||
|
let mut child_lock = tokio::task::block_in_place(|| child_arc_clone.blocking_write());
|
||||||
|
|
||||||
|
let stdin = child_lock.stdin.take().unwrap();
|
||||||
|
let stdout = child_lock.stdout.take().unwrap();
|
||||||
|
let stderr = child_lock.stderr.take().unwrap();
|
||||||
|
|
||||||
|
let child_arc_inner = Arc::clone(&child_arc);
|
||||||
|
|
||||||
|
// Spawn a background task to wait for the child to exit and send the exit code
|
||||||
|
let status_handle = tokio::spawn(async move {
|
||||||
|
let res = child_arc_inner.write().await.wait().await;
|
||||||
|
|
||||||
|
if let Ok(output) = res {
|
||||||
|
let code = output.code().unwrap_or_default();
|
||||||
|
|
||||||
|
code_tx
|
||||||
|
.send(code)
|
||||||
|
.expect("ExitCode receiver was unexpectedly dropped");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
TableBuilder::new(lua)?
|
||||||
|
.with_value("stdout", ChildProcessReader(stdout))?
|
||||||
|
.with_value("stderr", ChildProcessReader(stderr))?
|
||||||
|
.with_value("stdin", ChildProcessWriter(stdin))?
|
||||||
|
.with_async_function("kill", move |_, ()| {
|
||||||
|
// First, stop the status task so the RwLock is dropped
|
||||||
|
status_handle.abort();
|
||||||
|
let child_arc_clone = Arc::clone(&child_arc);
|
||||||
|
|
||||||
|
// Then get another RwLock to write to the child process and kill it
|
||||||
|
async move { Ok(child_arc_clone.write().await.kill().await?) }
|
||||||
|
})?
|
||||||
|
.with_async_function("status", move |lua, ()| {
|
||||||
|
let code_rx_rc_clone = Rc::clone(&code_rx_rc);
|
||||||
|
async move {
|
||||||
|
// Exit code of 9 corresponds to SIGKILL, which should be the only case where
|
||||||
|
// the receiver gets suddenly dropped
|
||||||
|
let code = code_rx_rc_clone.borrow_mut().recv().await.unwrap_or(9);
|
||||||
|
|
||||||
|
TableBuilder::new(lua)?
|
||||||
|
.with_value("code", code)?
|
||||||
|
.with_value("ok", code == 0)?
|
||||||
|
.build_readonly()
|
||||||
|
}
|
||||||
|
})?
|
||||||
|
.build_readonly()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn spawn_command_with_stdin(
|
||||||
program: String,
|
program: String,
|
||||||
args: Option<Vec<String>>,
|
args: Option<Vec<String>>,
|
||||||
mut options: ProcessSpawnOptions,
|
mut options: ProcessSpawnOptions,
|
||||||
) -> LuaResult<WaitForChildResult> {
|
) -> LuaResult<Child> {
|
||||||
let stdout = options.stdio.stdout;
|
|
||||||
let stderr = options.stdio.stderr;
|
|
||||||
let stdin = options.stdio.stdin.take();
|
let stdin = options.stdio.stdin.take();
|
||||||
|
|
||||||
let mut child = options
|
let mut child = spawn_command(program, args, options)?;
|
||||||
.into_command(program, args)
|
|
||||||
.stdin(if stdin.is_some() {
|
|
||||||
Stdio::piped()
|
|
||||||
} else {
|
|
||||||
Stdio::null()
|
|
||||||
})
|
|
||||||
.stdout(stdout.as_stdio())
|
|
||||||
.stderr(stderr.as_stdio())
|
|
||||||
.spawn()?;
|
|
||||||
|
|
||||||
if let Some(stdin) = stdin {
|
if let Some(stdin) = stdin {
|
||||||
let mut child_stdin = child.stdin.take().unwrap();
|
let mut child_stdin = child.stdin.take().unwrap();
|
||||||
child_stdin.write_all(&stdin).await.into_lua_err()?;
|
child_stdin.write_all(&stdin).await.into_lua_err()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
wait_for_child(child, stdout, stderr).await
|
Ok(child)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spawn_command(
|
||||||
|
program: String,
|
||||||
|
args: Option<Vec<String>>,
|
||||||
|
options: ProcessSpawnOptions,
|
||||||
|
) -> LuaResult<Child> {
|
||||||
|
let stdout = options.stdio.stdout;
|
||||||
|
let stderr = options.stdio.stderr;
|
||||||
|
|
||||||
|
let child = options
|
||||||
|
.into_command(program, args)
|
||||||
|
.stdin(Stdio::piped())
|
||||||
|
.stdout(stdout.as_stdio())
|
||||||
|
.stderr(stderr.as_stdio())
|
||||||
|
.spawn()?;
|
||||||
|
|
||||||
|
Ok(child)
|
||||||
}
|
}
|
||||||
|
|
58
crates/lune-std-process/src/stream.rs
Normal file
58
crates/lune-std-process/src/stream.rs
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
use bstr::BString;
|
||||||
|
use bytes::BytesMut;
|
||||||
|
use mlua::prelude::*;
|
||||||
|
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
|
||||||
|
|
||||||
|
const CHUNK_SIZE: usize = 8;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ChildProcessReader<R: AsyncRead>(pub R);
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ChildProcessWriter<W: AsyncWrite>(pub W);
|
||||||
|
|
||||||
|
impl<R: AsyncRead + Unpin> ChildProcessReader<R> {
|
||||||
|
pub async fn read(&mut self, chunk_size: Option<usize>) -> LuaResult<Vec<u8>> {
|
||||||
|
let mut buf = BytesMut::with_capacity(chunk_size.unwrap_or(CHUNK_SIZE));
|
||||||
|
self.0.read_buf(&mut buf).await?;
|
||||||
|
|
||||||
|
Ok(buf.to_vec())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn read_to_end(&mut self) -> LuaResult<Vec<u8>> {
|
||||||
|
let mut buf = vec![];
|
||||||
|
self.0.read_to_end(&mut buf).await?;
|
||||||
|
|
||||||
|
Ok(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<R: AsyncRead + Unpin + 'static> LuaUserData for ChildProcessReader<R> {
|
||||||
|
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||||
|
methods.add_async_method_mut("read", |lua, this, chunk_size: Option<usize>| async move {
|
||||||
|
let buf = this.read(chunk_size).await?;
|
||||||
|
|
||||||
|
if buf.is_empty() {
|
||||||
|
return Ok(LuaValue::Nil);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(LuaValue::String(lua.create_string(buf)?))
|
||||||
|
});
|
||||||
|
|
||||||
|
methods.add_async_method_mut("readToEnd", |lua, this, ()| async {
|
||||||
|
Ok(lua.create_string(this.read_to_end().await?))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: AsyncWrite + Unpin> ChildProcessWriter<W> {
|
||||||
|
pub async fn write(&mut self, data: BString) -> LuaResult<()> {
|
||||||
|
self.0.write_all(data.as_ref()).await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: AsyncWrite + Unpin + 'static> LuaUserData for ChildProcessWriter<W> {
|
||||||
|
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||||
|
methods.add_async_method_mut("write", |_, this, data| async { this.write(data).await });
|
||||||
|
}
|
||||||
|
}
|
|
@ -33,7 +33,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, W> AsyncWrite for AsyncTeeWriter<'a, W>
|
impl<W> AsyncWrite for AsyncTeeWriter<'_, W>
|
||||||
where
|
where
|
||||||
W: AsyncWrite + Unpin,
|
W: AsyncWrite + Unpin,
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "lune-std-regex"
|
name = "lune-std-regex"
|
||||||
version = "0.1.0"
|
version = "0.1.2"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
repository = "https://github.com/lune-org/lune"
|
repository = "https://github.com/lune-org/lune"
|
||||||
|
@ -13,9 +13,9 @@ path = "src/lib.rs"
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
mlua = { version = "0.9.7", features = ["luau"] }
|
mlua = { version = "0.9.9", features = ["luau"] }
|
||||||
|
|
||||||
regex = "1.10"
|
regex = "1.10"
|
||||||
self_cell = "1.0"
|
self_cell = "1.0"
|
||||||
|
|
||||||
lune-utils = { version = "0.1.0", path = "../lune-utils" }
|
lune-utils = { version = "0.1.3", path = "../lune-utils" }
|
||||||
|
|
|
@ -81,7 +81,7 @@ impl LuaUserData for LuaCaptures {
|
||||||
|
|
||||||
methods.add_meta_method(LuaMetaMethod::Len, |_, this, ()| Ok(this.num_captures()));
|
methods.add_meta_method(LuaMetaMethod::Len, |_, this, ()| Ok(this.num_captures()));
|
||||||
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| {
|
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| {
|
||||||
Ok(format!("RegexCaptures({})", this.num_captures()))
|
Ok(format!("{}", this.num_captures()))
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,7 @@ impl LuaUserData for LuaMatch {
|
||||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||||
methods.add_meta_method(LuaMetaMethod::Len, |_, this, ()| Ok(this.range().len()));
|
methods.add_meta_method(LuaMetaMethod::Len, |_, this, ()| Ok(this.range().len()));
|
||||||
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| {
|
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| {
|
||||||
Ok(format!("RegexMatch({})", this.slice()))
|
Ok(this.slice().to_string())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,7 +66,7 @@ impl LuaUserData for LuaRegex {
|
||||||
);
|
);
|
||||||
|
|
||||||
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| {
|
methods.add_meta_method(LuaMetaMethod::ToString, |_, this, ()| {
|
||||||
Ok(format!("Regex({})", this.inner.as_str()))
|
Ok(this.inner.as_str().to_string())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "lune-std-roblox"
|
name = "lune-std-roblox"
|
||||||
version = "0.1.0"
|
version = "0.1.4"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
repository = "https://github.com/lune-org/lune"
|
repository = "https://github.com/lune-org/lune"
|
||||||
|
@ -13,11 +13,12 @@ path = "src/lib.rs"
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
mlua = { version = "0.9.7", features = ["luau"] }
|
mlua = { version = "0.9.9", features = ["luau"] }
|
||||||
mlua-luau-scheduler = "0.0.2"
|
mlua-luau-scheduler = { version = "0.0.2", path = "../mlua-luau-scheduler" }
|
||||||
|
|
||||||
once_cell = "1.17"
|
once_cell = "1.17"
|
||||||
rbx_cookie = { version = "0.1.4", default-features = false }
|
rbx_cookie = { version = "0.1.4", default-features = false }
|
||||||
|
roblox_install = "1.0.0"
|
||||||
|
|
||||||
lune-utils = { version = "0.1.0", path = "../lune-utils" }
|
lune-utils = { version = "0.1.3", path = "../lune-utils" }
|
||||||
lune-roblox = { version = "0.1.0", path = "../lune-roblox" }
|
lune-roblox = { version = "0.1.4", path = "../lune-roblox" }
|
||||||
|
|
|
@ -13,6 +13,7 @@ use lune_roblox::{
|
||||||
static REFLECTION_DATABASE: OnceCell<ReflectionDatabase> = OnceCell::new();
|
static REFLECTION_DATABASE: OnceCell<ReflectionDatabase> = OnceCell::new();
|
||||||
|
|
||||||
use lune_utils::TableBuilder;
|
use lune_utils::TableBuilder;
|
||||||
|
use roblox_install::RobloxStudio;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Creates the `roblox` standard library module.
|
Creates the `roblox` standard library module.
|
||||||
|
@ -39,6 +40,10 @@ pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
|
||||||
.with_function("getReflectionDatabase", get_reflection_database)?
|
.with_function("getReflectionDatabase", get_reflection_database)?
|
||||||
.with_function("implementProperty", implement_property)?
|
.with_function("implementProperty", implement_property)?
|
||||||
.with_function("implementMethod", implement_method)?
|
.with_function("implementMethod", implement_method)?
|
||||||
|
.with_function("studioApplicationPath", studio_application_path)?
|
||||||
|
.with_function("studioContentPath", studio_content_path)?
|
||||||
|
.with_function("studioPluginPath", studio_plugin_path)?
|
||||||
|
.with_function("studioBuiltinPluginPath", studio_builtin_plugin_path)?
|
||||||
.build_readonly()
|
.build_readonly()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,7 +77,7 @@ async fn serialize_place<'lua>(
|
||||||
lua: &'lua Lua,
|
lua: &'lua Lua,
|
||||||
(data_model, as_xml): (LuaUserDataRef<'lua, Instance>, Option<bool>),
|
(data_model, as_xml): (LuaUserDataRef<'lua, Instance>, Option<bool>),
|
||||||
) -> LuaResult<LuaString<'lua>> {
|
) -> LuaResult<LuaString<'lua>> {
|
||||||
let data_model = (*data_model).clone();
|
let data_model = *data_model;
|
||||||
let fut = lua.spawn_blocking(move || {
|
let fut = lua.spawn_blocking(move || {
|
||||||
let doc = Document::from_data_model_instance(data_model)?;
|
let doc = Document::from_data_model_instance(data_model)?;
|
||||||
let bytes = doc.to_bytes_with_format(match as_xml {
|
let bytes = doc.to_bytes_with_format(match as_xml {
|
||||||
|
@ -89,7 +94,7 @@ async fn serialize_model<'lua>(
|
||||||
lua: &'lua Lua,
|
lua: &'lua Lua,
|
||||||
(instances, as_xml): (Vec<LuaUserDataRef<'lua, Instance>>, Option<bool>),
|
(instances, as_xml): (Vec<LuaUserDataRef<'lua, Instance>>, Option<bool>),
|
||||||
) -> LuaResult<LuaString<'lua>> {
|
) -> LuaResult<LuaString<'lua>> {
|
||||||
let instances = instances.iter().map(|i| (*i).clone()).collect();
|
let instances = instances.iter().map(|i| **i).collect();
|
||||||
let fut = lua.spawn_blocking(move || {
|
let fut = lua.spawn_blocking(move || {
|
||||||
let doc = Document::from_instance_array(instances)?;
|
let doc = Document::from_instance_array(instances)?;
|
||||||
let bytes = doc.to_bytes_with_format(match as_xml {
|
let bytes = doc.to_bytes_with_format(match as_xml {
|
||||||
|
@ -147,3 +152,27 @@ fn implement_method(
|
||||||
InstanceRegistry::insert_method(lua, &class_name, &method_name, method).into_lua_err()?;
|
InstanceRegistry::insert_method(lua, &class_name, &method_name, method).into_lua_err()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn studio_application_path(_: &Lua, _: ()) -> LuaResult<String> {
|
||||||
|
RobloxStudio::locate()
|
||||||
|
.map(|rs| rs.application_path().display().to_string())
|
||||||
|
.map_err(LuaError::external)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn studio_content_path(_: &Lua, _: ()) -> LuaResult<String> {
|
||||||
|
RobloxStudio::locate()
|
||||||
|
.map(|rs| rs.content_path().display().to_string())
|
||||||
|
.map_err(LuaError::external)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn studio_plugin_path(_: &Lua, _: ()) -> LuaResult<String> {
|
||||||
|
RobloxStudio::locate()
|
||||||
|
.map(|rs| rs.plugins_path().display().to_string())
|
||||||
|
.map_err(LuaError::external)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn studio_builtin_plugin_path(_: &Lua, _: ()) -> LuaResult<String> {
|
||||||
|
RobloxStudio::locate()
|
||||||
|
.map(|rs| rs.built_in_plugins_path().display().to_string())
|
||||||
|
.map_err(LuaError::external)
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "lune-std-serde"
|
name = "lune-std-serde"
|
||||||
version = "0.1.0"
|
version = "0.1.2"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
repository = "https://github.com/lune-org/lune"
|
repository = "https://github.com/lune-org/lune"
|
||||||
|
@ -13,7 +13,7 @@ path = "src/lib.rs"
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
mlua = { version = "0.9.7", features = ["luau", "serialize"] }
|
mlua = { version = "0.9.9", features = ["luau", "serialize"] }
|
||||||
|
|
||||||
async-compression = { version = "0.4", features = [
|
async-compression = { version = "0.4", features = [
|
||||||
"tokio",
|
"tokio",
|
||||||
|
@ -23,15 +23,25 @@ async-compression = { version = "0.4", features = [
|
||||||
"zlib",
|
"zlib",
|
||||||
] }
|
] }
|
||||||
bstr = "1.9"
|
bstr = "1.9"
|
||||||
lz4 = "1.24"
|
lz4 = "1.26"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = { version = "1.0", features = ["preserve_order"] }
|
serde_json = { version = "1.0", features = ["preserve_order"] }
|
||||||
serde_yaml = "0.9"
|
serde_yaml = "0.9"
|
||||||
toml = { version = "0.8", features = ["preserve_order"] }
|
toml = { version = "0.8", features = ["preserve_order"] }
|
||||||
|
|
||||||
|
digest = "0.10.7"
|
||||||
|
hmac = "0.12.1"
|
||||||
|
md-5 = "0.10.6"
|
||||||
|
sha1 = "0.10.6"
|
||||||
|
sha2 = "0.10.8"
|
||||||
|
sha3 = "0.10.8"
|
||||||
|
# This feature MIGHT break due to the unstable nature of the digest crate.
|
||||||
|
# Check before updating it.
|
||||||
|
blake3 = { version = "=1.5.0", features = ["traits-preview"] }
|
||||||
|
|
||||||
tokio = { version = "1", default-features = false, features = [
|
tokio = { version = "1", default-features = false, features = [
|
||||||
"rt",
|
"rt",
|
||||||
"io-util",
|
"io-util",
|
||||||
] }
|
] }
|
||||||
|
|
||||||
lune-utils = { version = "0.1.0", path = "../lune-utils" }
|
lune-utils = { version = "0.1.3", path = "../lune-utils" }
|
||||||
|
|
|
@ -13,6 +13,7 @@ use async_compression::{
|
||||||
BrotliDecoder, BrotliEncoder, GzipDecoder, GzipEncoder, ZlibDecoder, ZlibEncoder,
|
BrotliDecoder, BrotliEncoder, GzipDecoder, GzipEncoder, ZlibDecoder, ZlibEncoder,
|
||||||
},
|
},
|
||||||
Level::Best as CompressionQuality,
|
Level::Best as CompressionQuality,
|
||||||
|
Level::Precise as PreciseCompressionQuality,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -116,9 +117,10 @@ impl<'lua> FromLua<'lua> for CompressDecompressFormat {
|
||||||
|
|
||||||
Errors when the compression fails.
|
Errors when the compression fails.
|
||||||
*/
|
*/
|
||||||
pub async fn compress<'lua>(
|
pub async fn compress(
|
||||||
source: impl AsRef<[u8]>,
|
source: impl AsRef<[u8]>,
|
||||||
format: CompressDecompressFormat,
|
format: CompressDecompressFormat,
|
||||||
|
level: Option<i32>,
|
||||||
) -> LuaResult<Vec<u8>> {
|
) -> LuaResult<Vec<u8>> {
|
||||||
if let CompressDecompressFormat::LZ4 = format {
|
if let CompressDecompressFormat::LZ4 = format {
|
||||||
let source = source.as_ref().to_vec();
|
let source = source.as_ref().to_vec();
|
||||||
|
@ -130,18 +132,22 @@ pub async fn compress<'lua>(
|
||||||
|
|
||||||
let mut bytes = Vec::new();
|
let mut bytes = Vec::new();
|
||||||
let reader = BufReader::new(source.as_ref());
|
let reader = BufReader::new(source.as_ref());
|
||||||
|
let compression_quality = match level {
|
||||||
|
Some(l) => PreciseCompressionQuality(l),
|
||||||
|
None => CompressionQuality,
|
||||||
|
};
|
||||||
|
|
||||||
match format {
|
match format {
|
||||||
CompressDecompressFormat::Brotli => {
|
CompressDecompressFormat::Brotli => {
|
||||||
let mut encoder = BrotliEncoder::with_quality(reader, CompressionQuality);
|
let mut encoder = BrotliEncoder::with_quality(reader, compression_quality);
|
||||||
copy(&mut encoder, &mut bytes).await?;
|
copy(&mut encoder, &mut bytes).await?;
|
||||||
}
|
}
|
||||||
CompressDecompressFormat::GZip => {
|
CompressDecompressFormat::GZip => {
|
||||||
let mut encoder = GzipEncoder::with_quality(reader, CompressionQuality);
|
let mut encoder = GzipEncoder::with_quality(reader, compression_quality);
|
||||||
copy(&mut encoder, &mut bytes).await?;
|
copy(&mut encoder, &mut bytes).await?;
|
||||||
}
|
}
|
||||||
CompressDecompressFormat::ZLib => {
|
CompressDecompressFormat::ZLib => {
|
||||||
let mut encoder = ZlibEncoder::with_quality(reader, CompressionQuality);
|
let mut encoder = ZlibEncoder::with_quality(reader, compression_quality);
|
||||||
copy(&mut encoder, &mut bytes).await?;
|
copy(&mut encoder, &mut bytes).await?;
|
||||||
}
|
}
|
||||||
CompressDecompressFormat::LZ4 => unreachable!(),
|
CompressDecompressFormat::LZ4 => unreachable!(),
|
||||||
|
@ -157,7 +163,7 @@ pub async fn compress<'lua>(
|
||||||
|
|
||||||
Errors when the decompression fails.
|
Errors when the decompression fails.
|
||||||
*/
|
*/
|
||||||
pub async fn decompress<'lua>(
|
pub async fn decompress(
|
||||||
source: impl AsRef<[u8]>,
|
source: impl AsRef<[u8]>,
|
||||||
format: CompressDecompressFormat,
|
format: CompressDecompressFormat,
|
||||||
) -> LuaResult<Vec<u8>> {
|
) -> LuaResult<Vec<u8>> {
|
||||||
|
|
260
crates/lune-std-serde/src/hash.rs
Normal file
260
crates/lune-std-serde/src/hash.rs
Normal file
|
@ -0,0 +1,260 @@
|
||||||
|
use std::fmt::Write;
|
||||||
|
|
||||||
|
use bstr::BString;
|
||||||
|
use md5::Md5;
|
||||||
|
use mlua::prelude::*;
|
||||||
|
|
||||||
|
use blake3::Hasher as Blake3;
|
||||||
|
use sha1::Sha1;
|
||||||
|
use sha2::{Sha224, Sha256, Sha384, Sha512};
|
||||||
|
use sha3::{Sha3_224, Sha3_256, Sha3_384, Sha3_512};
|
||||||
|
|
||||||
|
pub struct HashOptions {
|
||||||
|
algorithm: HashAlgorithm,
|
||||||
|
message: BString,
|
||||||
|
secret: Option<BString>,
|
||||||
|
// seed: Option<BString>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
enum HashAlgorithm {
|
||||||
|
Md5,
|
||||||
|
Sha1,
|
||||||
|
// SHA-2 variants
|
||||||
|
Sha2_224,
|
||||||
|
Sha2_256,
|
||||||
|
Sha2_384,
|
||||||
|
Sha2_512,
|
||||||
|
// SHA-3 variants
|
||||||
|
Sha3_224,
|
||||||
|
Sha3_256,
|
||||||
|
Sha3_384,
|
||||||
|
Sha3_512,
|
||||||
|
// Blake3
|
||||||
|
Blake3,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HashAlgorithm {
|
||||||
|
pub const ALL: [Self; 11] = [
|
||||||
|
Self::Md5,
|
||||||
|
Self::Sha1,
|
||||||
|
Self::Sha2_224,
|
||||||
|
Self::Sha2_256,
|
||||||
|
Self::Sha2_384,
|
||||||
|
Self::Sha2_512,
|
||||||
|
Self::Sha3_224,
|
||||||
|
Self::Sha3_256,
|
||||||
|
Self::Sha3_384,
|
||||||
|
Self::Sha3_512,
|
||||||
|
Self::Blake3,
|
||||||
|
];
|
||||||
|
|
||||||
|
pub const fn name(self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::Md5 => "md5",
|
||||||
|
Self::Sha1 => "sha1",
|
||||||
|
Self::Sha2_224 => "sha224",
|
||||||
|
Self::Sha2_256 => "sha256",
|
||||||
|
Self::Sha2_384 => "sha384",
|
||||||
|
Self::Sha2_512 => "sha512",
|
||||||
|
Self::Sha3_224 => "sha3-224",
|
||||||
|
Self::Sha3_256 => "sha3-256",
|
||||||
|
Self::Sha3_384 => "sha3-384",
|
||||||
|
Self::Sha3_512 => "sha3-512",
|
||||||
|
Self::Blake3 => "blake3",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HashOptions {
|
||||||
|
/**
|
||||||
|
Computes the hash for the `message` using whatever `algorithm` is
|
||||||
|
contained within this struct and returns it as a string of hex digits.
|
||||||
|
*/
|
||||||
|
#[inline]
|
||||||
|
#[must_use = "hashing a message is useless without using the resulting hash"]
|
||||||
|
pub fn hash(self) -> String {
|
||||||
|
use digest::Digest;
|
||||||
|
|
||||||
|
let message = self.message;
|
||||||
|
let bytes = match self.algorithm {
|
||||||
|
HashAlgorithm::Md5 => Md5::digest(message).to_vec(),
|
||||||
|
HashAlgorithm::Sha1 => Sha1::digest(message).to_vec(),
|
||||||
|
HashAlgorithm::Sha2_224 => Sha224::digest(message).to_vec(),
|
||||||
|
HashAlgorithm::Sha2_256 => Sha256::digest(message).to_vec(),
|
||||||
|
HashAlgorithm::Sha2_384 => Sha384::digest(message).to_vec(),
|
||||||
|
HashAlgorithm::Sha2_512 => Sha512::digest(message).to_vec(),
|
||||||
|
|
||||||
|
HashAlgorithm::Sha3_224 => Sha3_224::digest(message).to_vec(),
|
||||||
|
HashAlgorithm::Sha3_256 => Sha3_256::digest(message).to_vec(),
|
||||||
|
HashAlgorithm::Sha3_384 => Sha3_384::digest(message).to_vec(),
|
||||||
|
HashAlgorithm::Sha3_512 => Sha3_512::digest(message).to_vec(),
|
||||||
|
|
||||||
|
HashAlgorithm::Blake3 => Blake3::digest(message).to_vec(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// We don't want to return raw binary data generally, since that's not
|
||||||
|
// what most people want a hash for. So we have to make a hex string.
|
||||||
|
bytes
|
||||||
|
.iter()
|
||||||
|
.fold(String::with_capacity(bytes.len() * 2), |mut output, b| {
|
||||||
|
let _ = write!(output, "{b:02x}");
|
||||||
|
output
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Computes the HMAC for the `message` using whatever `algorithm` and
|
||||||
|
`secret` are contained within this struct. The computed value is
|
||||||
|
returned as a string of hex digits.
|
||||||
|
|
||||||
|
# Errors
|
||||||
|
|
||||||
|
If the `secret` is not provided or is otherwise invalid.
|
||||||
|
*/
|
||||||
|
#[inline]
|
||||||
|
pub fn hmac(self) -> LuaResult<String> {
|
||||||
|
use hmac::{Hmac, Mac, SimpleHmac};
|
||||||
|
|
||||||
|
let secret = self
|
||||||
|
.secret
|
||||||
|
.ok_or_else(|| LuaError::FromLuaConversionError {
|
||||||
|
from: "nil",
|
||||||
|
to: "string or buffer",
|
||||||
|
message: Some("Argument #3 missing or nil".to_string()),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
/*
|
||||||
|
These macros exist to remove what would ultimately be dozens of
|
||||||
|
repeating lines. Essentially, there's several step to processing
|
||||||
|
HMacs, which expands into the 3 lines you see below. However,
|
||||||
|
the Hmac struct is specialized towards eager block-based processes.
|
||||||
|
In order to support anything else, like blake3, there's a second
|
||||||
|
type named `SimpleHmac`. This results in duplicate macros like
|
||||||
|
there are below.
|
||||||
|
*/
|
||||||
|
macro_rules! hmac {
|
||||||
|
($Type:ty) => {{
|
||||||
|
let mut mac: Hmac<$Type> = Hmac::new_from_slice(&secret).into_lua_err()?;
|
||||||
|
mac.update(&self.message);
|
||||||
|
mac.finalize().into_bytes().to_vec()
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
macro_rules! hmac_no_blocks {
|
||||||
|
($Type:ty) => {{
|
||||||
|
let mut mac: SimpleHmac<$Type> =
|
||||||
|
SimpleHmac::new_from_slice(&secret).into_lua_err()?;
|
||||||
|
mac.update(&self.message);
|
||||||
|
mac.finalize().into_bytes().to_vec()
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
let bytes = match self.algorithm {
|
||||||
|
HashAlgorithm::Md5 => hmac!(Md5),
|
||||||
|
HashAlgorithm::Sha1 => hmac!(Sha1),
|
||||||
|
|
||||||
|
HashAlgorithm::Sha2_224 => hmac!(Sha224),
|
||||||
|
HashAlgorithm::Sha2_256 => hmac!(Sha256),
|
||||||
|
HashAlgorithm::Sha2_384 => hmac!(Sha384),
|
||||||
|
HashAlgorithm::Sha2_512 => hmac!(Sha512),
|
||||||
|
|
||||||
|
HashAlgorithm::Sha3_224 => hmac!(Sha3_224),
|
||||||
|
HashAlgorithm::Sha3_256 => hmac!(Sha3_256),
|
||||||
|
HashAlgorithm::Sha3_384 => hmac!(Sha3_384),
|
||||||
|
HashAlgorithm::Sha3_512 => hmac!(Sha3_512),
|
||||||
|
|
||||||
|
HashAlgorithm::Blake3 => hmac_no_blocks!(Blake3),
|
||||||
|
};
|
||||||
|
Ok(bytes
|
||||||
|
.iter()
|
||||||
|
.fold(String::with_capacity(bytes.len() * 2), |mut output, b| {
|
||||||
|
let _ = write!(output, "{b:02x}");
|
||||||
|
output
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'lua> FromLua<'lua> for HashAlgorithm {
|
||||||
|
fn from_lua(value: LuaValue<'lua>, _lua: &'lua Lua) -> LuaResult<Self> {
|
||||||
|
if let LuaValue::String(str) = value {
|
||||||
|
/*
|
||||||
|
Casing tends to vary for algorithms, so rather than force
|
||||||
|
people to remember it we'll just accept any casing.
|
||||||
|
*/
|
||||||
|
let str = str.to_str()?.to_ascii_lowercase();
|
||||||
|
match str.as_str() {
|
||||||
|
"md5" => Ok(Self::Md5),
|
||||||
|
"sha1" => Ok(Self::Sha1),
|
||||||
|
|
||||||
|
"sha2-224" | "sha2_224" | "sha224" => Ok(Self::Sha2_224),
|
||||||
|
"sha2-256" | "sha2_256" | "sha256" => Ok(Self::Sha2_256),
|
||||||
|
"sha2-384" | "sha2_384" | "sha384" => Ok(Self::Sha2_384),
|
||||||
|
"sha2-512" | "sha2_512" | "sha512" => Ok(Self::Sha2_512),
|
||||||
|
|
||||||
|
"sha3-224" | "sha3_224" => Ok(Self::Sha3_224),
|
||||||
|
"sha3-256" | "sha3_256" => Ok(Self::Sha3_256),
|
||||||
|
"sha3-384" | "sha3_384" => Ok(Self::Sha3_384),
|
||||||
|
"sha3-512" | "sha3_512" => Ok(Self::Sha3_512),
|
||||||
|
|
||||||
|
"blake3" => Ok(Self::Blake3),
|
||||||
|
|
||||||
|
_ => Err(LuaError::FromLuaConversionError {
|
||||||
|
from: "string",
|
||||||
|
to: "HashAlgorithm",
|
||||||
|
message: Some(format!(
|
||||||
|
"Invalid hashing algorithm '{str}', valid kinds are:\n{}",
|
||||||
|
HashAlgorithm::ALL
|
||||||
|
.into_iter()
|
||||||
|
.map(HashAlgorithm::name)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ")
|
||||||
|
)),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(LuaError::FromLuaConversionError {
|
||||||
|
from: value.type_name(),
|
||||||
|
to: "HashAlgorithm",
|
||||||
|
message: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'lua> FromLuaMulti<'lua> for HashOptions {
|
||||||
|
fn from_lua_multi(mut values: LuaMultiValue<'lua>, lua: &'lua Lua) -> LuaResult<Self> {
|
||||||
|
let algorithm = values
|
||||||
|
.pop_front()
|
||||||
|
.map(|value| HashAlgorithm::from_lua(value, lua))
|
||||||
|
.transpose()?
|
||||||
|
.ok_or_else(|| LuaError::FromLuaConversionError {
|
||||||
|
from: "nil",
|
||||||
|
to: "HashAlgorithm",
|
||||||
|
message: Some("Argument #1 missing or nil".to_string()),
|
||||||
|
})?;
|
||||||
|
let message = values
|
||||||
|
.pop_front()
|
||||||
|
.map(|value| BString::from_lua(value, lua))
|
||||||
|
.transpose()?
|
||||||
|
.ok_or_else(|| LuaError::FromLuaConversionError {
|
||||||
|
from: "nil",
|
||||||
|
to: "string or buffer",
|
||||||
|
message: Some("Argument #2 missing or nil".to_string()),
|
||||||
|
})?;
|
||||||
|
let secret = values
|
||||||
|
.pop_front()
|
||||||
|
.map(|value| BString::from_lua(value, lua))
|
||||||
|
.transpose()?;
|
||||||
|
// let seed = values
|
||||||
|
// .pop_front()
|
||||||
|
// .map(|value| BString::from_lua(value, lua))
|
||||||
|
// .transpose()?;
|
||||||
|
|
||||||
|
Ok(HashOptions {
|
||||||
|
algorithm,
|
||||||
|
message,
|
||||||
|
secret,
|
||||||
|
// seed,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,9 +7,11 @@ use lune_utils::TableBuilder;
|
||||||
|
|
||||||
mod compress_decompress;
|
mod compress_decompress;
|
||||||
mod encode_decode;
|
mod encode_decode;
|
||||||
|
mod hash;
|
||||||
|
|
||||||
pub use self::compress_decompress::{compress, decompress, CompressDecompressFormat};
|
pub use self::compress_decompress::{compress, decompress, CompressDecompressFormat};
|
||||||
pub use self::encode_decode::{decode, encode, EncodeDecodeConfig, EncodeDecodeFormat};
|
pub use self::encode_decode::{decode, encode, EncodeDecodeConfig, EncodeDecodeFormat};
|
||||||
|
pub use self::hash::HashOptions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Creates the `serde` standard library module.
|
Creates the `serde` standard library module.
|
||||||
|
@ -24,6 +26,8 @@ pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
|
||||||
.with_function("decode", serde_decode)?
|
.with_function("decode", serde_decode)?
|
||||||
.with_async_function("compress", serde_compress)?
|
.with_async_function("compress", serde_compress)?
|
||||||
.with_async_function("decompress", serde_decompress)?
|
.with_async_function("decompress", serde_decompress)?
|
||||||
|
.with_function("hash", hash_message)?
|
||||||
|
.with_function("hmac", hmac_message)?
|
||||||
.build_readonly()
|
.build_readonly()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,9 +46,9 @@ fn serde_decode(lua: &Lua, (format, bs): (EncodeDecodeFormat, BString)) -> LuaRe
|
||||||
|
|
||||||
async fn serde_compress(
|
async fn serde_compress(
|
||||||
lua: &Lua,
|
lua: &Lua,
|
||||||
(format, bs): (CompressDecompressFormat, BString),
|
(format, bs, level): (CompressDecompressFormat, BString, Option<i32>),
|
||||||
) -> LuaResult<LuaString> {
|
) -> LuaResult<LuaString> {
|
||||||
let bytes = compress(bs, format).await?;
|
let bytes = compress(bs, format, level).await?;
|
||||||
lua.create_string(bytes)
|
lua.create_string(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,3 +59,11 @@ async fn serde_decompress(
|
||||||
let bytes = decompress(bs, format).await?;
|
let bytes = decompress(bs, format).await?;
|
||||||
lua.create_string(bytes)
|
lua.create_string(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn hash_message(lua: &Lua, options: HashOptions) -> LuaResult<LuaString> {
|
||||||
|
lua.create_string(options.hash())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hmac_message(lua: &Lua, options: HashOptions) -> LuaResult<LuaString> {
|
||||||
|
lua.create_string(options.hmac()?)
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "lune-std-stdio"
|
name = "lune-std-stdio"
|
||||||
version = "0.1.0"
|
version = "0.1.2"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
repository = "https://github.com/lune-org/lune"
|
repository = "https://github.com/lune-org/lune"
|
||||||
|
@ -14,12 +14,12 @@ workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
dialoguer = "0.11"
|
dialoguer = "0.11"
|
||||||
mlua = { version = "0.9.7", features = ["luau"] }
|
mlua = { version = "0.9.9", features = ["luau"] }
|
||||||
mlua-luau-scheduler = "0.0.2"
|
mlua-luau-scheduler = { version = "0.0.2", path = "../mlua-luau-scheduler" }
|
||||||
|
|
||||||
tokio = { version = "1", default-features = false, features = [
|
tokio = { version = "1", default-features = false, features = [
|
||||||
"io-std",
|
"io-std",
|
||||||
"io-util",
|
"io-util",
|
||||||
] }
|
] }
|
||||||
|
|
||||||
lune-utils = { version = "0.1.0", path = "../lune-utils" }
|
lune-utils = { version = "0.1.3", path = "../lune-utils" }
|
||||||
|
|
|
@ -194,14 +194,14 @@ pub fn prompt(options: PromptOptions) -> LuaResult<PromptResult> {
|
||||||
prompt = prompt.default(b);
|
prompt = prompt.default(b);
|
||||||
};
|
};
|
||||||
let result = prompt
|
let result = prompt
|
||||||
.with_prompt(&options.text.expect("Missing text in prompt options"))
|
.with_prompt(options.text.expect("Missing text in prompt options"))
|
||||||
.interact()
|
.interact()
|
||||||
.into_lua_err()?;
|
.into_lua_err()?;
|
||||||
Ok(PromptResult::Boolean(result))
|
Ok(PromptResult::Boolean(result))
|
||||||
}
|
}
|
||||||
PromptKind::Select => {
|
PromptKind::Select => {
|
||||||
let chosen = Select::with_theme(&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()
|
||||||
.into_lua_err()?;
|
.into_lua_err()?;
|
||||||
|
@ -212,7 +212,7 @@ pub fn prompt(options: PromptOptions) -> LuaResult<PromptResult> {
|
||||||
}
|
}
|
||||||
PromptKind::MultiSelect => {
|
PromptKind::MultiSelect => {
|
||||||
let chosen = MultiSelect::with_theme(&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()
|
||||||
.into_lua_err()?;
|
.into_lua_err()?;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "lune-std-task"
|
name = "lune-std-task"
|
||||||
version = "0.1.0"
|
version = "0.1.2"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
repository = "https://github.com/lune-org/lune"
|
repository = "https://github.com/lune-org/lune"
|
||||||
|
@ -13,9 +13,9 @@ path = "src/lib.rs"
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
mlua = { version = "0.9.7", features = ["luau"] }
|
mlua = { version = "0.9.9", features = ["luau"] }
|
||||||
mlua-luau-scheduler = "0.0.2"
|
mlua-luau-scheduler = { version = "0.0.2", path = "../mlua-luau-scheduler" }
|
||||||
|
|
||||||
tokio = { version = "1", default-features = false, features = ["time"] }
|
tokio = { version = "1", default-features = false, features = ["time"] }
|
||||||
|
|
||||||
lune-utils = { version = "0.1.0", path = "../lune-utils" }
|
lune-utils = { version = "0.1.3", path = "../lune-utils" }
|
||||||
|
|
|
@ -33,12 +33,6 @@ pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
|
||||||
.set_environment(task_delay_env)
|
.set_environment(task_delay_env)
|
||||||
.into_function()?;
|
.into_function()?;
|
||||||
|
|
||||||
// Overwrite resume & wrap functions on the coroutine global
|
|
||||||
// with ones that are compatible with our scheduler
|
|
||||||
let co = lua.globals().get::<_, LuaTable>("coroutine")?;
|
|
||||||
co.set("resume", fns.resume.clone())?;
|
|
||||||
co.set("wrap", fns.wrap.clone())?;
|
|
||||||
|
|
||||||
TableBuilder::new(lua)?
|
TableBuilder::new(lua)?
|
||||||
.with_value("cancel", fns.cancel)?
|
.with_value("cancel", fns.cancel)?
|
||||||
.with_value("defer", fns.defer)?
|
.with_value("defer", fns.defer)?
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "lune-std"
|
name = "lune-std"
|
||||||
version = "0.1.1"
|
version = "0.1.5"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
repository = "https://github.com/lune-org/lune"
|
repository = "https://github.com/lune-org/lune"
|
||||||
|
@ -38,22 +38,22 @@ stdio = ["dep:lune-std-stdio"]
|
||||||
task = ["dep:lune-std-task"]
|
task = ["dep:lune-std-task"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
mlua = { version = "0.9.7", features = ["luau"] }
|
mlua = { version = "0.9.9", features = ["luau"] }
|
||||||
mlua-luau-scheduler = "0.0.2"
|
mlua-luau-scheduler = { version = "0.0.2", path = "../mlua-luau-scheduler" }
|
||||||
|
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
tokio = { version = "1", default-features = false, features = ["fs", "sync"] }
|
tokio = { version = "1", default-features = false, features = ["fs", "sync"] }
|
||||||
|
|
||||||
lune-utils = { version = "0.1.0", path = "../lune-utils" }
|
lune-utils = { version = "0.1.3", path = "../lune-utils" }
|
||||||
|
|
||||||
lune-std-datetime = { optional = true, version = "0.1.1", path = "../lune-std-datetime" }
|
lune-std-datetime = { optional = true, version = "0.1.3", path = "../lune-std-datetime" }
|
||||||
lune-std-fs = { optional = true, version = "0.1.0", path = "../lune-std-fs" }
|
lune-std-fs = { optional = true, version = "0.1.2", path = "../lune-std-fs" }
|
||||||
lune-std-luau = { optional = true, version = "0.1.0", path = "../lune-std-luau" }
|
lune-std-luau = { optional = true, version = "0.1.2", path = "../lune-std-luau" }
|
||||||
lune-std-net = { optional = true, version = "0.1.0", path = "../lune-std-net" }
|
lune-std-net = { optional = true, version = "0.1.2", path = "../lune-std-net" }
|
||||||
lune-std-process = { optional = true, version = "0.1.0", path = "../lune-std-process" }
|
lune-std-process = { optional = true, version = "0.1.3", path = "../lune-std-process" }
|
||||||
lune-std-regex = { optional = true, version = "0.1.0", path = "../lune-std-regex" }
|
lune-std-regex = { optional = true, version = "0.1.2", path = "../lune-std-regex" }
|
||||||
lune-std-roblox = { optional = true, version = "0.1.0", path = "../lune-std-roblox" }
|
lune-std-roblox = { optional = true, version = "0.1.4", path = "../lune-std-roblox" }
|
||||||
lune-std-serde = { optional = true, version = "0.1.0", path = "../lune-std-serde" }
|
lune-std-serde = { optional = true, version = "0.1.2", path = "../lune-std-serde" }
|
||||||
lune-std-stdio = { optional = true, version = "0.1.0", path = "../lune-std-stdio" }
|
lune-std-stdio = { optional = true, version = "0.1.2", path = "../lune-std-stdio" }
|
||||||
lune-std-task = { optional = true, version = "0.1.0", path = "../lune-std-task" }
|
lune-std-task = { optional = true, version = "0.1.2", path = "../lune-std-task" }
|
||||||
|
|
|
@ -150,9 +150,9 @@ impl RequireContext {
|
||||||
self.get_from_cache(lua, abs_path.as_ref())
|
self.get_from_cache(lua, abs_path.as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn load<'lua>(
|
async fn load(
|
||||||
&self,
|
&self,
|
||||||
lua: &'lua Lua,
|
lua: &Lua,
|
||||||
abs_path: impl AsRef<Path>,
|
abs_path: impl AsRef<Path>,
|
||||||
rel_path: impl AsRef<Path>,
|
rel_path: impl AsRef<Path>,
|
||||||
) -> LuaResult<LuaRegistryKey> {
|
) -> LuaResult<LuaRegistryKey> {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "lune-utils"
|
name = "lune-utils"
|
||||||
version = "0.1.0"
|
version = "0.1.3"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
repository = "https://github.com/lune-org/lune"
|
repository = "https://github.com/lune-org/lune"
|
||||||
|
@ -13,7 +13,7 @@ path = "src/lib.rs"
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
mlua = { version = "0.9.7", features = ["luau", "async"] }
|
mlua = { version = "0.9.9", features = ["luau", "async"] }
|
||||||
|
|
||||||
tokio = { version = "1", default-features = false, features = ["fs"] }
|
tokio = { version = "1", default-features = false, features = ["fs"] }
|
||||||
|
|
||||||
|
@ -22,3 +22,5 @@ dunce = "1.0"
|
||||||
once_cell = "1.17"
|
once_cell = "1.17"
|
||||||
path-clean = "1.0"
|
path-clean = "1.0"
|
||||||
pathdiff = "0.2"
|
pathdiff = "0.2"
|
||||||
|
parking_lot = "0.12.3"
|
||||||
|
semver = "1.0"
|
||||||
|
|
|
@ -26,6 +26,11 @@ static STYLED_STACK_END: Lazy<String> = Lazy::new(|| {
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// NOTE: We indent using 4 spaces instead of tabs since
|
||||||
|
// these errors are most likely to be displayed in a terminal
|
||||||
|
// or some kind of live output - and tabs don't work well there
|
||||||
|
const STACK_TRACE_INDENT: &str = " ";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Error components parsed from a [`LuaError`].
|
Error components parsed from a [`LuaError`].
|
||||||
|
|
||||||
|
@ -86,7 +91,7 @@ impl fmt::Display for ErrorComponents {
|
||||||
let trace = self.trace.as_ref().unwrap();
|
let trace = self.trace.as_ref().unwrap();
|
||||||
writeln!(f, "{}", *STYLED_STACK_BEGIN)?;
|
writeln!(f, "{}", *STYLED_STACK_BEGIN)?;
|
||||||
for line in trace.lines() {
|
for line in trace.lines() {
|
||||||
writeln!(f, "\t{line}")?;
|
writeln!(f, "{STACK_TRACE_INDENT}{line}")?;
|
||||||
}
|
}
|
||||||
writeln!(f, "{}", *STYLED_STACK_END)?;
|
writeln!(f, "{}", *STYLED_STACK_END)?;
|
||||||
}
|
}
|
||||||
|
@ -124,7 +129,7 @@ impl From<LuaError> for ErrorComponents {
|
||||||
}
|
}
|
||||||
|
|
||||||
// We will then try to extract any stack trace
|
// We will then try to extract any stack trace
|
||||||
let trace = if let LuaError::CallbackError {
|
let mut trace = if let LuaError::CallbackError {
|
||||||
ref traceback,
|
ref traceback,
|
||||||
ref cause,
|
ref cause,
|
||||||
} = *error
|
} = *error
|
||||||
|
@ -147,6 +152,45 @@ impl From<LuaError> for ErrorComponents {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Sometimes, we can get duplicate stack trace lines that only
|
||||||
|
// mention "[C]", without a function name or path, and these can
|
||||||
|
// be safely ignored / removed if the following line has more info
|
||||||
|
if let Some(trace) = &mut trace {
|
||||||
|
let lines = trace.lines_mut();
|
||||||
|
loop {
|
||||||
|
let first_is_c_and_empty = lines
|
||||||
|
.first()
|
||||||
|
.is_some_and(|line| line.source().is_c() && line.is_empty());
|
||||||
|
let second_is_c_and_nonempty = lines
|
||||||
|
.get(1)
|
||||||
|
.is_some_and(|line| line.source().is_c() && !line.is_empty());
|
||||||
|
if first_is_c_and_empty && second_is_c_and_nonempty {
|
||||||
|
lines.remove(0);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally, we do some light postprocessing to remove duplicate
|
||||||
|
// information, such as the location prefix in the error message
|
||||||
|
if let Some(message) = messages.last_mut() {
|
||||||
|
if let Some(line) = trace
|
||||||
|
.iter()
|
||||||
|
.flat_map(StackTrace::lines)
|
||||||
|
.find(|line| line.source().is_lua())
|
||||||
|
{
|
||||||
|
let location_prefix = format!(
|
||||||
|
"[string \"{}\"]:{}:",
|
||||||
|
line.path().unwrap(),
|
||||||
|
line.line_number().unwrap()
|
||||||
|
);
|
||||||
|
if message.starts_with(&location_prefix) {
|
||||||
|
*message = message[location_prefix.len()..].trim().to_string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ErrorComponents { messages, trace }
|
ErrorComponents { messages, trace }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,24 @@ pub enum StackTraceSource {
|
||||||
Lua,
|
Lua,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl StackTraceSource {
|
||||||
|
/**
|
||||||
|
Returns `true` if the error originated from a C / Rust function, `false` otherwise.
|
||||||
|
*/
|
||||||
|
#[must_use]
|
||||||
|
pub const fn is_c(self) -> bool {
|
||||||
|
matches!(self, Self::C)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Returns `true` if the error originated from a Lua (user) function, `false` otherwise.
|
||||||
|
*/
|
||||||
|
#[must_use]
|
||||||
|
pub const fn is_lua(self) -> bool {
|
||||||
|
matches!(self, Self::Lua)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Stack trace line parsed from a [`LuaError`].
|
Stack trace line parsed from a [`LuaError`].
|
||||||
*/
|
*/
|
||||||
|
@ -82,6 +100,20 @@ impl StackTraceLine {
|
||||||
pub fn function_name(&self) -> Option<&str> {
|
pub fn function_name(&self) -> Option<&str> {
|
||||||
self.function_name.as_deref()
|
self.function_name.as_deref()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Returns `true` if the stack trace line contains no "useful" information, `false` otherwise.
|
||||||
|
|
||||||
|
Useful information is determined as one of:
|
||||||
|
|
||||||
|
- A path
|
||||||
|
- A line number
|
||||||
|
- A function name
|
||||||
|
*/
|
||||||
|
#[must_use]
|
||||||
|
pub const fn is_empty(&self) -> bool {
|
||||||
|
self.path.is_none() && self.line_number.is_none() && self.function_name.is_none()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for StackTraceLine {
|
impl FromStr for StackTraceLine {
|
||||||
|
@ -145,6 +177,14 @@ impl StackTrace {
|
||||||
pub fn lines(&self) -> &[StackTraceLine] {
|
pub fn lines(&self) -> &[StackTraceLine] {
|
||||||
&self.lines
|
&self.lines
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Returns the individual stack trace lines, mutably.
|
||||||
|
*/
|
||||||
|
#[must_use]
|
||||||
|
pub fn lines_mut(&mut self) -> &mut Vec<StackTraceLine> {
|
||||||
|
&mut self.lines
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromStr for StackTrace {
|
impl FromStr for StackTrace {
|
||||||
|
|
|
@ -2,7 +2,7 @@ use mlua::prelude::*;
|
||||||
|
|
||||||
use crate::fmt::ErrorComponents;
|
use crate::fmt::ErrorComponents;
|
||||||
|
|
||||||
fn new_lua_result() -> LuaResult<()> {
|
fn new_lua_runtime_error() -> LuaResult<()> {
|
||||||
let lua = Lua::new();
|
let lua = Lua::new();
|
||||||
|
|
||||||
lua.globals()
|
lua.globals()
|
||||||
|
@ -17,13 +17,34 @@ fn new_lua_result() -> LuaResult<()> {
|
||||||
lua.load("f()").set_name("chunk_name").eval()
|
lua.load("f()").set_name("chunk_name").eval()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn new_lua_script_error() -> LuaResult<()> {
|
||||||
|
let lua = Lua::new();
|
||||||
|
|
||||||
|
lua.load(
|
||||||
|
"local function inner()\
|
||||||
|
\n error(\"oh no, a script error\")\
|
||||||
|
\nend\
|
||||||
|
\n\
|
||||||
|
\nlocal function outer()\
|
||||||
|
\n inner()\
|
||||||
|
\nend\
|
||||||
|
\n\
|
||||||
|
\nouter()\
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.set_name("chunk_name")
|
||||||
|
.eval()
|
||||||
|
}
|
||||||
|
|
||||||
// Tests for error context stack
|
// Tests for error context stack
|
||||||
mod context {
|
mod context {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn preserves_original() {
|
fn preserves_original() {
|
||||||
let lua_error = new_lua_result().context("additional context").unwrap_err();
|
let lua_error = new_lua_runtime_error()
|
||||||
|
.context("additional context")
|
||||||
|
.unwrap_err();
|
||||||
let components = ErrorComponents::from(lua_error);
|
let components = ErrorComponents::from(lua_error);
|
||||||
|
|
||||||
assert_eq!(components.messages()[0], "additional context");
|
assert_eq!(components.messages()[0], "additional context");
|
||||||
|
@ -34,7 +55,7 @@ mod context {
|
||||||
fn preserves_levels() {
|
fn preserves_levels() {
|
||||||
// NOTE: The behavior in mlua is to preserve a single level of context
|
// NOTE: The behavior in mlua is to preserve a single level of context
|
||||||
// and not all levels (context gets replaced on each call to `context`)
|
// and not all levels (context gets replaced on each call to `context`)
|
||||||
let lua_error = new_lua_result()
|
let lua_error = new_lua_runtime_error()
|
||||||
.context("level 1")
|
.context("level 1")
|
||||||
.context("level 2")
|
.context("level 2")
|
||||||
.context("level 3")
|
.context("level 3")
|
||||||
|
@ -54,7 +75,7 @@ mod error_components {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn message() {
|
fn message() {
|
||||||
let lua_error = new_lua_result().unwrap_err();
|
let lua_error = new_lua_runtime_error().unwrap_err();
|
||||||
let components = ErrorComponents::from(lua_error);
|
let components = ErrorComponents::from(lua_error);
|
||||||
|
|
||||||
assert_eq!(components.messages()[0], "oh no, a runtime error");
|
assert_eq!(components.messages()[0], "oh no, a runtime error");
|
||||||
|
@ -62,7 +83,7 @@ mod error_components {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn stack_begin_end() {
|
fn stack_begin_end() {
|
||||||
let lua_error = new_lua_result().unwrap_err();
|
let lua_error = new_lua_runtime_error().unwrap_err();
|
||||||
let formatted = format!("{}", ErrorComponents::from(lua_error));
|
let formatted = format!("{}", ErrorComponents::from(lua_error));
|
||||||
|
|
||||||
assert!(formatted.contains("Stack Begin"));
|
assert!(formatted.contains("Stack Begin"));
|
||||||
|
@ -71,7 +92,7 @@ mod error_components {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn stack_lines() {
|
fn stack_lines() {
|
||||||
let lua_error = new_lua_result().unwrap_err();
|
let lua_error = new_lua_runtime_error().unwrap_err();
|
||||||
let components = ErrorComponents::from(lua_error);
|
let components = ErrorComponents::from(lua_error);
|
||||||
|
|
||||||
let mut lines = components.trace().unwrap().lines().iter();
|
let mut lines = components.trace().unwrap().lines().iter();
|
||||||
|
@ -83,3 +104,47 @@ mod error_components {
|
||||||
assert_eq!(line_2, "Script 'chunk_name', Line 1");
|
assert_eq!(line_2, "Script 'chunk_name', Line 1");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tests for general formatting
|
||||||
|
mod general {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn message_does_not_contain_location() {
|
||||||
|
let lua_error = new_lua_script_error().unwrap_err();
|
||||||
|
|
||||||
|
let components = ErrorComponents::from(lua_error);
|
||||||
|
let trace = components.trace().unwrap();
|
||||||
|
|
||||||
|
let first_message = components.messages().first().unwrap();
|
||||||
|
let first_lua_stack_line = trace
|
||||||
|
.lines()
|
||||||
|
.iter()
|
||||||
|
.find(|line| line.source().is_lua())
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let location_prefix = format!(
|
||||||
|
"[string \"{}\"]:{}:",
|
||||||
|
first_lua_stack_line.path().unwrap(),
|
||||||
|
first_lua_stack_line.line_number().unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(!first_message.starts_with(&location_prefix));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_redundant_c_mentions() {
|
||||||
|
let lua_error = new_lua_script_error().unwrap_err();
|
||||||
|
|
||||||
|
let components = ErrorComponents::from(lua_error);
|
||||||
|
let trace = components.trace().unwrap();
|
||||||
|
|
||||||
|
let c_stack_lines = trace
|
||||||
|
.lines()
|
||||||
|
.iter()
|
||||||
|
.filter(|line| line.source().is_c())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
assert_eq!(c_stack_lines.len(), 1); // Just the "error" call
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
use mlua::prelude::*;
|
use mlua::prelude::*;
|
||||||
|
|
||||||
|
use crate::fmt::ErrorComponents;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
metamethods::{call_table_tostring_metamethod, call_userdata_tostring_metamethod},
|
metamethods::{
|
||||||
|
call_table_tostring_metamethod, call_userdata_tostring_metamethod,
|
||||||
|
get_table_type_metavalue, get_userdata_type_metavalue,
|
||||||
|
},
|
||||||
style::{COLOR_CYAN, COLOR_GREEN, COLOR_MAGENTA, COLOR_YELLOW},
|
style::{COLOR_CYAN, COLOR_GREEN, COLOR_MAGENTA, COLOR_YELLOW},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -56,19 +61,39 @@ pub(crate) fn format_value_styled(value: &LuaValue, prefer_plain: bool) -> Strin
|
||||||
LuaValue::Function(_) => COLOR_MAGENTA.apply_to("<function>").to_string(),
|
LuaValue::Function(_) => COLOR_MAGENTA.apply_to("<function>").to_string(),
|
||||||
LuaValue::LightUserData(_) => COLOR_MAGENTA.apply_to("<pointer>").to_string(),
|
LuaValue::LightUserData(_) => COLOR_MAGENTA.apply_to("<pointer>").to_string(),
|
||||||
LuaValue::UserData(u) => {
|
LuaValue::UserData(u) => {
|
||||||
if let Some(s) = call_userdata_tostring_metamethod(u) {
|
let formatted = format_typename_and_tostringed(
|
||||||
s
|
"userdata",
|
||||||
} else {
|
get_userdata_type_metavalue(u),
|
||||||
COLOR_MAGENTA.apply_to("<userdata>").to_string()
|
call_userdata_tostring_metamethod(u),
|
||||||
}
|
);
|
||||||
|
COLOR_MAGENTA.apply_to(formatted).to_string()
|
||||||
}
|
}
|
||||||
LuaValue::Table(t) => {
|
LuaValue::Table(t) => {
|
||||||
if let Some(s) = call_table_tostring_metamethod(t) {
|
let formatted = format_typename_and_tostringed(
|
||||||
s
|
"table",
|
||||||
} else {
|
get_table_type_metavalue(t),
|
||||||
COLOR_MAGENTA.apply_to("<table>").to_string()
|
call_table_tostring_metamethod(t),
|
||||||
}
|
);
|
||||||
|
COLOR_MAGENTA.apply_to(formatted).to_string()
|
||||||
}
|
}
|
||||||
_ => COLOR_MAGENTA.apply_to("<?>").to_string(),
|
LuaValue::Error(e) => COLOR_MAGENTA
|
||||||
|
.apply_to(format!(
|
||||||
|
"<LuaError(\n{})>",
|
||||||
|
ErrorComponents::from(e.clone())
|
||||||
|
))
|
||||||
|
.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_typename_and_tostringed(
|
||||||
|
fallback: &'static str,
|
||||||
|
typename: Option<String>,
|
||||||
|
tostringed: Option<String>,
|
||||||
|
) -> String {
|
||||||
|
match (typename, tostringed) {
|
||||||
|
(Some(typename), Some(tostringed)) => format!("<{typename}({tostringed})>"),
|
||||||
|
(Some(typename), None) => format!("<{typename}>"),
|
||||||
|
(None, Some(tostringed)) => format!("<{tostringed}>"),
|
||||||
|
(None, None) => format!("<{fallback}>"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,29 +1,37 @@
|
||||||
use mlua::prelude::*;
|
use mlua::prelude::*;
|
||||||
|
|
||||||
|
pub fn get_table_type_metavalue<'a>(tab: &'a LuaTable<'a>) -> Option<String> {
|
||||||
|
let s = tab
|
||||||
|
.get_metatable()?
|
||||||
|
.get::<_, LuaString>(LuaMetaMethod::Type.name())
|
||||||
|
.ok()?;
|
||||||
|
let s = s.to_str().ok()?;
|
||||||
|
Some(s.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_userdata_type_metavalue<'a>(tab: &'a LuaAnyUserData<'a>) -> Option<String> {
|
||||||
|
let s = tab
|
||||||
|
.get_metatable()
|
||||||
|
.ok()?
|
||||||
|
.get::<LuaString>(LuaMetaMethod::Type.name())
|
||||||
|
.ok()?;
|
||||||
|
let s = s.to_str().ok()?;
|
||||||
|
Some(s.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn call_table_tostring_metamethod<'a>(tab: &'a LuaTable<'a>) -> Option<String> {
|
pub fn call_table_tostring_metamethod<'a>(tab: &'a LuaTable<'a>) -> Option<String> {
|
||||||
let f = match tab.get_metatable() {
|
tab.get_metatable()?
|
||||||
None => None,
|
.get::<_, LuaFunction>(LuaMetaMethod::ToString.name())
|
||||||
Some(meta) => match meta.get::<_, LuaFunction>(LuaMetaMethod::ToString.name()) {
|
.ok()?
|
||||||
Ok(method) => Some(method),
|
.call(tab)
|
||||||
Err(_) => None,
|
.ok()
|
||||||
},
|
|
||||||
}?;
|
|
||||||
match f.call::<_, String>(()) {
|
|
||||||
Ok(res) => Some(res),
|
|
||||||
Err(_) => None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn call_userdata_tostring_metamethod<'a>(tab: &'a LuaAnyUserData<'a>) -> Option<String> {
|
pub fn call_userdata_tostring_metamethod<'a>(tab: &'a LuaAnyUserData<'a>) -> Option<String> {
|
||||||
let f = match tab.get_metatable() {
|
tab.get_metatable()
|
||||||
Err(_) => None,
|
.ok()?
|
||||||
Ok(meta) => match meta.get::<LuaFunction>(LuaMetaMethod::ToString.name()) {
|
.get::<LuaFunction>(LuaMetaMethod::ToString.name())
|
||||||
Ok(method) => Some(method),
|
.ok()?
|
||||||
Err(_) => None,
|
.call(tab)
|
||||||
},
|
.ok()
|
||||||
}?;
|
|
||||||
match f.call::<_, String>(()) {
|
|
||||||
Ok(res) => Some(res),
|
|
||||||
Err(_) => None,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
use std::{
|
use std::{collections::HashSet, sync::Arc};
|
||||||
collections::HashSet,
|
|
||||||
sync::{Arc, Mutex},
|
|
||||||
};
|
|
||||||
|
|
||||||
use console::{colors_enabled as get_colors_enabled, set_colors_enabled};
|
use console::{colors_enabled as get_colors_enabled, set_colors_enabled};
|
||||||
use mlua::prelude::*;
|
use mlua::prelude::*;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
use parking_lot::ReentrantMutex;
|
||||||
|
|
||||||
mod basic;
|
mod basic;
|
||||||
mod config;
|
mod config;
|
||||||
|
@ -20,7 +18,7 @@ pub use self::config::ValueFormatConfig;
|
||||||
// NOTE: Since the setting for colors being enabled is global,
|
// NOTE: Since the setting for colors being enabled is global,
|
||||||
// and these functions may be called in parallel, we use this global
|
// and these functions may be called in parallel, we use this global
|
||||||
// lock to make sure that we don't mess up the colors for other threads.
|
// lock to make sure that we don't mess up the colors for other threads.
|
||||||
static COLORS_LOCK: Lazy<Arc<Mutex<()>>> = Lazy::new(|| Arc::new(Mutex::new(())));
|
static COLORS_LOCK: Lazy<Arc<ReentrantMutex<()>>> = Lazy::new(|| Arc::new(ReentrantMutex::new(())));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Formats a Lua value into a pretty string using the given config.
|
Formats a Lua value into a pretty string using the given config.
|
||||||
|
@ -28,7 +26,7 @@ static COLORS_LOCK: Lazy<Arc<Mutex<()>>> = Lazy::new(|| Arc::new(Mutex::new(()))
|
||||||
#[must_use]
|
#[must_use]
|
||||||
#[allow(clippy::missing_panics_doc)]
|
#[allow(clippy::missing_panics_doc)]
|
||||||
pub fn pretty_format_value(value: &LuaValue, config: &ValueFormatConfig) -> String {
|
pub fn pretty_format_value(value: &LuaValue, config: &ValueFormatConfig) -> String {
|
||||||
let _guard = COLORS_LOCK.lock().unwrap();
|
let _guard = COLORS_LOCK.lock();
|
||||||
|
|
||||||
let were_colors_enabled = get_colors_enabled();
|
let were_colors_enabled = get_colors_enabled();
|
||||||
set_colors_enabled(were_colors_enabled && config.colors_enabled);
|
set_colors_enabled(were_colors_enabled && config.colors_enabled);
|
||||||
|
@ -48,7 +46,7 @@ pub fn pretty_format_value(value: &LuaValue, config: &ValueFormatConfig) -> Stri
|
||||||
#[must_use]
|
#[must_use]
|
||||||
#[allow(clippy::missing_panics_doc)]
|
#[allow(clippy::missing_panics_doc)]
|
||||||
pub fn pretty_format_multi_value(values: &LuaMultiValue, config: &ValueFormatConfig) -> String {
|
pub fn pretty_format_multi_value(values: &LuaMultiValue, config: &ValueFormatConfig) -> String {
|
||||||
let _guard = COLORS_LOCK.lock().unwrap();
|
let _guard = COLORS_LOCK.lock();
|
||||||
|
|
||||||
let were_colors_enabled = get_colors_enabled();
|
let were_colors_enabled = get_colors_enabled();
|
||||||
set_colors_enabled(were_colors_enabled && config.colors_enabled);
|
set_colors_enabled(were_colors_enabled && config.colors_enabled);
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
|
use std::cmp::Ordering;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::fmt::{self, Write as _};
|
use std::fmt::{self, Write as _};
|
||||||
|
|
||||||
use mlua::prelude::*;
|
use mlua::prelude::*;
|
||||||
|
|
||||||
|
use super::metamethods::{call_table_tostring_metamethod, get_table_type_metavalue};
|
||||||
use super::{
|
use super::{
|
||||||
basic::{format_value_styled, lua_value_as_plain_string_key},
|
basic::{format_value_styled, lua_value_as_plain_string_key},
|
||||||
config::ValueFormatConfig,
|
config::ValueFormatConfig,
|
||||||
|
@ -45,40 +47,50 @@ pub(crate) fn format_value_recursive(
|
||||||
let mut buffer = String::new();
|
let mut buffer = String::new();
|
||||||
|
|
||||||
if let LuaValue::Table(ref t) = value {
|
if let LuaValue::Table(ref t) = value {
|
||||||
if depth >= config.max_depth {
|
if let Some(formatted) = format_typename_and_tostringed(
|
||||||
|
get_table_type_metavalue(t),
|
||||||
|
call_table_tostring_metamethod(t),
|
||||||
|
) {
|
||||||
|
write!(buffer, "{formatted}")?;
|
||||||
|
} else if depth >= config.max_depth {
|
||||||
write!(buffer, "{}", STYLE_DIM.apply_to("{ ... }"))?;
|
write!(buffer, "{}", STYLE_DIM.apply_to("{ ... }"))?;
|
||||||
} else if !visited.insert(LuaValueId::from(t)) {
|
} else if !visited.insert(LuaValueId::from(t)) {
|
||||||
write!(buffer, "{}", STYLE_DIM.apply_to("{ recursive }"))?;
|
write!(buffer, "{}", STYLE_DIM.apply_to("{ recursive }"))?;
|
||||||
} else {
|
} else {
|
||||||
writeln!(buffer, "{}", STYLE_DIM.apply_to("{"))?;
|
write!(buffer, "{}", STYLE_DIM.apply_to("{"))?;
|
||||||
|
|
||||||
for res in t.clone().pairs::<LuaValue, LuaValue>() {
|
let mut values = t
|
||||||
let (key, value) = res.expect("conversion to LuaValue should never fail");
|
.clone()
|
||||||
let formatted = if let Some(plain_key) = lua_value_as_plain_string_key(&key) {
|
.pairs::<LuaValue, LuaValue>()
|
||||||
format!(
|
.map(|res| res.expect("conversion to LuaValue should never fail"))
|
||||||
"{}{plain_key} {} {}{}",
|
.collect::<Vec<_>>();
|
||||||
INDENT.repeat(1 + depth),
|
sort_for_formatting(&mut values);
|
||||||
STYLE_DIM.apply_to("="),
|
|
||||||
format_value_recursive(&value, config, visited, depth + 1)?,
|
let is_empty = values.is_empty();
|
||||||
STYLE_DIM.apply_to(","),
|
let is_array = values
|
||||||
)
|
.iter()
|
||||||
} else {
|
.enumerate()
|
||||||
format!(
|
.all(|(i, (key, _))| key.as_integer().is_some_and(|x| x == (i as i32) + 1));
|
||||||
"{}{}{}{} {} {}{}",
|
|
||||||
INDENT.repeat(1 + depth),
|
let formatted_values = if is_array {
|
||||||
STYLE_DIM.apply_to("["),
|
format_array(values, config, visited, depth)?
|
||||||
format_value_recursive(&key, config, visited, depth + 1)?,
|
} else {
|
||||||
STYLE_DIM.apply_to("]"),
|
format_table(values, config, visited, depth)?
|
||||||
STYLE_DIM.apply_to("="),
|
};
|
||||||
format_value_recursive(&value, config, visited, depth + 1)?,
|
|
||||||
STYLE_DIM.apply_to(","),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
buffer.push_str(&formatted);
|
|
||||||
}
|
|
||||||
|
|
||||||
visited.remove(&LuaValueId::from(t));
|
visited.remove(&LuaValueId::from(t));
|
||||||
write!(buffer, "\n{}", STYLE_DIM.apply_to("}"))?;
|
|
||||||
|
if is_empty {
|
||||||
|
write!(buffer, " {}", STYLE_DIM.apply_to("}"))?;
|
||||||
|
} else {
|
||||||
|
write!(
|
||||||
|
buffer,
|
||||||
|
"\n{}\n{}{}",
|
||||||
|
formatted_values.join("\n"),
|
||||||
|
INDENT.repeat(depth),
|
||||||
|
STYLE_DIM.apply_to("}")
|
||||||
|
)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let prefer_plain = depth == 0;
|
let prefer_plain = depth == 0;
|
||||||
|
@ -87,3 +99,86 @@ pub(crate) fn format_value_recursive(
|
||||||
|
|
||||||
Ok(buffer)
|
Ok(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sort_for_formatting(values: &mut [(LuaValue, LuaValue)]) {
|
||||||
|
values.sort_by(|(a, _), (b, _)| {
|
||||||
|
if a.type_name() == b.type_name() {
|
||||||
|
// If we have the same type, sort either numerically or alphabetically
|
||||||
|
match (a, b) {
|
||||||
|
(LuaValue::Integer(a), LuaValue::Integer(b)) => a.cmp(b),
|
||||||
|
(LuaValue::Number(a), LuaValue::Number(b)) => a.partial_cmp(b).unwrap(),
|
||||||
|
(LuaValue::String(a), LuaValue::String(b)) => a.to_str().ok().cmp(&b.to_str().ok()),
|
||||||
|
_ => Ordering::Equal,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If we have different types, sort numbers first, then strings, then others
|
||||||
|
a.is_number()
|
||||||
|
.cmp(&b.is_number())
|
||||||
|
.then_with(|| a.is_string().cmp(&b.is_string()))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_array(
|
||||||
|
values: Vec<(LuaValue, LuaValue)>,
|
||||||
|
config: &ValueFormatConfig,
|
||||||
|
visited: &mut HashSet<LuaValueId>,
|
||||||
|
depth: usize,
|
||||||
|
) -> Result<Vec<String>, fmt::Error> {
|
||||||
|
values
|
||||||
|
.into_iter()
|
||||||
|
.map(|(_, value)| {
|
||||||
|
Ok(format!(
|
||||||
|
"{}{}{}",
|
||||||
|
INDENT.repeat(1 + depth),
|
||||||
|
format_value_recursive(&value, config, visited, depth + 1)?,
|
||||||
|
STYLE_DIM.apply_to(","),
|
||||||
|
))
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_table(
|
||||||
|
values: Vec<(LuaValue, LuaValue)>,
|
||||||
|
config: &ValueFormatConfig,
|
||||||
|
visited: &mut HashSet<LuaValueId>,
|
||||||
|
depth: usize,
|
||||||
|
) -> Result<Vec<String>, fmt::Error> {
|
||||||
|
values
|
||||||
|
.into_iter()
|
||||||
|
.map(|(key, value)| {
|
||||||
|
if let Some(plain_key) = lua_value_as_plain_string_key(&key) {
|
||||||
|
Ok(format!(
|
||||||
|
"{}{plain_key} {} {}{}",
|
||||||
|
INDENT.repeat(1 + depth),
|
||||||
|
STYLE_DIM.apply_to("="),
|
||||||
|
format_value_recursive(&value, config, visited, depth + 1)?,
|
||||||
|
STYLE_DIM.apply_to(","),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Ok(format!(
|
||||||
|
"{}{}{}{} {} {}{}",
|
||||||
|
INDENT.repeat(1 + depth),
|
||||||
|
STYLE_DIM.apply_to("["),
|
||||||
|
format_value_recursive(&key, config, visited, depth + 1)?,
|
||||||
|
STYLE_DIM.apply_to("]"),
|
||||||
|
STYLE_DIM.apply_to("="),
|
||||||
|
format_value_recursive(&value, config, visited, depth + 1)?,
|
||||||
|
STYLE_DIM.apply_to(","),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_typename_and_tostringed(
|
||||||
|
typename: Option<String>,
|
||||||
|
tostringed: Option<String>,
|
||||||
|
) -> Option<String> {
|
||||||
|
match (typename, tostringed) {
|
||||||
|
(Some(typename), Some(tostringed)) => Some(format!("<{typename}({tostringed})>")),
|
||||||
|
(Some(typename), None) => Some(format!("<{typename}>")),
|
||||||
|
(None, Some(tostringed)) => Some(tostringed),
|
||||||
|
(None, None) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
30
crates/lune-utils/src/jit.rs
Normal file
30
crates/lune-utils/src/jit.rs
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
|
pub struct JitStatus(bool);
|
||||||
|
|
||||||
|
impl JitStatus {
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(enabled: bool) -> Self {
|
||||||
|
Self(enabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_status(&mut self, enabled: bool) {
|
||||||
|
self.0 = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn enabled(self) -> bool {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<JitStatus> for bool {
|
||||||
|
fn from(val: JitStatus) -> Self {
|
||||||
|
val.enabled()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<bool> for JitStatus {
|
||||||
|
fn from(val: bool) -> Self {
|
||||||
|
Self::new(val)
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ mod table_builder;
|
||||||
mod version_string;
|
mod version_string;
|
||||||
|
|
||||||
pub mod fmt;
|
pub mod fmt;
|
||||||
|
pub mod jit;
|
||||||
pub mod path;
|
pub mod path;
|
||||||
|
|
||||||
pub use self::table_builder::TableBuilder;
|
pub use self::table_builder::TableBuilder;
|
||||||
|
|
|
@ -2,6 +2,7 @@ use std::sync::Arc;
|
||||||
|
|
||||||
use mlua::prelude::*;
|
use mlua::prelude::*;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
use semver::Version;
|
||||||
|
|
||||||
static LUAU_VERSION: Lazy<Arc<String>> = Lazy::new(create_luau_version_string);
|
static LUAU_VERSION: Lazy<Arc<String>> = Lazy::new(create_luau_version_string);
|
||||||
|
|
||||||
|
@ -20,12 +21,10 @@ pub fn get_version_string(lune_version: impl AsRef<str>) -> String {
|
||||||
let lune_version = lune_version.as_ref();
|
let lune_version = lune_version.as_ref();
|
||||||
|
|
||||||
assert!(!lune_version.is_empty(), "Lune version string is empty");
|
assert!(!lune_version.is_empty(), "Lune version string is empty");
|
||||||
assert!(
|
match Version::parse(lune_version) {
|
||||||
lune_version.chars().all(is_valid_version_char),
|
Ok(semver) => format!("Lune {semver}+{}", *LUAU_VERSION),
|
||||||
"Lune version string contains invalid characters"
|
Err(e) => panic!("Lune version string is not valid semver: {e}"),
|
||||||
);
|
}
|
||||||
|
|
||||||
format!("Lune {lune_version}+{}", *LUAU_VERSION)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_luau_version_string() -> Arc<String> {
|
fn create_luau_version_string() -> Arc<String> {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "lune"
|
name = "lune"
|
||||||
version = "0.8.4"
|
version = "0.8.9"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
repository = "https://github.com/lune-org/lune"
|
repository = "https://github.com/lune-org/lune"
|
||||||
|
@ -50,8 +50,8 @@ cli = ["dep:clap", "dep:include_dir", "dep:rustyline", "dep:zip_next"]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
mlua = { version = "0.9.7", features = ["luau"] }
|
mlua = { version = "0.9.9", features = ["luau"] }
|
||||||
mlua-luau-scheduler = "0.0.2"
|
mlua-luau-scheduler = { version = "0.0.2", path = "../mlua-luau-scheduler" }
|
||||||
|
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
console = "0.15"
|
console = "0.15"
|
||||||
|
@ -59,6 +59,7 @@ dialoguer = "0.11"
|
||||||
directories = "5.0"
|
directories = "5.0"
|
||||||
futures-util = "0.3"
|
futures-util = "0.3"
|
||||||
once_cell = "1.17"
|
once_cell = "1.17"
|
||||||
|
self_cell = "1.0"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
|
@ -70,9 +71,9 @@ reqwest = { version = "0.11", default-features = false, features = [
|
||||||
"rustls-tls",
|
"rustls-tls",
|
||||||
] }
|
] }
|
||||||
|
|
||||||
lune-std = { optional = true, version = "0.1.1", path = "../lune-std" }
|
lune-std = { optional = true, version = "0.1.5", path = "../lune-std" }
|
||||||
lune-roblox = { optional = true, version = "0.1.0", path = "../lune-roblox" }
|
lune-roblox = { optional = true, version = "0.1.4", path = "../lune-roblox" }
|
||||||
lune-utils = { version = "0.1.0", path = "../lune-utils" }
|
lune-utils = { version = "0.1.3", path = "../lune-utils" }
|
||||||
|
|
||||||
### CLI
|
### CLI
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::process::ExitCode;
|
use std::{env, process::ExitCode};
|
||||||
|
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
@ -40,17 +40,27 @@ impl RunCommand {
|
||||||
(file_display_name, file_contents)
|
(file_display_name, file_contents)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create a new lune object with all globals & run the script
|
// Create a new lune runtime with all globals & run the script
|
||||||
let result = Runtime::new()
|
let mut rt = Runtime::new()
|
||||||
.with_args(self.script_args)
|
.with_args(self.script_args)
|
||||||
|
// Enable JIT compilation unless it was requested to be disabled
|
||||||
|
.with_jit(
|
||||||
|
!matches!(
|
||||||
|
env::var("LUNE_LUAU_JIT").ok(),
|
||||||
|
Some(jit_enabled) if jit_enabled == "0" || jit_enabled == "false" || jit_enabled == "off"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
let result = rt
|
||||||
.run(&script_display_name, strip_shebang(script_contents))
|
.run(&script_display_name, strip_shebang(script_contents))
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
Ok(match result {
|
Ok(match result {
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!("{err}");
|
eprintln!("{err}");
|
||||||
ExitCode::FAILURE
|
ExitCode::FAILURE
|
||||||
}
|
}
|
||||||
Ok(code) => code,
|
Ok((code, _)) => ExitCode::from(code),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,8 +64,8 @@ pub fn discover_script_path(path: impl AsRef<str>, in_home_dir: bool) -> Result<
|
||||||
// NOTE: We use metadata directly here to try to
|
// NOTE: We use metadata directly here to try to
|
||||||
// avoid accessing the file path more than once
|
// avoid accessing the file path more than once
|
||||||
let file_meta = file_path.metadata();
|
let file_meta = file_path.metadata();
|
||||||
let is_file = file_meta.as_ref().map_or(false, Metadata::is_file);
|
let is_file = file_meta.as_ref().is_ok_and(Metadata::is_file);
|
||||||
let is_dir = file_meta.as_ref().map_or(false, Metadata::is_dir);
|
let is_dir = file_meta.as_ref().is_ok_and(Metadata::is_dir);
|
||||||
let is_abs = file_path.is_absolute();
|
let is_abs = file_path.is_absolute();
|
||||||
let ext = file_path.extension();
|
let ext = file_path.extension();
|
||||||
if is_file {
|
if is_file {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#![allow(clippy::missing_panics_doc)]
|
#![allow(clippy::missing_panics_doc)]
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
process::ExitCode,
|
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{AtomicBool, Ordering},
|
atomic::{AtomicBool, Ordering},
|
||||||
|
@ -9,15 +8,100 @@ use std::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use mlua::Lua;
|
use lune_utils::jit::JitStatus;
|
||||||
use mlua_luau_scheduler::Scheduler;
|
use mlua::prelude::*;
|
||||||
|
use mlua_luau_scheduler::{Functions, Scheduler};
|
||||||
|
use self_cell::self_cell;
|
||||||
|
|
||||||
use super::{RuntimeError, RuntimeResult};
|
use super::{RuntimeError, RuntimeResult};
|
||||||
|
|
||||||
#[derive(Debug)]
|
// NOTE: We need to use self_cell to create a self-referential
|
||||||
|
// struct storing both the Lua VM and the scheduler. The scheduler
|
||||||
|
// needs to be created at the same time so that we can also create
|
||||||
|
// and inject the scheduler functions which will be used across runs.
|
||||||
|
self_cell! {
|
||||||
|
struct RuntimeInner {
|
||||||
|
owner: Rc<Lua>,
|
||||||
|
#[covariant]
|
||||||
|
dependent: Scheduler,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RuntimeInner {
|
||||||
|
fn create() -> LuaResult<Self> {
|
||||||
|
let lua = Rc::new(Lua::new());
|
||||||
|
|
||||||
|
lua.set_app_data(Rc::downgrade(&lua));
|
||||||
|
lua.set_app_data(Vec::<String>::new());
|
||||||
|
|
||||||
|
Self::try_new(lua, |lua| {
|
||||||
|
let sched = Scheduler::new(lua);
|
||||||
|
let fns = Functions::new(lua)?;
|
||||||
|
|
||||||
|
// Overwrite some globals that are not compatible with our scheduler
|
||||||
|
let co = lua.globals().get::<_, LuaTable>("coroutine")?;
|
||||||
|
co.set("resume", fns.resume.clone())?;
|
||||||
|
co.set("wrap", fns.wrap.clone())?;
|
||||||
|
|
||||||
|
// Inject all the globals that are enabled
|
||||||
|
#[cfg(any(
|
||||||
|
feature = "std-datetime",
|
||||||
|
feature = "std-fs",
|
||||||
|
feature = "std-luau",
|
||||||
|
feature = "std-net",
|
||||||
|
feature = "std-process",
|
||||||
|
feature = "std-regex",
|
||||||
|
feature = "std-roblox",
|
||||||
|
feature = "std-serde",
|
||||||
|
feature = "std-stdio",
|
||||||
|
feature = "std-task",
|
||||||
|
))]
|
||||||
|
{
|
||||||
|
lune_std::set_global_version(lua, env!("CARGO_PKG_VERSION"));
|
||||||
|
lune_std::inject_globals(lua)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sandbox the Luau VM and make it go zooooooooom
|
||||||
|
lua.sandbox(true)?;
|
||||||
|
|
||||||
|
// _G table needs to be injected again after sandboxing,
|
||||||
|
// otherwise it will be read-only and completely unusable
|
||||||
|
#[cfg(any(
|
||||||
|
feature = "std-datetime",
|
||||||
|
feature = "std-fs",
|
||||||
|
feature = "std-luau",
|
||||||
|
feature = "std-net",
|
||||||
|
feature = "std-process",
|
||||||
|
feature = "std-regex",
|
||||||
|
feature = "std-roblox",
|
||||||
|
feature = "std-serde",
|
||||||
|
feature = "std-stdio",
|
||||||
|
feature = "std-task",
|
||||||
|
))]
|
||||||
|
{
|
||||||
|
let g_table = lune_std::LuneStandardGlobal::GTable;
|
||||||
|
lua.globals().set(g_table.name(), g_table.create(lua)?)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(sched)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn lua(&self) -> &Lua {
|
||||||
|
self.borrow_owner()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scheduler(&self) -> &Scheduler {
|
||||||
|
self.borrow_dependent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
A Lune runtime.
|
||||||
|
*/
|
||||||
pub struct Runtime {
|
pub struct Runtime {
|
||||||
lua: Rc<Lua>,
|
inner: RuntimeInner,
|
||||||
args: Vec<String>,
|
jit_status: JitStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Runtime {
|
impl Runtime {
|
||||||
|
@ -29,30 +113,9 @@ impl Runtime {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
#[allow(clippy::new_without_default)]
|
#[allow(clippy::new_without_default)]
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let lua = Rc::new(Lua::new());
|
|
||||||
|
|
||||||
lua.set_app_data(Rc::downgrade(&lua));
|
|
||||||
lua.set_app_data(Vec::<String>::new());
|
|
||||||
|
|
||||||
#[cfg(any(
|
|
||||||
feature = "std-datetime",
|
|
||||||
feature = "std-fs",
|
|
||||||
feature = "std-luau",
|
|
||||||
feature = "std-net",
|
|
||||||
feature = "std-process",
|
|
||||||
feature = "std-regex",
|
|
||||||
feature = "std-roblox",
|
|
||||||
feature = "std-serde",
|
|
||||||
feature = "std-stdio",
|
|
||||||
feature = "std-task",
|
|
||||||
))]
|
|
||||||
{
|
|
||||||
lune_std::inject_globals(&lua).expect("Failed to inject globals");
|
|
||||||
}
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
lua,
|
inner: RuntimeInner::create().expect("Failed to create runtime"),
|
||||||
args: Vec::new(),
|
jit_status: JitStatus::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,12 +123,22 @@ impl Runtime {
|
||||||
Sets arguments to give in `process.args` for Lune scripts.
|
Sets arguments to give in `process.args` for Lune scripts.
|
||||||
*/
|
*/
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn with_args<V>(mut self, args: V) -> Self
|
pub fn with_args<A, S>(self, args: A) -> Self
|
||||||
where
|
where
|
||||||
V: Into<Vec<String>>,
|
A: IntoIterator<Item = S>,
|
||||||
|
S: Into<String>,
|
||||||
{
|
{
|
||||||
self.args = args.into();
|
let args = args.into_iter().map(Into::into).collect::<Vec<_>>();
|
||||||
self.lua.set_app_data(self.args.clone());
|
self.inner.lua().set_app_data(args);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Enables or disables JIT compilation.
|
||||||
|
*/
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_jit(mut self, jit_status: impl Into<JitStatus>) -> Self {
|
||||||
|
self.jit_status = jit_status.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,35 +155,41 @@ impl Runtime {
|
||||||
&mut self,
|
&mut self,
|
||||||
script_name: impl AsRef<str>,
|
script_name: impl AsRef<str>,
|
||||||
script_contents: impl AsRef<[u8]>,
|
script_contents: impl AsRef<[u8]>,
|
||||||
) -> RuntimeResult<ExitCode> {
|
) -> RuntimeResult<(u8, Vec<LuaValue>)> {
|
||||||
// Create a new scheduler for this run
|
let lua = self.inner.lua();
|
||||||
let sched = Scheduler::new(&self.lua);
|
let sched = self.inner.scheduler();
|
||||||
|
|
||||||
// Add error callback to format errors nicely + store status
|
// Add error callback to format errors nicely + store status
|
||||||
let got_any_error = Arc::new(AtomicBool::new(false));
|
let got_any_error = Arc::new(AtomicBool::new(false));
|
||||||
let got_any_inner = Arc::clone(&got_any_error);
|
let got_any_inner = Arc::clone(&got_any_error);
|
||||||
sched.set_error_callback(move |e| {
|
self.inner.scheduler().set_error_callback(move |e| {
|
||||||
got_any_inner.store(true, Ordering::SeqCst);
|
got_any_inner.store(true, Ordering::SeqCst);
|
||||||
eprintln!("{}", RuntimeError::from(e));
|
eprintln!("{}", RuntimeError::from(e));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Enable / disable the JIT as requested and store the current status as AppData
|
||||||
|
lua.set_app_data(self.jit_status);
|
||||||
|
lua.enable_jit(self.jit_status.enabled());
|
||||||
|
|
||||||
// Load our "main" thread
|
// Load our "main" thread
|
||||||
let main = self
|
let main = lua
|
||||||
.lua
|
|
||||||
.load(script_contents.as_ref())
|
.load(script_contents.as_ref())
|
||||||
.set_name(script_name.as_ref());
|
.set_name(script_name.as_ref());
|
||||||
|
|
||||||
// Run it on our scheduler until it and any other spawned threads complete
|
// Run it on our scheduler until it and any other spawned threads complete
|
||||||
sched.push_thread_back(main, ())?;
|
let main_thread_id = sched.push_thread_back(main, ())?;
|
||||||
sched.run().await;
|
sched.run().await;
|
||||||
|
|
||||||
// Return the exit code - default to FAILURE if we got any errors
|
let main_thread_res = match sched.get_thread_result(main_thread_id) {
|
||||||
Ok(sched.get_exit_code().unwrap_or({
|
Some(res) => res,
|
||||||
if got_any_error.load(Ordering::SeqCst) {
|
None => LuaValue::Nil.into_lua_multi(lua),
|
||||||
ExitCode::FAILURE
|
}?;
|
||||||
} else {
|
|
||||||
ExitCode::SUCCESS
|
Ok((
|
||||||
}
|
sched
|
||||||
}))
|
.get_exit_code()
|
||||||
|
.unwrap_or(u8::from(got_any_error.load(Ordering::SeqCst))),
|
||||||
|
main_thread_res.into_vec(),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,16 +29,15 @@ pub async fn run(patched_bin: impl AsRef<[u8]>) -> Result<ExitCode> {
|
||||||
let args = env::args().skip(1).collect::<Vec<_>>();
|
let args = env::args().skip(1).collect::<Vec<_>>();
|
||||||
let meta = Metadata::from_bytes(patched_bin).expect("must be a standalone binary");
|
let meta = Metadata::from_bytes(patched_bin).expect("must be a standalone binary");
|
||||||
|
|
||||||
let result = Runtime::new()
|
let mut rt = Runtime::new().with_args(args);
|
||||||
.with_args(args)
|
|
||||||
.run("STANDALONE", meta.bytecode)
|
let result = rt.run("STANDALONE", meta.bytecode).await;
|
||||||
.await;
|
|
||||||
|
|
||||||
Ok(match result {
|
Ok(match result {
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
eprintln!("{err}");
|
eprintln!("{err}");
|
||||||
ExitCode::FAILURE
|
ExitCode::FAILURE
|
||||||
}
|
}
|
||||||
Ok(code) => code,
|
Ok((code, _)) => ExitCode::from(code),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,19 +31,21 @@ macro_rules! create_tests {
|
||||||
// The rest of the test logic can continue as normal
|
// The rest of the test logic can continue as normal
|
||||||
let full_name = format!("{}/tests/{}.luau", workspace_dir.display(), $value);
|
let full_name = format!("{}/tests/{}.luau", workspace_dir.display(), $value);
|
||||||
let script = read_to_string(&full_name).await?;
|
let script = read_to_string(&full_name).await?;
|
||||||
let mut lune = Runtime::new().with_args(
|
let mut lune = Runtime::new()
|
||||||
ARGS
|
.with_jit(true)
|
||||||
.clone()
|
.with_args(
|
||||||
.iter()
|
ARGS
|
||||||
.map(ToString::to_string)
|
.clone()
|
||||||
.collect::<Vec<_>>()
|
.iter()
|
||||||
);
|
.map(ToString::to_string)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
);
|
||||||
let script_name = full_name
|
let script_name = full_name
|
||||||
.trim_end_matches(".luau")
|
.trim_end_matches(".luau")
|
||||||
.trim_end_matches(".lua")
|
.trim_end_matches(".lua")
|
||||||
.to_string();
|
.to_string();
|
||||||
let exit_code = lune.run(&script_name, &script).await?;
|
let (exit_code, _) = lune.run(&script_name, &script).await?;
|
||||||
Ok(exit_code)
|
Ok(ExitCode::from(exit_code))
|
||||||
}
|
}
|
||||||
)* }
|
)* }
|
||||||
}
|
}
|
||||||
|
@ -113,6 +115,7 @@ create_tests! {
|
||||||
luau_compile: "luau/compile",
|
luau_compile: "luau/compile",
|
||||||
luau_load: "luau/load",
|
luau_load: "luau/load",
|
||||||
luau_options: "luau/options",
|
luau_options: "luau/options",
|
||||||
|
luau_safeenv: "luau/safeenv",
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "std-net")]
|
#[cfg(feature = "std-net")]
|
||||||
|
@ -137,12 +140,16 @@ create_tests! {
|
||||||
process_cwd: "process/cwd",
|
process_cwd: "process/cwd",
|
||||||
process_env: "process/env",
|
process_env: "process/env",
|
||||||
process_exit: "process/exit",
|
process_exit: "process/exit",
|
||||||
process_spawn_async: "process/spawn/async",
|
process_exec_async: "process/exec/async",
|
||||||
process_spawn_basic: "process/spawn/basic",
|
process_exec_basic: "process/exec/basic",
|
||||||
process_spawn_cwd: "process/spawn/cwd",
|
process_exec_cwd: "process/exec/cwd",
|
||||||
process_spawn_shell: "process/spawn/shell",
|
process_exec_no_panic: "process/exec/no_panic",
|
||||||
process_spawn_stdin: "process/spawn/stdin",
|
process_exec_shell: "process/exec/shell",
|
||||||
process_spawn_stdio: "process/spawn/stdio",
|
process_exec_stdin: "process/exec/stdin",
|
||||||
|
process_exec_stdio: "process/exec/stdio",
|
||||||
|
process_spawn_non_blocking: "process/create/non_blocking",
|
||||||
|
process_spawn_status: "process/create/status",
|
||||||
|
process_spawn_stream: "process/create/stream",
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "std-regex")]
|
#[cfg(feature = "std-regex")]
|
||||||
|
@ -160,6 +167,7 @@ create_tests! {
|
||||||
roblox_datatype_color3: "roblox/datatypes/Color3",
|
roblox_datatype_color3: "roblox/datatypes/Color3",
|
||||||
roblox_datatype_color_sequence: "roblox/datatypes/ColorSequence",
|
roblox_datatype_color_sequence: "roblox/datatypes/ColorSequence",
|
||||||
roblox_datatype_color_sequence_keypoint: "roblox/datatypes/ColorSequenceKeypoint",
|
roblox_datatype_color_sequence_keypoint: "roblox/datatypes/ColorSequenceKeypoint",
|
||||||
|
roblox_datatype_content: "roblox/datatypes/Content",
|
||||||
roblox_datatype_enum: "roblox/datatypes/Enum",
|
roblox_datatype_enum: "roblox/datatypes/Enum",
|
||||||
roblox_datatype_faces: "roblox/datatypes/Faces",
|
roblox_datatype_faces: "roblox/datatypes/Faces",
|
||||||
roblox_datatype_font: "roblox/datatypes/Font",
|
roblox_datatype_font: "roblox/datatypes/Font",
|
||||||
|
@ -229,6 +237,8 @@ create_tests! {
|
||||||
serde_json_encode: "serde/json/encode",
|
serde_json_encode: "serde/json/encode",
|
||||||
serde_toml_decode: "serde/toml/decode",
|
serde_toml_decode: "serde/toml/decode",
|
||||||
serde_toml_encode: "serde/toml/encode",
|
serde_toml_encode: "serde/toml/encode",
|
||||||
|
serde_hashing_hash: "serde/hashing/hash",
|
||||||
|
serde_hashing_hmac: "serde/hashing/hmac",
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "std-stdio")]
|
#[cfg(feature = "std-stdio")]
|
||||||
|
|
67
crates/mlua-luau-scheduler/Cargo.toml
Normal file
67
crates/mlua-luau-scheduler/Cargo.toml
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
[package]
|
||||||
|
name = "mlua-luau-scheduler"
|
||||||
|
version = "0.0.2"
|
||||||
|
edition = "2021"
|
||||||
|
license = "MPL-2.0"
|
||||||
|
repository = "https://github.com/lune-org/lune"
|
||||||
|
description = "Luau-based async scheduler, using mlua and async-executor"
|
||||||
|
readme = "README.md"
|
||||||
|
keywords = ["async", "luau", "scheduler"]
|
||||||
|
categories = ["async"]
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
async-executor = "1.8"
|
||||||
|
blocking = "1.5"
|
||||||
|
concurrent-queue = "2.4"
|
||||||
|
derive_more = "0.99"
|
||||||
|
event-listener = "4.0"
|
||||||
|
futures-lite = "2.2"
|
||||||
|
rustc-hash = "1.1"
|
||||||
|
tracing = "0.1"
|
||||||
|
|
||||||
|
mlua = { version = "0.9.9", features = [
|
||||||
|
"luau",
|
||||||
|
"luau-jit",
|
||||||
|
"async",
|
||||||
|
"serialize",
|
||||||
|
] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
async-fs = "2.1"
|
||||||
|
async-io = "2.3"
|
||||||
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
tracing-tracy = "0.11"
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "basic_sleep"
|
||||||
|
test = true
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "basic_spawn"
|
||||||
|
test = true
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "callbacks"
|
||||||
|
test = true
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "exit_code"
|
||||||
|
test = true
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "lots_of_threads"
|
||||||
|
test = true
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "scheduler_ordering"
|
||||||
|
test = true
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "tracy"
|
||||||
|
test = false
|
78
crates/mlua-luau-scheduler/README.md
Normal file
78
crates/mlua-luau-scheduler/README.md
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
<!-- markdownlint-disable MD033 -->
|
||||||
|
<!-- markdownlint-disable MD041 -->
|
||||||
|
|
||||||
|
# `mlua-luau-scheduler`
|
||||||
|
|
||||||
|
An async scheduler for Luau, using [`mlua`][mlua] and built on top of [`async-executor`][async-executor].
|
||||||
|
|
||||||
|
This crate is runtime-agnostic and is compatible with any async runtime, including [Tokio][tokio], [smol][smol], [async-std][async-std], and others. </br>
|
||||||
|
However, since many dependencies are shared with [smol][smol], depending on it over other runtimes may be preferred.
|
||||||
|
|
||||||
|
[async-executor]: https://crates.io/crates/async-executor
|
||||||
|
[async-std]: https://async.rs
|
||||||
|
[mlua]: https://crates.io/crates/mlua
|
||||||
|
[smol]: https://github.com/smol-rs/smol
|
||||||
|
[tokio]: https://tokio.rs
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
### 1. Import dependencies
|
||||||
|
|
||||||
|
```rs
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
use std::io::ErrorKind;
|
||||||
|
|
||||||
|
use async_io::{block_on, Timer};
|
||||||
|
use async_fs::read_to_string;
|
||||||
|
|
||||||
|
use mlua::prelude::*;
|
||||||
|
use mlua_luau_scheduler::*;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Set up Lua environment
|
||||||
|
|
||||||
|
```rs
|
||||||
|
let lua = Lua::new();
|
||||||
|
|
||||||
|
lua.globals().set(
|
||||||
|
"sleep",
|
||||||
|
lua.create_async_function(|_, duration: f64| async move {
|
||||||
|
let before = Instant::now();
|
||||||
|
let after = Timer::after(Duration::from_secs_f64(duration)).await;
|
||||||
|
Ok((after - before).as_secs_f64())
|
||||||
|
})?,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
lua.globals().set(
|
||||||
|
"readFile",
|
||||||
|
lua.create_async_function(|lua, path: String| async move {
|
||||||
|
// Spawn background task that does not take up resources on the lua thread
|
||||||
|
// Normally, futures in mlua can not be shared across threads, but this can
|
||||||
|
let task = lua.spawn(async move {
|
||||||
|
match read_to_string(path).await {
|
||||||
|
Ok(s) => Ok(Some(s)),
|
||||||
|
Err(e) if e.kind() == ErrorKind::NotFound => Ok(None),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
task.await.into_lua_err()
|
||||||
|
})?,
|
||||||
|
)?;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Set up scheduler, run threads
|
||||||
|
|
||||||
|
```rs
|
||||||
|
let sched = Scheduler::new(&lua)?;
|
||||||
|
|
||||||
|
// We can create multiple lua threads ...
|
||||||
|
let sleepThread = lua.load("sleep(0.1)");
|
||||||
|
let fileThread = lua.load("readFile(\"Cargo.toml\")");
|
||||||
|
|
||||||
|
// ... spawn them both onto the scheduler ...
|
||||||
|
sched.push_thread_front(sleepThread, ());
|
||||||
|
sched.push_thread_front(fileThread, ());
|
||||||
|
|
||||||
|
// ... and run until they finish
|
||||||
|
block_on(sched.run());
|
||||||
|
```
|
45
crates/mlua-luau-scheduler/examples/basic_sleep.rs
Normal file
45
crates/mlua-luau-scheduler/examples/basic_sleep.rs
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
#![allow(clippy::missing_errors_doc)]
|
||||||
|
#![allow(clippy::cargo_common_metadata)]
|
||||||
|
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
use async_io::{block_on, Timer};
|
||||||
|
|
||||||
|
use mlua::prelude::*;
|
||||||
|
use mlua_luau_scheduler::Scheduler;
|
||||||
|
|
||||||
|
const MAIN_SCRIPT: &str = include_str!("./lua/basic_sleep.luau");
|
||||||
|
|
||||||
|
pub fn main() -> LuaResult<()> {
|
||||||
|
tracing_subscriber::fmt()
|
||||||
|
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
|
||||||
|
.with_target(false)
|
||||||
|
.without_time()
|
||||||
|
.init();
|
||||||
|
|
||||||
|
// Set up persistent Lua environment
|
||||||
|
let lua = Lua::new();
|
||||||
|
lua.globals().set(
|
||||||
|
"sleep",
|
||||||
|
lua.create_async_function(|_, duration: f64| async move {
|
||||||
|
let before = Instant::now();
|
||||||
|
let after = Timer::after(Duration::from_secs_f64(duration)).await;
|
||||||
|
Ok((after - before).as_secs_f64())
|
||||||
|
})?,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Load the main script into a scheduler
|
||||||
|
let sched = Scheduler::new(&lua);
|
||||||
|
let main = lua.load(MAIN_SCRIPT);
|
||||||
|
sched.push_thread_front(main, ())?;
|
||||||
|
|
||||||
|
// Run until completion
|
||||||
|
block_on(sched.run());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_basic_sleep() -> LuaResult<()> {
|
||||||
|
main()
|
||||||
|
}
|
64
crates/mlua-luau-scheduler/examples/basic_spawn.rs
Normal file
64
crates/mlua-luau-scheduler/examples/basic_spawn.rs
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
#![allow(clippy::missing_errors_doc)]
|
||||||
|
#![allow(clippy::cargo_common_metadata)]
|
||||||
|
|
||||||
|
use std::io::ErrorKind;
|
||||||
|
|
||||||
|
use async_fs::read_to_string;
|
||||||
|
use async_io::block_on;
|
||||||
|
|
||||||
|
use mlua::prelude::*;
|
||||||
|
use mlua_luau_scheduler::{LuaSpawnExt, Scheduler};
|
||||||
|
|
||||||
|
const MAIN_SCRIPT: &str = include_str!("./lua/basic_spawn.luau");
|
||||||
|
|
||||||
|
pub fn main() -> LuaResult<()> {
|
||||||
|
tracing_subscriber::fmt()
|
||||||
|
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
|
||||||
|
.with_target(false)
|
||||||
|
.without_time()
|
||||||
|
.init();
|
||||||
|
|
||||||
|
// Set up persistent Lua environment
|
||||||
|
let lua = Lua::new();
|
||||||
|
lua.globals().set(
|
||||||
|
"readFile",
|
||||||
|
lua.create_async_function(|lua, path: String| async move {
|
||||||
|
// Spawn background task that does not take up resources on the Lua thread
|
||||||
|
let task = lua.spawn(async move {
|
||||||
|
match read_to_string(path).await {
|
||||||
|
Ok(s) => Ok(Some(s)),
|
||||||
|
Err(e) if e.kind() == ErrorKind::NotFound => Ok(None),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Wait for it to complete
|
||||||
|
let result = task.await.into_lua_err();
|
||||||
|
|
||||||
|
// We can also spawn local tasks that do take up resources
|
||||||
|
// on the Lua thread, but that do not have the Send bound
|
||||||
|
if result.is_ok() {
|
||||||
|
lua.spawn_local(async move {
|
||||||
|
println!("File read successfully!");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
})?,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Load the main script into a scheduler
|
||||||
|
let sched = Scheduler::new(&lua);
|
||||||
|
let main = lua.load(MAIN_SCRIPT);
|
||||||
|
sched.push_thread_front(main, ())?;
|
||||||
|
|
||||||
|
// Run until completion
|
||||||
|
block_on(sched.run());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_basic_spawn() -> LuaResult<()> {
|
||||||
|
main()
|
||||||
|
}
|
48
crates/mlua-luau-scheduler/examples/callbacks.rs
Normal file
48
crates/mlua-luau-scheduler/examples/callbacks.rs
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
#![allow(clippy::missing_errors_doc)]
|
||||||
|
#![allow(clippy::missing_panics_doc)]
|
||||||
|
#![allow(clippy::cargo_common_metadata)]
|
||||||
|
|
||||||
|
use mlua::prelude::*;
|
||||||
|
use mlua_luau_scheduler::Scheduler;
|
||||||
|
|
||||||
|
use async_io::block_on;
|
||||||
|
|
||||||
|
const MAIN_SCRIPT: &str = include_str!("./lua/callbacks.luau");
|
||||||
|
|
||||||
|
pub fn main() -> LuaResult<()> {
|
||||||
|
tracing_subscriber::fmt()
|
||||||
|
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
|
||||||
|
.with_target(false)
|
||||||
|
.without_time()
|
||||||
|
.init();
|
||||||
|
|
||||||
|
// Set up persistent Lua environment
|
||||||
|
let lua = Lua::new();
|
||||||
|
|
||||||
|
// Create a new scheduler with custom callbacks
|
||||||
|
let sched = Scheduler::new(&lua);
|
||||||
|
sched.set_error_callback(|e| {
|
||||||
|
println!(
|
||||||
|
"Captured error from Lua!\n{}\n{e}\n{}",
|
||||||
|
"-".repeat(15),
|
||||||
|
"-".repeat(15)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load the main script into the scheduler, and keep track of the thread we spawn
|
||||||
|
let main = lua.load(MAIN_SCRIPT);
|
||||||
|
let id = sched.push_thread_front(main, ())?;
|
||||||
|
|
||||||
|
// Run until completion
|
||||||
|
block_on(sched.run());
|
||||||
|
|
||||||
|
// We should have gotten the error back from our script
|
||||||
|
assert!(sched.get_thread_result(id).unwrap().is_err());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_callbacks() -> LuaResult<()> {
|
||||||
|
main()
|
||||||
|
}
|
43
crates/mlua-luau-scheduler/examples/exit_code.rs
Normal file
43
crates/mlua-luau-scheduler/examples/exit_code.rs
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
#![allow(clippy::missing_errors_doc)]
|
||||||
|
#![allow(clippy::missing_panics_doc)]
|
||||||
|
#![allow(clippy::cargo_common_metadata)]
|
||||||
|
|
||||||
|
use async_io::block_on;
|
||||||
|
|
||||||
|
use mlua::prelude::*;
|
||||||
|
use mlua_luau_scheduler::{Functions, Scheduler};
|
||||||
|
|
||||||
|
const MAIN_SCRIPT: &str = include_str!("./lua/exit_code.luau");
|
||||||
|
|
||||||
|
pub fn main() -> LuaResult<()> {
|
||||||
|
tracing_subscriber::fmt()
|
||||||
|
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
|
||||||
|
.with_target(false)
|
||||||
|
.without_time()
|
||||||
|
.init();
|
||||||
|
|
||||||
|
// Set up persistent Lua environment
|
||||||
|
let lua = Lua::new();
|
||||||
|
let sched = Scheduler::new(&lua);
|
||||||
|
let fns = Functions::new(&lua)?;
|
||||||
|
|
||||||
|
lua.globals().set("exit", fns.exit)?;
|
||||||
|
|
||||||
|
// Load the main script into the scheduler
|
||||||
|
let main = lua.load(MAIN_SCRIPT);
|
||||||
|
sched.push_thread_front(main, ())?;
|
||||||
|
|
||||||
|
// Run until completion
|
||||||
|
block_on(sched.run());
|
||||||
|
|
||||||
|
// Verify that we got a correct exit code
|
||||||
|
let code = sched.get_exit_code().unwrap_or_default();
|
||||||
|
assert_eq!(code, 1);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_exit_code() -> LuaResult<()> {
|
||||||
|
main()
|
||||||
|
}
|
51
crates/mlua-luau-scheduler/examples/lots_of_threads.rs
Normal file
51
crates/mlua-luau-scheduler/examples/lots_of_threads.rs
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
#![allow(clippy::missing_errors_doc)]
|
||||||
|
#![allow(clippy::cargo_common_metadata)]
|
||||||
|
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use async_io::{block_on, Timer};
|
||||||
|
|
||||||
|
use mlua::prelude::*;
|
||||||
|
use mlua_luau_scheduler::{Functions, Scheduler};
|
||||||
|
|
||||||
|
const MAIN_SCRIPT: &str = include_str!("./lua/lots_of_threads.luau");
|
||||||
|
|
||||||
|
const ONE_NANOSECOND: Duration = Duration::from_nanos(1);
|
||||||
|
|
||||||
|
pub fn main() -> LuaResult<()> {
|
||||||
|
tracing_subscriber::fmt()
|
||||||
|
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
|
||||||
|
.with_target(false)
|
||||||
|
.without_time()
|
||||||
|
.init();
|
||||||
|
|
||||||
|
// Set up persistent Lua environment
|
||||||
|
let lua = Lua::new();
|
||||||
|
let sched = Scheduler::new(&lua);
|
||||||
|
let fns = Functions::new(&lua)?;
|
||||||
|
|
||||||
|
lua.globals().set("spawn", fns.spawn)?;
|
||||||
|
lua.globals().set(
|
||||||
|
"sleep",
|
||||||
|
lua.create_async_function(|_, ()| async move {
|
||||||
|
// Obviously we can't sleep for a single nanosecond since
|
||||||
|
// this uses OS scheduling under the hood, but we can try
|
||||||
|
Timer::after(ONE_NANOSECOND).await;
|
||||||
|
Ok(())
|
||||||
|
})?,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Load the main script into the scheduler
|
||||||
|
let main = lua.load(MAIN_SCRIPT);
|
||||||
|
sched.push_thread_front(main, ())?;
|
||||||
|
|
||||||
|
// Run until completion
|
||||||
|
block_on(sched.run());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_lots_of_threads() -> LuaResult<()> {
|
||||||
|
main()
|
||||||
|
}
|
13
crates/mlua-luau-scheduler/examples/lua/basic_sleep.luau
Normal file
13
crates/mlua-luau-scheduler/examples/lua/basic_sleep.luau
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
--!nocheck
|
||||||
|
--!nolint UnknownGlobal
|
||||||
|
|
||||||
|
print("Sleeping for 3 seconds...")
|
||||||
|
|
||||||
|
sleep(1)
|
||||||
|
print("1 second passed")
|
||||||
|
|
||||||
|
sleep(1)
|
||||||
|
print("2 seconds passed")
|
||||||
|
|
||||||
|
sleep(1)
|
||||||
|
print("3 seconds passed")
|
17
crates/mlua-luau-scheduler/examples/lua/basic_spawn.luau
Normal file
17
crates/mlua-luau-scheduler/examples/lua/basic_spawn.luau
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
--!nocheck
|
||||||
|
--!nolint UnknownGlobal
|
||||||
|
|
||||||
|
local _, err = pcall(function()
|
||||||
|
local file = readFile("Cargo.toml")
|
||||||
|
if file ~= nil then
|
||||||
|
print("Cargo.toml found!")
|
||||||
|
print("Contents:")
|
||||||
|
print(file)
|
||||||
|
else
|
||||||
|
print("Cargo.toml not found!")
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
if err ~= nil then
|
||||||
|
print("Error while reading file: " .. err)
|
||||||
|
end
|
4
crates/mlua-luau-scheduler/examples/lua/callbacks.luau
Normal file
4
crates/mlua-luau-scheduler/examples/lua/callbacks.luau
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
--!nocheck
|
||||||
|
--!nolint UnknownGlobal
|
||||||
|
|
||||||
|
error("Oh no! Something went very very wrong!")
|
8
crates/mlua-luau-scheduler/examples/lua/exit_code.luau
Normal file
8
crates/mlua-luau-scheduler/examples/lua/exit_code.luau
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
--!nocheck
|
||||||
|
--!nolint UnknownGlobal
|
||||||
|
|
||||||
|
print("Setting exit code manually")
|
||||||
|
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
error("unreachable")
|
29
crates/mlua-luau-scheduler/examples/lua/lots_of_threads.luau
Normal file
29
crates/mlua-luau-scheduler/examples/lua/lots_of_threads.luau
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
--!nocheck
|
||||||
|
--!nolint UnknownGlobal
|
||||||
|
|
||||||
|
local NUM_BATCHES = 10
|
||||||
|
local NUM_THREADS = 100_000
|
||||||
|
|
||||||
|
print(`Spawning {NUM_BATCHES * NUM_THREADS} threads split into {NUM_BATCHES} batches\n`)
|
||||||
|
|
||||||
|
local before = os.clock()
|
||||||
|
for i = 1, NUM_BATCHES do
|
||||||
|
print(`Batch {i} of {NUM_BATCHES}`)
|
||||||
|
local thread = coroutine.running()
|
||||||
|
|
||||||
|
local counter = 0
|
||||||
|
for j = 1, NUM_THREADS do
|
||||||
|
spawn(function()
|
||||||
|
sleep(0.1)
|
||||||
|
counter += 1
|
||||||
|
if counter == NUM_THREADS then
|
||||||
|
spawn(thread)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
coroutine.yield()
|
||||||
|
end
|
||||||
|
local after = os.clock()
|
||||||
|
|
||||||
|
print(`\nSpawned {NUM_BATCHES * NUM_THREADS} sleeping threads in {after - before}s`)
|
|
@ -0,0 +1,34 @@
|
||||||
|
--!nocheck
|
||||||
|
--!nolint UnknownGlobal
|
||||||
|
|
||||||
|
local nums = {}
|
||||||
|
local function insert(n: number)
|
||||||
|
table.insert(nums, n)
|
||||||
|
print(n)
|
||||||
|
end
|
||||||
|
|
||||||
|
insert(1)
|
||||||
|
|
||||||
|
-- Defer will run at the end of the resumption cycle, but without yielding
|
||||||
|
defer(function()
|
||||||
|
insert(5)
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- Spawn will instantly run up until the first yield, and must then be resumed manually ...
|
||||||
|
spawn(function()
|
||||||
|
insert(2)
|
||||||
|
coroutine.yield()
|
||||||
|
error("unreachable code")
|
||||||
|
end)
|
||||||
|
|
||||||
|
-- ... unless calling functions created using `lua.create_async_function(...)`,
|
||||||
|
-- which will resume their calling thread with their result automatically
|
||||||
|
spawn(function()
|
||||||
|
insert(3)
|
||||||
|
sleep(1)
|
||||||
|
insert(6)
|
||||||
|
end)
|
||||||
|
|
||||||
|
insert(4)
|
||||||
|
|
||||||
|
return nums
|
56
crates/mlua-luau-scheduler/examples/scheduler_ordering.rs
Normal file
56
crates/mlua-luau-scheduler/examples/scheduler_ordering.rs
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
#![allow(clippy::missing_errors_doc)]
|
||||||
|
#![allow(clippy::missing_panics_doc)]
|
||||||
|
#![allow(clippy::cargo_common_metadata)]
|
||||||
|
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
use async_io::{block_on, Timer};
|
||||||
|
|
||||||
|
use mlua::prelude::*;
|
||||||
|
use mlua_luau_scheduler::{Functions, Scheduler};
|
||||||
|
|
||||||
|
const MAIN_SCRIPT: &str = include_str!("./lua/scheduler_ordering.luau");
|
||||||
|
|
||||||
|
pub fn main() -> LuaResult<()> {
|
||||||
|
tracing_subscriber::fmt()
|
||||||
|
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
|
||||||
|
.with_target(false)
|
||||||
|
.without_time()
|
||||||
|
.init();
|
||||||
|
|
||||||
|
// Set up persistent Lua environment
|
||||||
|
let lua = Lua::new();
|
||||||
|
let sched = Scheduler::new(&lua);
|
||||||
|
let fns = Functions::new(&lua)?;
|
||||||
|
|
||||||
|
lua.globals().set("spawn", fns.spawn)?;
|
||||||
|
lua.globals().set("defer", fns.defer)?;
|
||||||
|
lua.globals().set(
|
||||||
|
"sleep",
|
||||||
|
lua.create_async_function(|_, duration: Option<f64>| async move {
|
||||||
|
let duration = duration.unwrap_or_default().max(1.0 / 250.0);
|
||||||
|
let before = Instant::now();
|
||||||
|
let after = Timer::after(Duration::from_secs_f64(duration)).await;
|
||||||
|
Ok((after - before).as_secs_f64())
|
||||||
|
})?,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Load the main script into the scheduler, and keep track of the thread we spawn
|
||||||
|
let main = lua.load(MAIN_SCRIPT);
|
||||||
|
let id = sched.push_thread_front(main, ())?;
|
||||||
|
|
||||||
|
// Run until completion
|
||||||
|
block_on(sched.run());
|
||||||
|
|
||||||
|
// We should have gotten proper values back from our script
|
||||||
|
let res = sched.get_thread_result(id).unwrap().unwrap();
|
||||||
|
let nums = Vec::<usize>::from_lua_multi(res, &lua)?;
|
||||||
|
assert_eq!(nums, vec![1, 2, 3, 4, 5, 6]);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_scheduler_ordering() -> LuaResult<()> {
|
||||||
|
main()
|
||||||
|
}
|
61
crates/mlua-luau-scheduler/examples/tracy.rs
Normal file
61
crates/mlua-luau-scheduler/examples/tracy.rs
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
NOTE: This example is the same as "lots_of_threads", but with tracy set up for performance profiling.
|
||||||
|
|
||||||
|
How to run:
|
||||||
|
|
||||||
|
1. Install tracy
|
||||||
|
- Follow the instructions at https://github.com/wolfpld/tracy
|
||||||
|
- Or install via something like homebrew: `brew install tracy`
|
||||||
|
2. Run the server (`tracy`) in a terminal
|
||||||
|
3. Run the example in another terminal
|
||||||
|
- `export RUST_LOG=trace`
|
||||||
|
- `cargo run --example tracy`
|
||||||
|
*/
|
||||||
|
|
||||||
|
#![allow(clippy::missing_errors_doc)]
|
||||||
|
#![allow(clippy::cargo_common_metadata)]
|
||||||
|
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use async_io::{block_on, Timer};
|
||||||
|
use tracing_subscriber::layer::SubscriberExt;
|
||||||
|
use tracing_tracy::{client::Client as TracyClient, TracyLayer};
|
||||||
|
|
||||||
|
use mlua::prelude::*;
|
||||||
|
use mlua_luau_scheduler::{Functions, Scheduler};
|
||||||
|
|
||||||
|
const MAIN_SCRIPT: &str = include_str!("./lua/lots_of_threads.luau");
|
||||||
|
|
||||||
|
const ONE_NANOSECOND: Duration = Duration::from_nanos(1);
|
||||||
|
|
||||||
|
pub fn main() -> LuaResult<()> {
|
||||||
|
let _client = TracyClient::start();
|
||||||
|
let _ = tracing::subscriber::set_global_default(
|
||||||
|
tracing_subscriber::registry().with(TracyLayer::default()),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set up persistent Lua environment
|
||||||
|
let lua = Lua::new();
|
||||||
|
let sched = Scheduler::new(&lua);
|
||||||
|
let fns = Functions::new(&lua)?;
|
||||||
|
|
||||||
|
lua.globals().set("spawn", fns.spawn)?;
|
||||||
|
lua.globals().set(
|
||||||
|
"sleep",
|
||||||
|
lua.create_async_function(|_, ()| async move {
|
||||||
|
// Obviously we can't sleep for a single nanosecond since
|
||||||
|
// this uses OS scheduling under the hood, but we can try
|
||||||
|
Timer::after(ONE_NANOSECOND).await;
|
||||||
|
Ok(())
|
||||||
|
})?,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Load the main script into the scheduler
|
||||||
|
let main = lua.load(MAIN_SCRIPT);
|
||||||
|
sched.push_thread_front(main, ())?;
|
||||||
|
|
||||||
|
// Run until completion
|
||||||
|
block_on(sched.run());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
45
crates/mlua-luau-scheduler/src/error_callback.rs
Normal file
45
crates/mlua-luau-scheduler/src/error_callback.rs
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
use std::{cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
|
use mlua::prelude::*;
|
||||||
|
|
||||||
|
type ErrorCallback = Box<dyn Fn(LuaError) + Send + 'static>;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(crate) struct ThreadErrorCallback {
|
||||||
|
inner: Rc<RefCell<Option<ErrorCallback>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ThreadErrorCallback {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
inner: Rc::new(RefCell::new(None)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn replace(&self, callback: impl Fn(LuaError) + Send + 'static) {
|
||||||
|
self.inner.borrow_mut().replace(Box::new(callback));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear(&self) {
|
||||||
|
self.inner.borrow_mut().take();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn call(&self, error: &LuaError) {
|
||||||
|
if let Some(cb) = &*self.inner.borrow() {
|
||||||
|
cb(error.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::needless_pass_by_value)]
|
||||||
|
fn default_error_callback(e: LuaError) {
|
||||||
|
eprintln!("{e}");
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ThreadErrorCallback {
|
||||||
|
fn default() -> Self {
|
||||||
|
let this = Self::new();
|
||||||
|
this.replace(default_error_callback);
|
||||||
|
this
|
||||||
|
}
|
||||||
|
}
|
31
crates/mlua-luau-scheduler/src/exit.rs
Normal file
31
crates/mlua-luau-scheduler/src/exit.rs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
use std::{cell::Cell, rc::Rc};
|
||||||
|
|
||||||
|
use event_listener::Event;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub(crate) struct Exit {
|
||||||
|
code: Rc<Cell<Option<u8>>>,
|
||||||
|
event: Rc<Event>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Exit {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
code: Rc::new(Cell::new(None)),
|
||||||
|
event: Rc::new(Event::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(&self, code: u8) {
|
||||||
|
self.code.set(Some(code));
|
||||||
|
self.event.notify(usize::MAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self) -> Option<u8> {
|
||||||
|
self.code.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn listen(&self) {
|
||||||
|
self.event.listen().await;
|
||||||
|
}
|
||||||
|
}
|
279
crates/mlua-luau-scheduler/src/functions.rs
Normal file
279
crates/mlua-luau-scheduler/src/functions.rs
Normal file
|
@ -0,0 +1,279 @@
|
||||||
|
#![allow(clippy::too_many_lines)]
|
||||||
|
|
||||||
|
use mlua::prelude::*;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
error_callback::ThreadErrorCallback,
|
||||||
|
queue::{DeferredThreadQueue, SpawnedThreadQueue},
|
||||||
|
result_map::ThreadResultMap,
|
||||||
|
thread_id::ThreadId,
|
||||||
|
traits::LuaSchedulerExt,
|
||||||
|
util::{is_poll_pending, LuaThreadOrFunction, ThreadResult},
|
||||||
|
};
|
||||||
|
|
||||||
|
const ERR_METADATA_NOT_ATTACHED: &str = "\
|
||||||
|
Lua state does not have scheduler metadata attached!\
|
||||||
|
\nThis is most likely caused by creating functions outside of a scheduler.\
|
||||||
|
\nScheduler functions must always be created from within an active scheduler.\
|
||||||
|
";
|
||||||
|
|
||||||
|
const EXIT_IMPL_LUA: &str = r"
|
||||||
|
exit(...)
|
||||||
|
yield()
|
||||||
|
";
|
||||||
|
|
||||||
|
const WRAP_IMPL_LUA: &str = r"
|
||||||
|
local t = create(...)
|
||||||
|
return function(...)
|
||||||
|
local r = { resume(t, ...) }
|
||||||
|
if r[1] then
|
||||||
|
return select(2, unpack(r))
|
||||||
|
else
|
||||||
|
error(r[2], 2)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
";
|
||||||
|
|
||||||
|
/**
|
||||||
|
A collection of lua functions that may be called to interact with a [`Scheduler`].
|
||||||
|
|
||||||
|
Note that these may all be implemented using [`LuaSchedulerExt`], however, this struct
|
||||||
|
is implemented using internal (non-public) APIs, and generally has better performance.
|
||||||
|
*/
|
||||||
|
pub struct Functions<'lua> {
|
||||||
|
/**
|
||||||
|
Implementation of `coroutine.resume` that handles async polling properly.
|
||||||
|
|
||||||
|
Defers onto the scheduler queue if the thread calls an async function.
|
||||||
|
*/
|
||||||
|
pub resume: LuaFunction<'lua>,
|
||||||
|
/**
|
||||||
|
Implementation of `coroutine.wrap` that handles async polling properly.
|
||||||
|
|
||||||
|
Defers onto the scheduler queue if the thread calls an async function.
|
||||||
|
*/
|
||||||
|
pub wrap: LuaFunction<'lua>,
|
||||||
|
/**
|
||||||
|
Resumes a function / thread once instantly, and runs until first yield.
|
||||||
|
|
||||||
|
Spawns onto the scheduler queue if not completed.
|
||||||
|
*/
|
||||||
|
pub spawn: LuaFunction<'lua>,
|
||||||
|
/**
|
||||||
|
Defers a function / thread onto the scheduler queue.
|
||||||
|
|
||||||
|
Does not resume instantly, only adds to the queue.
|
||||||
|
*/
|
||||||
|
pub defer: LuaFunction<'lua>,
|
||||||
|
/**
|
||||||
|
Cancels a function / thread, removing it from the queue.
|
||||||
|
*/
|
||||||
|
pub cancel: LuaFunction<'lua>,
|
||||||
|
/**
|
||||||
|
Exits the scheduler, stopping all other threads and closing the scheduler.
|
||||||
|
|
||||||
|
Yields the calling thread to ensure that it does not continue.
|
||||||
|
*/
|
||||||
|
pub exit: LuaFunction<'lua>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'lua> Functions<'lua> {
|
||||||
|
/**
|
||||||
|
Creates a new collection of Lua functions that may be called to interact with a [`Scheduler`].
|
||||||
|
|
||||||
|
# Errors
|
||||||
|
|
||||||
|
Errors when out of memory, or if default Lua globals are missing.
|
||||||
|
|
||||||
|
# Panics
|
||||||
|
|
||||||
|
Panics when the given [`Lua`] instance does not have an attached [`Scheduler`].
|
||||||
|
*/
|
||||||
|
pub fn new(lua: &'lua Lua) -> LuaResult<Self> {
|
||||||
|
let spawn_queue = lua
|
||||||
|
.app_data_ref::<SpawnedThreadQueue>()
|
||||||
|
.expect(ERR_METADATA_NOT_ATTACHED)
|
||||||
|
.clone();
|
||||||
|
let defer_queue = lua
|
||||||
|
.app_data_ref::<DeferredThreadQueue>()
|
||||||
|
.expect(ERR_METADATA_NOT_ATTACHED)
|
||||||
|
.clone();
|
||||||
|
let error_callback = lua
|
||||||
|
.app_data_ref::<ThreadErrorCallback>()
|
||||||
|
.expect(ERR_METADATA_NOT_ATTACHED)
|
||||||
|
.clone();
|
||||||
|
let result_map = lua
|
||||||
|
.app_data_ref::<ThreadResultMap>()
|
||||||
|
.expect(ERR_METADATA_NOT_ATTACHED)
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
let resume_queue = defer_queue.clone();
|
||||||
|
let resume_map = result_map.clone();
|
||||||
|
let resume =
|
||||||
|
lua.create_function(move |lua, (thread, args): (LuaThread, LuaMultiValue)| {
|
||||||
|
let _span = tracing::trace_span!("Scheduler::fn_resume").entered();
|
||||||
|
match thread.resume::<_, LuaMultiValue>(args.clone()) {
|
||||||
|
Ok(v) => {
|
||||||
|
if v.get(0).is_some_and(is_poll_pending) {
|
||||||
|
// Pending, defer to scheduler and return nil
|
||||||
|
resume_queue.push_item(lua, &thread, args)?;
|
||||||
|
(true, LuaValue::Nil).into_lua_multi(lua)
|
||||||
|
} else {
|
||||||
|
// Not pending, store the value if thread is done
|
||||||
|
if thread.status() != LuaThreadStatus::Resumable {
|
||||||
|
let id = ThreadId::from(&thread);
|
||||||
|
if resume_map.is_tracked(id) {
|
||||||
|
let res = ThreadResult::new(Ok(v.clone()), lua);
|
||||||
|
resume_map.insert(id, res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(true, v).into_lua_multi(lua)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
// Not pending, store the error
|
||||||
|
let id = ThreadId::from(&thread);
|
||||||
|
if resume_map.is_tracked(id) {
|
||||||
|
let res = ThreadResult::new(Err(e.clone()), lua);
|
||||||
|
resume_map.insert(id, res);
|
||||||
|
}
|
||||||
|
(false, e.to_string()).into_lua_multi(lua)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let wrap_env = lua.create_table_from(vec![
|
||||||
|
("resume", resume.clone()),
|
||||||
|
("error", lua.globals().get::<_, LuaFunction>("error")?),
|
||||||
|
("select", lua.globals().get::<_, LuaFunction>("select")?),
|
||||||
|
("unpack", lua.globals().get::<_, LuaFunction>("unpack")?),
|
||||||
|
(
|
||||||
|
"create",
|
||||||
|
lua.globals()
|
||||||
|
.get::<_, LuaTable>("coroutine")?
|
||||||
|
.get::<_, LuaFunction>("create")?,
|
||||||
|
),
|
||||||
|
])?;
|
||||||
|
let wrap = lua
|
||||||
|
.load(WRAP_IMPL_LUA)
|
||||||
|
.set_name("=__scheduler_wrap")
|
||||||
|
.set_environment(wrap_env)
|
||||||
|
.into_function()?;
|
||||||
|
|
||||||
|
let spawn_map = result_map.clone();
|
||||||
|
let spawn = lua.create_function(
|
||||||
|
move |lua, (tof, args): (LuaThreadOrFunction, LuaMultiValue)| {
|
||||||
|
let _span = tracing::trace_span!("Scheduler::fn_spawn").entered();
|
||||||
|
let thread = tof.into_thread(lua)?;
|
||||||
|
if thread.status() == LuaThreadStatus::Resumable {
|
||||||
|
// NOTE: We need to resume the thread once instantly for correct behavior,
|
||||||
|
// and only if we get the pending value back we can spawn to async executor
|
||||||
|
match thread.resume::<_, LuaMultiValue>(args.clone()) {
|
||||||
|
Ok(v) => {
|
||||||
|
if v.get(0).is_some_and(is_poll_pending) {
|
||||||
|
spawn_queue.push_item(lua, &thread, args)?;
|
||||||
|
} else {
|
||||||
|
// Not pending, store the value if thread is done
|
||||||
|
if thread.status() != LuaThreadStatus::Resumable {
|
||||||
|
let id = ThreadId::from(&thread);
|
||||||
|
if spawn_map.is_tracked(id) {
|
||||||
|
let res = ThreadResult::new(Ok(v), lua);
|
||||||
|
spawn_map.insert(id, res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error_callback.call(&e);
|
||||||
|
// Not pending, store the error
|
||||||
|
let id = ThreadId::from(&thread);
|
||||||
|
if spawn_map.is_tracked(id) {
|
||||||
|
let res = ThreadResult::new(Err(e), lua);
|
||||||
|
spawn_map.insert(id, res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Ok(thread)
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let defer = lua.create_function(
|
||||||
|
move |lua, (tof, args): (LuaThreadOrFunction, LuaMultiValue)| {
|
||||||
|
let _span = tracing::trace_span!("Scheduler::fn_defer").entered();
|
||||||
|
let thread = tof.into_thread(lua)?;
|
||||||
|
if thread.status() == LuaThreadStatus::Resumable {
|
||||||
|
defer_queue.push_item(lua, &thread, args)?;
|
||||||
|
}
|
||||||
|
Ok(thread)
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let close = lua
|
||||||
|
.globals()
|
||||||
|
.get::<_, LuaTable>("coroutine")?
|
||||||
|
.get::<_, LuaFunction>("close")?;
|
||||||
|
let close_key = lua.create_registry_value(close)?;
|
||||||
|
let cancel = lua.create_function(move |lua, thread: LuaThread| {
|
||||||
|
let _span = tracing::trace_span!("Scheduler::fn_cancel").entered();
|
||||||
|
let close: LuaFunction = lua.registry_value(&close_key)?;
|
||||||
|
match close.call(thread) {
|
||||||
|
Err(LuaError::CoroutineInactive) | Ok(()) => Ok(()),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let exit_env = lua.create_table_from(vec![
|
||||||
|
(
|
||||||
|
"exit",
|
||||||
|
lua.create_function(|lua, code: Option<u8>| {
|
||||||
|
let _span = tracing::trace_span!("Scheduler::fn_exit").entered();
|
||||||
|
let code = code.unwrap_or_default();
|
||||||
|
lua.set_exit_code(code);
|
||||||
|
Ok(())
|
||||||
|
})?,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"yield",
|
||||||
|
lua.globals()
|
||||||
|
.get::<_, LuaTable>("coroutine")?
|
||||||
|
.get::<_, LuaFunction>("yield")?,
|
||||||
|
),
|
||||||
|
])?;
|
||||||
|
let exit = lua
|
||||||
|
.load(EXIT_IMPL_LUA)
|
||||||
|
.set_name("=__scheduler_exit")
|
||||||
|
.set_environment(exit_env)
|
||||||
|
.into_function()?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
resume,
|
||||||
|
wrap,
|
||||||
|
spawn,
|
||||||
|
defer,
|
||||||
|
cancel,
|
||||||
|
exit,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Functions<'_> {
|
||||||
|
/**
|
||||||
|
Injects [`Scheduler`]-compatible functions into the given [`Lua`] instance.
|
||||||
|
|
||||||
|
This will overwrite the following functions:
|
||||||
|
|
||||||
|
- `coroutine.resume`
|
||||||
|
- `coroutine.wrap`
|
||||||
|
|
||||||
|
# Errors
|
||||||
|
|
||||||
|
Errors when out of memory, or if default Lua globals are missing.
|
||||||
|
*/
|
||||||
|
pub fn inject_compat(&self, lua: &Lua) -> LuaResult<()> {
|
||||||
|
let co: LuaTable = lua.globals().get("coroutine")?;
|
||||||
|
co.set("resume", self.resume.clone())?;
|
||||||
|
co.set("wrap", self.wrap.clone())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
18
crates/mlua-luau-scheduler/src/lib.rs
Normal file
18
crates/mlua-luau-scheduler/src/lib.rs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
#![allow(clippy::cargo_common_metadata)]
|
||||||
|
|
||||||
|
mod error_callback;
|
||||||
|
mod exit;
|
||||||
|
mod functions;
|
||||||
|
mod queue;
|
||||||
|
mod result_map;
|
||||||
|
mod scheduler;
|
||||||
|
mod status;
|
||||||
|
mod thread_id;
|
||||||
|
mod traits;
|
||||||
|
mod util;
|
||||||
|
|
||||||
|
pub use functions::Functions;
|
||||||
|
pub use scheduler::Scheduler;
|
||||||
|
pub use status::Status;
|
||||||
|
pub use thread_id::ThreadId;
|
||||||
|
pub use traits::{IntoLuaThread, LuaSchedulerExt, LuaSpawnExt};
|
139
crates/mlua-luau-scheduler/src/queue.rs
Normal file
139
crates/mlua-luau-scheduler/src/queue.rs
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
use std::{pin::Pin, rc::Rc};
|
||||||
|
|
||||||
|
use concurrent_queue::ConcurrentQueue;
|
||||||
|
use derive_more::{Deref, DerefMut};
|
||||||
|
use event_listener::Event;
|
||||||
|
use futures_lite::{Future, FutureExt};
|
||||||
|
use mlua::prelude::*;
|
||||||
|
|
||||||
|
use crate::{traits::IntoLuaThread, util::ThreadWithArgs, ThreadId};
|
||||||
|
|
||||||
|
/**
|
||||||
|
Queue for storing [`LuaThread`]s with associated arguments.
|
||||||
|
|
||||||
|
Provides methods for pushing and draining the queue, as
|
||||||
|
well as listening for new items being pushed to the queue.
|
||||||
|
*/
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub(crate) struct ThreadQueue {
|
||||||
|
queue: Rc<ConcurrentQueue<ThreadWithArgs>>,
|
||||||
|
event: Rc<Event>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ThreadQueue {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let queue = Rc::new(ConcurrentQueue::unbounded());
|
||||||
|
let event = Rc::new(Event::new());
|
||||||
|
Self { queue, event }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_item<'lua>(
|
||||||
|
&self,
|
||||||
|
lua: &'lua Lua,
|
||||||
|
thread: impl IntoLuaThread<'lua>,
|
||||||
|
args: impl IntoLuaMulti<'lua>,
|
||||||
|
) -> LuaResult<ThreadId> {
|
||||||
|
let thread = thread.into_lua_thread(lua)?;
|
||||||
|
let args = args.into_lua_multi(lua)?;
|
||||||
|
|
||||||
|
tracing::trace!("pushing item to queue with {} args", args.len());
|
||||||
|
let id = ThreadId::from(&thread);
|
||||||
|
let stored = ThreadWithArgs::new(lua, thread, args)?;
|
||||||
|
|
||||||
|
self.queue.push(stored).into_lua_err()?;
|
||||||
|
self.event.notify(usize::MAX);
|
||||||
|
|
||||||
|
Ok(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn drain_items<'outer, 'lua>(
|
||||||
|
&'outer self,
|
||||||
|
lua: &'lua Lua,
|
||||||
|
) -> impl Iterator<Item = (LuaThread<'lua>, LuaMultiValue<'lua>)> + 'outer
|
||||||
|
where
|
||||||
|
'lua: 'outer,
|
||||||
|
{
|
||||||
|
self.queue.try_iter().map(|stored| stored.into_inner(lua))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub async fn wait_for_item(&self) {
|
||||||
|
if self.queue.is_empty() {
|
||||||
|
let listener = self.event.listen();
|
||||||
|
// NOTE: Need to check again, we could have gotten
|
||||||
|
// new queued items while creating our listener
|
||||||
|
if self.queue.is_empty() {
|
||||||
|
listener.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.queue.is_empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Alias for [`ThreadQueue`], providing a newtype to store in Lua app data.
|
||||||
|
*/
|
||||||
|
#[derive(Debug, Clone, Deref, DerefMut)]
|
||||||
|
pub(crate) struct SpawnedThreadQueue(ThreadQueue);
|
||||||
|
|
||||||
|
impl SpawnedThreadQueue {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self(ThreadQueue::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Alias for [`ThreadQueue`], providing a newtype to store in Lua app data.
|
||||||
|
*/
|
||||||
|
#[derive(Debug, Clone, Deref, DerefMut)]
|
||||||
|
pub(crate) struct DeferredThreadQueue(ThreadQueue);
|
||||||
|
|
||||||
|
impl DeferredThreadQueue {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self(ThreadQueue::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type LocalBoxFuture<'fut> = Pin<Box<dyn Future<Output = ()> + 'fut>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Queue for storing local futures.
|
||||||
|
|
||||||
|
Provides methods for pushing and draining the queue, as
|
||||||
|
well as listening for new items being pushed to the queue.
|
||||||
|
*/
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub(crate) struct FuturesQueue<'fut> {
|
||||||
|
queue: Rc<ConcurrentQueue<LocalBoxFuture<'fut>>>,
|
||||||
|
event: Rc<Event>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'fut> FuturesQueue<'fut> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let queue = Rc::new(ConcurrentQueue::unbounded());
|
||||||
|
let event = Rc::new(Event::new());
|
||||||
|
Self { queue, event }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_item(&self, fut: impl Future<Output = ()> + 'fut) {
|
||||||
|
let _ = self.queue.push(fut.boxed_local());
|
||||||
|
self.event.notify(usize::MAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn drain_items<'outer>(
|
||||||
|
&'outer self,
|
||||||
|
) -> impl Iterator<Item = LocalBoxFuture<'fut>> + 'outer {
|
||||||
|
self.queue.try_iter()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn wait_for_item(&self) {
|
||||||
|
if self.queue.is_empty() {
|
||||||
|
self.event.listen().await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
64
crates/mlua-luau-scheduler/src/result_map.rs
Normal file
64
crates/mlua-luau-scheduler/src/result_map.rs
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
#![allow(clippy::inline_always)]
|
||||||
|
|
||||||
|
use std::{cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
|
use event_listener::Event;
|
||||||
|
// NOTE: This is the hash algorithm that mlua also uses, so we
|
||||||
|
// are not adding any additional dependencies / bloat by using it.
|
||||||
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
|
|
||||||
|
use crate::{thread_id::ThreadId, util::ThreadResult};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(crate) struct ThreadResultMap {
|
||||||
|
tracked: Rc<RefCell<FxHashSet<ThreadId>>>,
|
||||||
|
results: Rc<RefCell<FxHashMap<ThreadId, ThreadResult>>>,
|
||||||
|
events: Rc<RefCell<FxHashMap<ThreadId, Rc<Event>>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ThreadResultMap {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
tracked: Rc::new(RefCell::new(FxHashSet::default())),
|
||||||
|
results: Rc::new(RefCell::new(FxHashMap::default())),
|
||||||
|
events: Rc::new(RefCell::new(FxHashMap::default())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn track(&self, id: ThreadId) {
|
||||||
|
self.tracked.borrow_mut().insert(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn is_tracked(&self, id: ThreadId) -> bool {
|
||||||
|
self.tracked.borrow().contains(&id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert(&self, id: ThreadId, result: ThreadResult) {
|
||||||
|
debug_assert!(self.is_tracked(id), "Thread must be tracked");
|
||||||
|
self.results.borrow_mut().insert(id, result);
|
||||||
|
if let Some(event) = self.events.borrow_mut().remove(&id) {
|
||||||
|
event.notify(usize::MAX);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn listen(&self, id: ThreadId) {
|
||||||
|
debug_assert!(self.is_tracked(id), "Thread must be tracked");
|
||||||
|
if !self.results.borrow().contains_key(&id) {
|
||||||
|
let listener = {
|
||||||
|
let mut events = self.events.borrow_mut();
|
||||||
|
let event = events.entry(id).or_insert_with(|| Rc::new(Event::new()));
|
||||||
|
event.listen()
|
||||||
|
};
|
||||||
|
listener.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove(&self, id: ThreadId) -> Option<ThreadResult> {
|
||||||
|
let res = self.results.borrow_mut().remove(&id)?;
|
||||||
|
self.tracked.borrow_mut().remove(&id);
|
||||||
|
self.events.borrow_mut().remove(&id);
|
||||||
|
Some(res)
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue