mirror of
https://github.com/lune-org/lune.git
synced 2025-04-03 01:50:55 +01:00
Compare commits
131 commits
Author | SHA1 | Date | |
---|---|---|---|
|
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 | ||
|
95b81d65fe | ||
|
43de7b277a | ||
|
1d0bab5e65 | ||
|
e0f065b678 | ||
|
5b5c46f802 | ||
|
489c8a6609 | ||
|
a13b17fee6 | ||
|
e0b9ceb86d | ||
|
6bc1fa2343 | ||
|
763b3ff6a7 | ||
|
816b6654da | ||
|
efcc3c6028 | ||
|
de71558c5d | ||
|
3f53fc983c | ||
|
089ecb3a4c | ||
|
7a46f12c02 | ||
|
96eed54a65 | ||
|
abe7217a58 | ||
|
54081c1b0f | ||
|
12f5824da9 | ||
|
e11302766b | ||
|
70f0c55d35 | ||
|
476125cc74 | ||
|
b8196d6284 | ||
|
a11c1558ed | ||
|
cec77a9bd9 | ||
|
0685e62a8f | ||
|
53463641b8 | ||
66ed1a0b72 | |||
f830ce7fad | |||
7fb48dfa1f | |||
|
fa7f6c6f51 | ||
|
753897222a | ||
|
4a28499aaa | ||
|
03c773fd79 | ||
fe55442dac | |||
|
34fc23d024 | ||
|
fcfb5ed3a8 | ||
|
f36ec1483a | ||
|
aa04fb345c | ||
|
3f79756f70 | ||
|
8220216893 | ||
0d302cf0c1 | |||
|
1e3a604d0f | ||
|
a65ef2ae1b | ||
|
94ba331ed0 | ||
|
59aa780654 | ||
|
0116d405b2 | ||
|
9f58414e99 |
275 changed files with 11282 additions and 3297 deletions
4
.gitattributes
vendored
4
.gitattributes
vendored
|
@ -1,9 +1,5 @@
|
|||
* 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
|
||||
*.lua eol=lf
|
||||
*.luau eol=lf
|
||||
|
|
21
.github/workflows/ci.yaml
vendored
21
.github/workflows/ci.yaml
vendored
|
@ -23,11 +23,8 @@ jobs:
|
|||
with:
|
||||
components: rustfmt
|
||||
|
||||
- name: Install Just
|
||||
uses: extractions/setup-just@v1
|
||||
|
||||
- name: Install Tooling
|
||||
uses: ok-nick/setup-aftman@v0.4.2
|
||||
uses: CompeyDev/setup-rokit@v0.1.2
|
||||
|
||||
- name: Check Formatting
|
||||
run: just fmt-check
|
||||
|
@ -40,11 +37,8 @@ jobs:
|
|||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Just
|
||||
uses: extractions/setup-just@v1
|
||||
|
||||
- name: Install Tooling
|
||||
uses: ok-nick/setup-aftman@v0.4.2
|
||||
uses: CompeyDev/setup-rokit@v0.1.2
|
||||
|
||||
- name: Analyze
|
||||
run: just analyze
|
||||
|
@ -64,9 +58,13 @@ jobs:
|
|||
cargo-target: x86_64-unknown-linux-gnu
|
||||
|
||||
- name: macOS x86_64
|
||||
runner-os: macos-latest
|
||||
runner-os: macos-13
|
||||
cargo-target: x86_64-apple-darwin
|
||||
timeout-minutes: 10
|
||||
|
||||
- name: macOS aarch64
|
||||
runner-os: macos-14
|
||||
cargo-target: aarch64-apple-darwin
|
||||
|
||||
name: CI - ${{ matrix.name }}
|
||||
runs-on: ${{ matrix.runner-os }}
|
||||
steps:
|
||||
|
@ -84,17 +82,20 @@ jobs:
|
|||
- name: Build
|
||||
run: |
|
||||
cargo build \
|
||||
--workspace \
|
||||
--locked --all-features \
|
||||
--target ${{ matrix.cargo-target }}
|
||||
|
||||
- name: Lint
|
||||
run: |
|
||||
cargo clippy \
|
||||
--workspace \
|
||||
--locked --all-features \
|
||||
--target ${{ matrix.cargo-target }}
|
||||
|
||||
- name: Test
|
||||
run: |
|
||||
cargo test \
|
||||
--lib --workspace \
|
||||
--locked --all-features \
|
||||
--target ${{ matrix.cargo-target }}
|
||||
|
|
24
.github/workflows/publish.yaml
vendored
24
.github/workflows/publish.yaml
vendored
|
@ -1,24 +0,0 @@
|
|||
name: Publish
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: Publish
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Publish to crates.io
|
||||
uses: katyo/publish-crates@v2
|
||||
with:
|
||||
registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||
ignore-unpublished-changes: true
|
58
.github/workflows/release.yaml
vendored
58
.github/workflows/release.yaml
vendored
|
@ -21,14 +21,32 @@ jobs:
|
|||
uses: actions/checkout@v4
|
||||
|
||||
- name: Get version from manifest
|
||||
uses: SebRollen/toml-action@9062fbef52816d61278d24ce53c8070440e1e8dd
|
||||
uses: SebRollen/toml-action@v1.2.0
|
||||
id: get_version
|
||||
with:
|
||||
file: Cargo.toml
|
||||
file: crates/lune/Cargo.toml
|
||||
field: package.version
|
||||
|
||||
# dry-run:
|
||||
# name: Dry-run
|
||||
# needs: ["init"]
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - name: Checkout repository
|
||||
# uses: actions/checkout@v4
|
||||
|
||||
# - name: Install Rust
|
||||
# uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
# - name: Publish (dry-run)
|
||||
# uses: katyo/publish-crates@v2
|
||||
# with:
|
||||
# dry-run: true
|
||||
# check-repo: true
|
||||
# registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||
|
||||
build:
|
||||
needs: ["init"]
|
||||
needs: ["init"] # , "dry-run"]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
@ -70,7 +88,7 @@ jobs:
|
|||
targets: ${{ matrix.cargo-target }}
|
||||
|
||||
- name: Install Just
|
||||
uses: extractions/setup-just@v1
|
||||
uses: extractions/setup-just@v2
|
||||
|
||||
- name: Install build tooling (aarch64-unknown-linux-gnu)
|
||||
if: matrix.cargo-target == 'aarch64-unknown-linux-gnu'
|
||||
|
@ -86,24 +104,24 @@ jobs:
|
|||
run: just zip-release ${{ matrix.cargo-target }}
|
||||
|
||||
- name: Upload release artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.artifact-name }}
|
||||
path: release.zip
|
||||
|
||||
release:
|
||||
name: Release
|
||||
release-github:
|
||||
name: Release (GitHub)
|
||||
runs-on: ubuntu-latest
|
||||
needs: ["init", "build"]
|
||||
needs: ["init", "build"] # , "dry-run", "build"]
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Just
|
||||
uses: extractions/setup-just@v1
|
||||
uses: extractions/setup-just@v2
|
||||
|
||||
- name: Download releases
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: ./releases
|
||||
|
||||
|
@ -111,7 +129,7 @@ jobs:
|
|||
run: just unpack-releases "./releases"
|
||||
|
||||
- name: Create release
|
||||
uses: softprops/action-gh-release@v1
|
||||
uses: softprops/action-gh-release@v2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
|
@ -120,3 +138,21 @@ jobs:
|
|||
fail_on_unmatched_files: true
|
||||
files: ./releases/*.zip
|
||||
draft: true
|
||||
|
||||
# release-crates:
|
||||
# name: Release (crates.io)
|
||||
# runs-on: ubuntu-latest
|
||||
# needs: ["init", "dry-run", "build"]
|
||||
# steps:
|
||||
# - name: Checkout repository
|
||||
# uses: actions/checkout@v4
|
||||
|
||||
# - name: Install Rust
|
||||
# uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
# - name: Publish crates
|
||||
# uses: katyo/publish-crates@v2
|
||||
# with:
|
||||
# dry-run: false
|
||||
# check-repo: true
|
||||
# registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||
|
|
|
@ -129,7 +129,7 @@ end
|
|||
]]
|
||||
|
||||
print("Sending 4 pings to google 🌏")
|
||||
local result = process.spawn("ping", {
|
||||
local result = process.exec("ping", {
|
||||
"google.com",
|
||||
"-c 4",
|
||||
})
|
||||
|
|
|
@ -28,8 +28,8 @@ end)
|
|||
|
||||
for _ = 1, 5 do
|
||||
local start = os.clock()
|
||||
socket.send(tostring(1))
|
||||
local response = socket.next()
|
||||
socket:send(tostring(1))
|
||||
local response = socket:next()
|
||||
local elapsed = os.clock() - start
|
||||
print(`Got response '{response}' in {elapsed * 1_000} milliseconds`)
|
||||
task.wait(1 - elapsed)
|
||||
|
@ -38,7 +38,7 @@ end
|
|||
-- Everything went well, and we are done with the socket, so we can close it
|
||||
|
||||
print("Closing web socket...")
|
||||
socket.close()
|
||||
socket:close()
|
||||
|
||||
task.cancel(forceExit)
|
||||
print("Done! 🌙")
|
||||
|
|
|
@ -15,9 +15,9 @@ local handle = net.serve(PORT, {
|
|||
handleWebSocket = function(socket)
|
||||
print("Got new web socket connection!")
|
||||
repeat
|
||||
local message = socket.next()
|
||||
local message = socket:next()
|
||||
if message ~= nil then
|
||||
socket.send("Echo - " .. message)
|
||||
socket:send("Echo - " .. message)
|
||||
end
|
||||
until message == nil
|
||||
print("Web socket disconnected.")
|
||||
|
|
236
CHANGELOG.md
236
CHANGELOG.md
|
@ -8,6 +8,242 @@ 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/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## `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
|
||||
|
||||
- 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.
|
||||
|
||||
Example basic usage:
|
||||
|
||||
```lua
|
||||
local Regex = require("@lune/regex")
|
||||
|
||||
local re = Regex.new("hello")
|
||||
|
||||
if re:isMatch("hello, world!") then
|
||||
print("Matched!")
|
||||
end
|
||||
|
||||
local caps = re:captures("hello, world! hello, again!")
|
||||
|
||||
print(#caps) -- 2
|
||||
print(caps:get(1)) -- "hello"
|
||||
print(caps:get(2)) -- "hello"
|
||||
print(caps:get(3)) -- nil
|
||||
```
|
||||
|
||||
Check out the documentation for more details.
|
||||
|
||||
- Added support for buffers as arguments in builtin APIs ([#148])
|
||||
|
||||
This includes APIs such as `fs.writeFile`, `serde.encode`, and more.
|
||||
|
||||
- Added support for cross-compilation of standalone binaries ([#162])
|
||||
|
||||
You can now compile standalone binaries for other platforms by passing
|
||||
an additional `target` argument to the `build` subcommand:
|
||||
|
||||
```sh
|
||||
lune build my-file.luau --output my-bin --target windows-x86_64
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
- Added `stdio.readToEnd()` for reading the entire stdin passed to Lune
|
||||
|
||||
### Changed
|
||||
|
||||
- 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:
|
||||
|
||||
- `lune` (old)
|
||||
- `lune-utils`
|
||||
- `lune-roblox`
|
||||
- `lune-std-*` for every builtin library
|
||||
|
||||
When depending on the main `lune` crate, each builtin library also has a feature flag that can be toggled in the format `std-*`.
|
||||
|
||||
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.
|
||||
|
||||
- Changed the `User-Agent` header in `net.request` to be more descriptive ([#186])
|
||||
- Updated to Luau version `0.622`.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed not being able to decompress `lz4` format in high compression mode
|
||||
- Fixed stack overflow for tables with circular keys ([#183])
|
||||
- Fixed `net.serve` no longer accepting ipv6 addresses
|
||||
- Fixed headers in `net.serve` being raw bytes instead of strings
|
||||
|
||||
[#148]: https://github.com/lune-org/lune/pull/148
|
||||
[#162]: https://github.com/lune-org/lune/pull/162
|
||||
[#183]: https://github.com/lune-org/lune/pull/183
|
||||
[#186]: https://github.com/lune-org/lune/pull/186
|
||||
[#188]: https://github.com/lune-org/lune/pull/188
|
||||
|
||||
## `0.8.3` - April 15th, 2024
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `require` not throwing syntax errors ([#168])
|
||||
- Fixed `require` caching not working correctly ([#171])
|
||||
- Fixed case-sensitivity issue in `require` with aliases ([#173])
|
||||
- Fixed `itertools` dependency being marked optional even though it is mandatory ([#176])
|
||||
- Fixed test cases for the `net` built-in library on Windows ([#177])
|
||||
|
||||
[#168]: https://github.com/lune-org/lune/pull/168
|
||||
[#171]: https://github.com/lune-org/lune/pull/171
|
||||
[#173]: https://github.com/lune-org/lune/pull/173
|
||||
[#176]: https://github.com/lune-org/lune/pull/176
|
||||
[#177]: https://github.com/lune-org/lune/pull/177
|
||||
|
||||
## `0.8.2` - March 12th, 2024
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed REPL panicking after the first evaluation / run.
|
||||
- Fixed globals reloading on each run in the REPL, causing unnecessary slowdowns.
|
||||
- Fixed `net.serve` requests no longer being plain tables in Lune `0.8.1`, breaking usage of things such as `table.clone`.
|
||||
|
||||
## `0.8.1` - March 11th, 2024
|
||||
|
||||
### Added
|
||||
|
|
2002
Cargo.lock
generated
2002
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
174
Cargo.toml
174
Cargo.toml
|
@ -1,42 +1,22 @@
|
|||
[package]
|
||||
name = "lune"
|
||||
version = "0.8.1"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/lune-org/lune"
|
||||
description = "A standalone Luau runtime"
|
||||
readme = "README.md"
|
||||
keywords = ["cli", "lua", "luau", "runtime"]
|
||||
categories = ["command-line-interface"]
|
||||
|
||||
[[bin]]
|
||||
name = "lune"
|
||||
path = "src/main.rs"
|
||||
|
||||
[lib]
|
||||
name = "lune"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[features]
|
||||
default = ["cli", "roblox"]
|
||||
cli = [
|
||||
"dep:anyhow",
|
||||
"dep:env_logger",
|
||||
"dep:itertools",
|
||||
"dep:clap",
|
||||
"dep:include_dir",
|
||||
"dep:regex",
|
||||
"dep:rustyline",
|
||||
]
|
||||
roblox = [
|
||||
"dep:glam",
|
||||
"dep:rand",
|
||||
"dep:rbx_cookie",
|
||||
"dep:rbx_binary",
|
||||
"dep:rbx_dom_weak",
|
||||
"dep:rbx_reflection",
|
||||
"dep:rbx_reflection_database",
|
||||
"dep:rbx_xml",
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
default-members = ["crates/lune"]
|
||||
members = [
|
||||
"crates/lune",
|
||||
"crates/lune-roblox",
|
||||
"crates/lune-std",
|
||||
"crates/lune-std-datetime",
|
||||
"crates/lune-std-fs",
|
||||
"crates/lune-std-luau",
|
||||
"crates/lune-std-net",
|
||||
"crates/lune-std-process",
|
||||
"crates/lune-std-regex",
|
||||
"crates/lune-std-roblox",
|
||||
"crates/lune-std-serde",
|
||||
"crates/lune-std-stdio",
|
||||
"crates/lune-std-task",
|
||||
"crates/lune-utils",
|
||||
"crates/mlua-luau-scheduler",
|
||||
]
|
||||
|
||||
# Profile for building the release binary, with the following options set:
|
||||
|
@ -54,99 +34,31 @@ opt-level = "z"
|
|||
strip = true
|
||||
lto = true
|
||||
|
||||
# All of the dependencies for Lune.
|
||||
# Lints for all crates in the workspace
|
||||
#
|
||||
# Dependencies are categorized as following:
|
||||
#
|
||||
# 1. General dependencies with no specific features set
|
||||
# 2. Large / core dependencies that have many different crates and / or features set
|
||||
# 3. Dependencies for specific features of Lune, eg. the CLI or massive Roblox builtin library
|
||||
#
|
||||
[dependencies]
|
||||
console = "0.15"
|
||||
directories = "5.0"
|
||||
futures-util = "0.3"
|
||||
once_cell = "1.17"
|
||||
thiserror = "1.0"
|
||||
async-trait = "0.1"
|
||||
dialoguer = "0.11"
|
||||
dunce = "1.0"
|
||||
lz4_flex = "0.11"
|
||||
path-clean = "1.0"
|
||||
pathdiff = "0.2"
|
||||
pin-project = "1.0"
|
||||
urlencoding = "2.1"
|
||||
# 1. Error on all lints by default, then make cargo + clippy pedantic lints just warn
|
||||
# 2. Selectively allow some lints that are _too_ pedantic, such as:
|
||||
# - Casts between number types
|
||||
# - Module naming conventions
|
||||
# - Imports and multiple dependency versions
|
||||
[workspace.lints.clippy]
|
||||
all = { level = "deny", priority = -3 }
|
||||
cargo = { level = "warn", priority = -2 }
|
||||
pedantic = { level = "warn", priority = -1 }
|
||||
|
||||
### RUNTIME
|
||||
cast_lossless = { level = "allow", priority = 1 }
|
||||
cast_possible_truncation = { level = "allow", priority = 1 }
|
||||
cast_possible_wrap = { level = "allow", priority = 1 }
|
||||
cast_precision_loss = { level = "allow", priority = 1 }
|
||||
cast_sign_loss = { level = "allow", priority = 1 }
|
||||
|
||||
blocking = "1.5"
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||
tokio = { version = "1.24", features = ["full", "tracing"] }
|
||||
os_str_bytes = { version = "7.0", features = ["conversions"] }
|
||||
similar_names = { level = "allow", priority = 1 }
|
||||
unnecessary_wraps = { level = "allow", priority = 1 }
|
||||
unnested_or_patterns = { level = "allow", priority = 1 }
|
||||
unreadable_literal = { level = "allow", priority = 1 }
|
||||
|
||||
mlua-luau-scheduler = { version = "0.0.2" }
|
||||
mlua = { version = "0.9.6", features = [
|
||||
"luau",
|
||||
"luau-jit",
|
||||
"async",
|
||||
"serialize",
|
||||
] }
|
||||
|
||||
### SERDE
|
||||
|
||||
async-compression = { version = "0.4", features = [
|
||||
"tokio",
|
||||
"brotli",
|
||||
"deflate",
|
||||
"gzip",
|
||||
"zlib",
|
||||
] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = { version = "1.0", features = ["preserve_order"] }
|
||||
serde_yaml = "0.9"
|
||||
toml = { version = "0.8", features = ["preserve_order"] }
|
||||
|
||||
### NET
|
||||
|
||||
hyper = { version = "1.1", features = ["full"] }
|
||||
hyper-util = { version = "0.1", features = ["full"] }
|
||||
http = "1.0"
|
||||
http-body-util = { version = "0.1" }
|
||||
hyper-tungstenite = { version = "0.13" }
|
||||
|
||||
reqwest = { version = "0.11", default-features = false, features = [
|
||||
"rustls-tls",
|
||||
] }
|
||||
|
||||
tokio-tungstenite = { version = "0.21", features = ["rustls-tls-webpki-roots"] }
|
||||
|
||||
### DATETIME
|
||||
chrono = "=0.4.34" # NOTE: 0.4.35 does not compile with chrono_lc
|
||||
chrono_lc = "0.1"
|
||||
|
||||
### CLI
|
||||
|
||||
anyhow = { optional = true, version = "1.0" }
|
||||
env_logger = { optional = true, version = "0.11" }
|
||||
itertools = { optional = true, version = "0.12" }
|
||||
clap = { optional = true, version = "4.1", features = ["derive"] }
|
||||
include_dir = { optional = true, version = "0.7", features = ["glob"] }
|
||||
regex = { optional = true, version = "1.7", default-features = false, features = [
|
||||
"std",
|
||||
"unicode-perl",
|
||||
] }
|
||||
rustyline = { optional = true, version = "14.0" }
|
||||
|
||||
### ROBLOX
|
||||
|
||||
glam = { optional = true, version = "0.25" }
|
||||
rand = { optional = true, version = "0.8" }
|
||||
|
||||
rbx_cookie = { optional = true, version = "0.1.4", default-features = false }
|
||||
|
||||
rbx_binary = { optional = true, version = "0.7.3" }
|
||||
rbx_dom_weak = { optional = true, version = "2.6.0" }
|
||||
rbx_reflection = { optional = true, version = "4.4.0" }
|
||||
rbx_reflection_database = { optional = true, version = "0.2.9" }
|
||||
rbx_xml = { optional = true, version = "0.13.2" }
|
||||
multiple_crate_versions = { level = "allow", priority = 1 }
|
||||
module_inception = { level = "allow", priority = 1 }
|
||||
module_name_repetitions = { level = "allow", priority = 1 }
|
||||
needless_pass_by_value = { level = "allow", priority = 1 }
|
||||
wildcard_imports = { level = "allow", priority = 1 }
|
||||
|
|
|
@ -33,7 +33,7 @@ Lune provides fully asynchronous APIs wherever possible, and is built in Rust
|
|||
## Features
|
||||
|
||||
- 🌙 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
|
||||
- 🏡 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
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
[tools]
|
||||
luau-lsp = "JohnnyMorganz/luau-lsp@1.27.0"
|
||||
selene = "Kampfkarren/selene@0.26.1"
|
||||
stylua = "JohnnyMorganz/StyLua@0.19.1"
|
29
crates/lune-roblox/Cargo.toml
Normal file
29
crates/lune-roblox/Cargo.toml
Normal file
|
@ -0,0 +1,29 @@
|
|||
[package]
|
||||
name = "lune-roblox"
|
||||
version = "0.1.4"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/lune-org/lune"
|
||||
description = "Roblox library for Lune"
|
||||
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
mlua = { version = "0.9.9", features = ["luau"] }
|
||||
|
||||
glam = "0.27"
|
||||
rand = "0.8"
|
||||
thiserror = "1.0"
|
||||
once_cell = "1.17"
|
||||
|
||||
rbx_binary = "1.0.0"
|
||||
rbx_dom_weak = "3.0.0"
|
||||
rbx_reflection = "5.0.0"
|
||||
rbx_reflection_database = "1.0.0"
|
||||
rbx_xml = "1.0.0"
|
||||
|
||||
lune-utils = { version = "0.1.3", path = "../lune-utils" }
|
|
@ -4,6 +4,15 @@ use rbx_dom_weak::types::{Variant as DomValue, VariantType as DomType};
|
|||
|
||||
use super::extension::DomValueExt;
|
||||
|
||||
/**
|
||||
Checks if the given name is a valid attribute name.
|
||||
|
||||
# Errors
|
||||
|
||||
- If the name starts with the prefix "RBX".
|
||||
- If the name contains any characters other than alphanumeric characters and underscore.
|
||||
- If the name is longer than 100 characters.
|
||||
*/
|
||||
pub fn ensure_valid_attribute_name(name: impl AsRef<str>) -> LuaResult<()> {
|
||||
let name = name.as_ref();
|
||||
if name.to_ascii_uppercase().starts_with("RBX") {
|
||||
|
@ -23,6 +32,13 @@ pub fn ensure_valid_attribute_name(name: impl AsRef<str>) -> LuaResult<()> {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Checks if the given value is a valid attribute value.
|
||||
|
||||
# Errors
|
||||
|
||||
- If the value is not a valid attribute type.
|
||||
*/
|
||||
pub fn ensure_valid_attribute_value(value: &DomValue) -> LuaResult<()> {
|
||||
let is_valid = matches!(
|
||||
value.ty(),
|
|
@ -2,7 +2,7 @@ use mlua::prelude::*;
|
|||
|
||||
use rbx_dom_weak::types::{Variant as DomValue, VariantType as DomType};
|
||||
|
||||
use crate::roblox::{datatypes::extension::DomValueExt, instance::Instance};
|
||||
use crate::{datatypes::extension::DomValueExt, instance::Instance};
|
||||
|
||||
use super::*;
|
||||
|
||||
|
@ -51,7 +51,7 @@ impl<'lua> DomValueToLua<'lua> for LuaValue<'lua> {
|
|||
DomValue::Float32(n) => Ok(LuaValue::Number(*n as f64)),
|
||||
DomValue::String(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))?,
|
||||
)),
|
||||
|
||||
|
@ -65,8 +65,10 @@ impl<'lua> DomValueToLua<'lua> for LuaValue<'lua> {
|
|||
|
||||
// NOTE: Some values are either optional or default and we should handle
|
||||
// that properly here since the userdata conversion above will always fail
|
||||
DomValue::OptionalCFrame(None) => Ok(LuaValue::Nil),
|
||||
DomValue::PhysicalProperties(dom::PhysicalProperties::Default) => Ok(LuaValue::Nil),
|
||||
DomValue::OptionalCFrame(None)
|
||||
| DomValue::PhysicalProperties(dom::PhysicalProperties::Default) => {
|
||||
Ok(LuaValue::Nil)
|
||||
}
|
||||
|
||||
_ => Err(e),
|
||||
},
|
|
@ -6,6 +6,7 @@ pub(crate) trait DomValueExt {
|
|||
|
||||
impl DomValueExt for DomType {
|
||||
fn variant_name(&self) -> Option<&'static str> {
|
||||
#[allow(clippy::enum_glob_use)]
|
||||
use DomType::*;
|
||||
Some(match self {
|
||||
Attributes => "Attributes",
|
|
@ -10,4 +10,4 @@ mod util;
|
|||
|
||||
use result::*;
|
||||
|
||||
pub use crate::roblox::shared::userdata::*;
|
||||
pub use crate::shared::userdata::*;
|
|
@ -3,7 +3,9 @@ use core::fmt;
|
|||
use mlua::prelude::*;
|
||||
use rbx_dom_weak::types::Axes as DomAxes;
|
||||
|
||||
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use crate::exports::LuaExportsTable;
|
||||
|
||||
use super::{super::*, EnumItem};
|
||||
|
||||
|
@ -52,8 +54,7 @@ impl LuaExportsTable<'_> for Axes {
|
|||
check(&e);
|
||||
} else {
|
||||
return Err(LuaError::RuntimeError(format!(
|
||||
"Expected argument #{} to be an EnumItem, got userdata",
|
||||
index
|
||||
"Expected argument #{index} to be an EnumItem, got userdata",
|
||||
)));
|
||||
}
|
||||
} else {
|
|
@ -4,14 +4,16 @@ use mlua::prelude::*;
|
|||
use rand::seq::SliceRandom;
|
||||
use rbx_dom_weak::types::BrickColor as DomBrickColor;
|
||||
|
||||
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use crate::exports::LuaExportsTable;
|
||||
|
||||
use super::{super::*, Color3};
|
||||
|
||||
/**
|
||||
An implementation of the [BrickColor](https://create.roblox.com/docs/reference/engine/datatypes/BrickColor) Roblox datatype.
|
||||
|
||||
This implements all documented properties, methods & constructors of the BrickColor class as of March 2023.
|
||||
This implements all documented properties, methods & constructors of the `BrickColor` class as of March 2023.
|
||||
*/
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct BrickColor {
|
|
@ -1,3 +1,5 @@
|
|||
#![allow(clippy::items_after_statements)]
|
||||
|
||||
use core::fmt;
|
||||
use std::ops;
|
||||
|
||||
|
@ -5,7 +7,9 @@ use glam::{EulerRot, Mat3, Mat4, Quat, Vec3};
|
|||
use mlua::{prelude::*, Variadic};
|
||||
use rbx_dom_weak::types::{CFrame as DomCFrame, Matrix3 as DomMatrix3, Vector3 as DomVector3};
|
||||
|
||||
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use crate::exports::LuaExportsTable;
|
||||
|
||||
use super::{super::*, Vector3};
|
||||
|
||||
|
@ -14,7 +18,7 @@ use super::{super::*, Vector3};
|
|||
Roblox datatype, backed by [`glam::Mat4`].
|
||||
|
||||
This implements all documented properties, methods &
|
||||
constructors of the CFrame class as of March 2023.
|
||||
constructors of the `CFrame` class as of March 2023.
|
||||
*/
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct CFrame(pub Mat4);
|
||||
|
@ -42,6 +46,7 @@ impl CFrame {
|
|||
impl LuaExportsTable<'_> for CFrame {
|
||||
const EXPORT_NAME: &'static str = "CFrame";
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
|
||||
let cframe_angles = |_, (rx, ry, rz): (f32, f32, f32)| {
|
||||
Ok(CFrame(Mat4::from_euler(EulerRot::XYZ, rx, ry, rz)))
|
||||
|
@ -68,8 +73,7 @@ impl LuaExportsTable<'_> for CFrame {
|
|||
Ok(CFrame(Mat4::from_cols(
|
||||
rx.0.extend(0.0),
|
||||
ry.0.extend(0.0),
|
||||
rz.map(|r| r.0)
|
||||
.unwrap_or_else(|| rx.0.cross(ry.0).normalize())
|
||||
rz.map_or_else(|| rx.0.cross(ry.0).normalize(), |r| r.0)
|
||||
.extend(0.0),
|
||||
pos.0.extend(1.0),
|
||||
)))
|
||||
|
@ -195,6 +199,7 @@ impl LuaUserData for CFrame {
|
|||
});
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
// Methods
|
||||
methods.add_method("Inverse", |_, this, ()| Ok(this.inverse()));
|
||||
|
@ -226,34 +231,49 @@ impl LuaUserData for CFrame {
|
|||
methods.add_method(
|
||||
"ToWorldSpace",
|
||||
|_, this, rhs: Variadic<LuaUserDataRef<CFrame>>| {
|
||||
Ok(Variadic::from_iter(rhs.into_iter().map(|cf| *this * *cf)))
|
||||
Ok(rhs
|
||||
.into_iter()
|
||||
.map(|cf| *this * *cf)
|
||||
.collect::<Variadic<_>>())
|
||||
},
|
||||
);
|
||||
methods.add_method(
|
||||
"ToObjectSpace",
|
||||
|_, this, rhs: Variadic<LuaUserDataRef<CFrame>>| {
|
||||
let inverse = this.inverse();
|
||||
Ok(Variadic::from_iter(rhs.into_iter().map(|cf| inverse * *cf)))
|
||||
Ok(rhs
|
||||
.into_iter()
|
||||
.map(|cf| inverse * *cf)
|
||||
.collect::<Variadic<_>>())
|
||||
},
|
||||
);
|
||||
methods.add_method(
|
||||
"PointToWorldSpace",
|
||||
|_, this, rhs: Variadic<LuaUserDataRef<Vector3>>| {
|
||||
Ok(Variadic::from_iter(rhs.into_iter().map(|v3| *this * *v3)))
|
||||
Ok(rhs
|
||||
.into_iter()
|
||||
.map(|v3| *this * *v3)
|
||||
.collect::<Variadic<_>>())
|
||||
},
|
||||
);
|
||||
methods.add_method(
|
||||
"PointToObjectSpace",
|
||||
|_, this, rhs: Variadic<LuaUserDataRef<Vector3>>| {
|
||||
let inverse = this.inverse();
|
||||
Ok(Variadic::from_iter(rhs.into_iter().map(|v3| inverse * *v3)))
|
||||
Ok(rhs
|
||||
.into_iter()
|
||||
.map(|v3| inverse * *v3)
|
||||
.collect::<Variadic<_>>())
|
||||
},
|
||||
);
|
||||
methods.add_method(
|
||||
"VectorToWorldSpace",
|
||||
|_, this, rhs: Variadic<LuaUserDataRef<Vector3>>| {
|
||||
let result = *this - Vector3(this.position());
|
||||
Ok(Variadic::from_iter(rhs.into_iter().map(|v3| result * *v3)))
|
||||
Ok(rhs
|
||||
.into_iter()
|
||||
.map(|v3| result * *v3)
|
||||
.collect::<Variadic<_>>())
|
||||
},
|
||||
);
|
||||
methods.add_method(
|
||||
|
@ -261,8 +281,10 @@ impl LuaUserData for CFrame {
|
|||
|_, this, rhs: Variadic<LuaUserDataRef<Vector3>>| {
|
||||
let inverse = this.inverse();
|
||||
let result = inverse - Vector3(inverse.position());
|
||||
|
||||
Ok(Variadic::from_iter(rhs.into_iter().map(|v3| result * *v3)))
|
||||
Ok(rhs
|
||||
.into_iter()
|
||||
.map(|v3| result * *v3)
|
||||
.collect::<Variadic<_>>())
|
||||
},
|
||||
);
|
||||
#[rustfmt::skip]
|
||||
|
@ -445,7 +467,7 @@ mod cframe_test {
|
|||
Vec3::new(1.0, 2.0, 3.0).extend(1.0),
|
||||
));
|
||||
|
||||
assert_eq!(CFrame::from(dom_cframe), cframe)
|
||||
assert_eq!(CFrame::from(dom_cframe), cframe);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -466,6 +488,6 @@ mod cframe_test {
|
|||
),
|
||||
);
|
||||
|
||||
assert_eq!(DomCFrame::from(cframe), dom_cframe)
|
||||
assert_eq!(DomCFrame::from(cframe), dom_cframe);
|
||||
}
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
#![allow(clippy::many_single_char_names)]
|
||||
|
||||
use core::fmt;
|
||||
use std::ops;
|
||||
|
||||
|
@ -5,7 +7,9 @@ use glam::Vec3;
|
|||
use mlua::prelude::*;
|
||||
use rbx_dom_weak::types::{Color3 as DomColor3, Color3uint8 as DomColor3uint8};
|
||||
|
||||
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use crate::exports::LuaExportsTable;
|
||||
|
||||
use super::super::*;
|
||||
|
||||
|
@ -85,8 +89,7 @@ impl LuaExportsTable<'_> for Color3 {
|
|||
b: (b as f32) / 255f32,
|
||||
}),
|
||||
_ => Err(LuaError::RuntimeError(format!(
|
||||
"Hex color string '{}' contains invalid character",
|
||||
trimmed
|
||||
"Hex color string '{trimmed}' contains invalid character",
|
||||
))),
|
||||
}
|
||||
};
|
||||
|
@ -151,6 +154,7 @@ impl LuaUserData for Color3 {
|
|||
let max = r.max(g).max(b);
|
||||
let diff = max - min;
|
||||
|
||||
#[allow(clippy::float_cmp)]
|
||||
let hue = (match max {
|
||||
max if max == min => 0.0,
|
||||
max if max == r => (g - b) / diff + (if g < b { 6.0 } else { 0.0 }),
|
|
@ -5,14 +5,16 @@ use rbx_dom_weak::types::{
|
|||
ColorSequence as DomColorSequence, ColorSequenceKeypoint as DomColorSequenceKeypoint,
|
||||
};
|
||||
|
||||
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use crate::exports::LuaExportsTable;
|
||||
|
||||
use super::{super::*, Color3, ColorSequenceKeypoint};
|
||||
|
||||
/**
|
||||
An implementation of the [ColorSequence](https://create.roblox.com/docs/reference/engine/datatypes/ColorSequence) Roblox datatype.
|
||||
|
||||
This implements all documented properties, methods & constructors of the ColorSequence class as of March 2023.
|
||||
This implements all documented properties, methods & constructors of the `ColorSequence` class as of March 2023.
|
||||
*/
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ColorSequence {
|
||||
|
@ -87,9 +89,9 @@ impl fmt::Display for ColorSequence {
|
|||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
for (index, keypoint) in self.keypoints.iter().enumerate() {
|
||||
if index < self.keypoints.len() - 1 {
|
||||
write!(f, "{}, ", keypoint)?;
|
||||
write!(f, "{keypoint}, ")?;
|
||||
} else {
|
||||
write!(f, "{}", keypoint)?;
|
||||
write!(f, "{keypoint}")?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
@ -102,7 +104,7 @@ impl From<DomColorSequence> for ColorSequence {
|
|||
keypoints: v
|
||||
.keypoints
|
||||
.iter()
|
||||
.cloned()
|
||||
.copied()
|
||||
.map(ColorSequenceKeypoint::from)
|
||||
.collect(),
|
||||
}
|
||||
|
@ -115,7 +117,7 @@ impl From<ColorSequence> for DomColorSequence {
|
|||
keypoints: v
|
||||
.keypoints
|
||||
.iter()
|
||||
.cloned()
|
||||
.copied()
|
||||
.map(DomColorSequenceKeypoint::from)
|
||||
.collect(),
|
||||
}
|
|
@ -3,14 +3,16 @@ use core::fmt;
|
|||
use mlua::prelude::*;
|
||||
use rbx_dom_weak::types::ColorSequenceKeypoint as DomColorSequenceKeypoint;
|
||||
|
||||
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use crate::exports::LuaExportsTable;
|
||||
|
||||
use super::{super::*, Color3};
|
||||
|
||||
/**
|
||||
An implementation of the [ColorSequenceKeypoint](https://create.roblox.com/docs/reference/engine/datatypes/ColorSequenceKeypoint) Roblox datatype.
|
||||
|
||||
This implements all documented properties, methods & constructors of the ColorSequenceKeypoint class as of March 2023.
|
||||
This implements all documented properties, methods & constructors of the `ColorSequenceKeypoint` class as of March 2023.
|
||||
*/
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct ColorSequenceKeypoint {
|
|
@ -8,7 +8,7 @@ use super::{super::*, Enum};
|
|||
/**
|
||||
An implementation of the [EnumItem](https://create.roblox.com/docs/reference/engine/datatypes/EnumItem) Roblox datatype.
|
||||
|
||||
This implements all documented properties, methods & constructors of the EnumItem class as of March 2023.
|
||||
This implements all documented properties, methods & constructors of the `EnumItem` class as of March 2023.
|
||||
*/
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EnumItem {
|
|
@ -24,8 +24,7 @@ impl LuaUserData for Enums {
|
|||
|_, _, name: String| match Enum::from_name(&name) {
|
||||
Some(e) => Ok(e),
|
||||
None => Err(LuaError::RuntimeError(format!(
|
||||
"The enum '{}' does not exist",
|
||||
name
|
||||
"The enum '{name}' does not exist",
|
||||
))),
|
||||
},
|
||||
);
|
|
@ -1,9 +1,13 @@
|
|||
#![allow(clippy::struct_excessive_bools)]
|
||||
|
||||
use core::fmt;
|
||||
|
||||
use mlua::prelude::*;
|
||||
use rbx_dom_weak::types::Faces as DomFaces;
|
||||
|
||||
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use crate::exports::LuaExportsTable;
|
||||
|
||||
use super::{super::*, EnumItem};
|
||||
|
||||
|
@ -54,8 +58,7 @@ impl LuaExportsTable<'_> for Faces {
|
|||
check(&e);
|
||||
} else {
|
||||
return Err(LuaError::RuntimeError(format!(
|
||||
"Expected argument #{} to be an EnumItem, got userdata",
|
||||
index
|
||||
"Expected argument #{index} to be an EnumItem, got userdata",
|
||||
)));
|
||||
}
|
||||
} else {
|
|
@ -6,7 +6,9 @@ use rbx_dom_weak::types::{
|
|||
Font as DomFont, FontStyle as DomFontStyle, FontWeight as DomFontWeight,
|
||||
};
|
||||
|
||||
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use crate::exports::LuaExportsTable;
|
||||
|
||||
use super::{super::*, EnumItem};
|
||||
|
||||
|
@ -62,7 +64,7 @@ impl LuaExportsTable<'_> for Font {
|
|||
let font_from_name =
|
||||
|_, (file, weight, style): (String, Option<FontWeight>, Option<FontStyle>)| {
|
||||
Ok(Font {
|
||||
family: format!("rbxasset://fonts/families/{}.json", file),
|
||||
family: format!("rbxasset://fonts/families/{file}.json"),
|
||||
weight: weight.unwrap_or_default(),
|
||||
style: style.unwrap_or_default(),
|
||||
cached_id: None,
|
||||
|
@ -72,7 +74,7 @@ impl LuaExportsTable<'_> for Font {
|
|||
let font_from_id =
|
||||
|_, (id, weight, style): (i32, Option<FontWeight>, Option<FontStyle>)| {
|
||||
Ok(Font {
|
||||
family: format!("rbxassetid://{}", id),
|
||||
family: format!("rbxassetid://{id}"),
|
||||
weight: weight.unwrap_or_default(),
|
||||
style: style.unwrap_or_default(),
|
||||
cached_id: None,
|
||||
|
@ -206,7 +208,7 @@ pub(crate) enum FontWeight {
|
|||
}
|
||||
|
||||
impl FontWeight {
|
||||
pub(crate) fn as_u16(&self) -> u16 {
|
||||
pub(crate) fn as_u16(self) -> u16 {
|
||||
match self {
|
||||
Self::Thin => 100,
|
||||
Self::ExtraLight => 200,
|
||||
|
@ -288,12 +290,11 @@ impl<'lua> FromLua<'lua> for FontWeight {
|
|||
if value.parent.desc.name == "FontWeight" {
|
||||
if let Ok(value) = FontWeight::from_str(&value.name) {
|
||||
return Ok(value);
|
||||
} else {
|
||||
message = Some(format!(
|
||||
"Found unknown Enum.FontWeight value '{}'",
|
||||
value.name
|
||||
));
|
||||
}
|
||||
message = Some(format!(
|
||||
"Found unknown Enum.FontWeight value '{}'",
|
||||
value.name
|
||||
));
|
||||
} else {
|
||||
message = Some(format!(
|
||||
"Expected Enum.FontWeight, got Enum.{}",
|
||||
|
@ -316,7 +317,7 @@ impl<'lua> IntoLua<'lua> for FontWeight {
|
|||
None => Err(LuaError::ToLuaConversionError {
|
||||
from: "FontWeight",
|
||||
to: "EnumItem",
|
||||
message: Some(format!("Found unknown Enum.FontWeight value '{}'", self)),
|
||||
message: Some(format!("Found unknown Enum.FontWeight value '{self}'")),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
@ -329,7 +330,7 @@ pub(crate) enum FontStyle {
|
|||
}
|
||||
|
||||
impl FontStyle {
|
||||
pub(crate) fn as_u8(&self) -> u8 {
|
||||
pub(crate) fn as_u8(self) -> u8 {
|
||||
match self {
|
||||
Self::Normal => 0,
|
||||
Self::Italic => 1,
|
||||
|
@ -383,12 +384,11 @@ impl<'lua> FromLua<'lua> for FontStyle {
|
|||
if value.parent.desc.name == "FontStyle" {
|
||||
if let Ok(value) = FontStyle::from_str(&value.name) {
|
||||
return Ok(value);
|
||||
} else {
|
||||
message = Some(format!(
|
||||
"Found unknown Enum.FontStyle value '{}'",
|
||||
value.name
|
||||
));
|
||||
}
|
||||
message = Some(format!(
|
||||
"Found unknown Enum.FontStyle value '{}'",
|
||||
value.name
|
||||
));
|
||||
} else {
|
||||
message = Some(format!(
|
||||
"Expected Enum.FontStyle, got Enum.{}",
|
||||
|
@ -411,7 +411,7 @@ impl<'lua> IntoLua<'lua> for FontStyle {
|
|||
None => Err(LuaError::ToLuaConversionError {
|
||||
from: "FontStyle",
|
||||
to: "EnumItem",
|
||||
message: Some(format!("Found unknown Enum.FontStyle value '{}'", self)),
|
||||
message: Some(format!("Found unknown Enum.FontStyle value '{self}'")),
|
||||
}),
|
||||
}
|
||||
}
|
|
@ -3,14 +3,16 @@ use core::fmt;
|
|||
use mlua::prelude::*;
|
||||
use rbx_dom_weak::types::NumberRange as DomNumberRange;
|
||||
|
||||
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use crate::exports::LuaExportsTable;
|
||||
|
||||
use super::super::*;
|
||||
|
||||
/**
|
||||
An implementation of the [NumberRange](https://create.roblox.com/docs/reference/engine/datatypes/NumberRange) Roblox datatype.
|
||||
|
||||
This implements all documented properties, methods & constructors of the NumberRange class as of March 2023.
|
||||
This implements all documented properties, methods & constructors of the `NumberRange` class as of March 2023.
|
||||
*/
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct NumberRange {
|
|
@ -5,14 +5,16 @@ use rbx_dom_weak::types::{
|
|||
NumberSequence as DomNumberSequence, NumberSequenceKeypoint as DomNumberSequenceKeypoint,
|
||||
};
|
||||
|
||||
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use crate::exports::LuaExportsTable;
|
||||
|
||||
use super::{super::*, NumberSequenceKeypoint};
|
||||
|
||||
/**
|
||||
An implementation of the [NumberSequence](https://create.roblox.com/docs/reference/engine/datatypes/NumberSequence) Roblox datatype.
|
||||
|
||||
This implements all documented properties, methods & constructors of the NumberSequence class as of March 2023.
|
||||
This implements all documented properties, methods & constructors of the `NumberSequence` class as of March 2023.
|
||||
*/
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct NumberSequence {
|
||||
|
@ -91,9 +93,9 @@ impl fmt::Display for NumberSequence {
|
|||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
for (index, keypoint) in self.keypoints.iter().enumerate() {
|
||||
if index < self.keypoints.len() - 1 {
|
||||
write!(f, "{}, ", keypoint)?;
|
||||
write!(f, "{keypoint}, ")?;
|
||||
} else {
|
||||
write!(f, "{}", keypoint)?;
|
||||
write!(f, "{keypoint}")?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
@ -106,7 +108,7 @@ impl From<DomNumberSequence> for NumberSequence {
|
|||
keypoints: v
|
||||
.keypoints
|
||||
.iter()
|
||||
.cloned()
|
||||
.copied()
|
||||
.map(NumberSequenceKeypoint::from)
|
||||
.collect(),
|
||||
}
|
||||
|
@ -119,7 +121,7 @@ impl From<NumberSequence> for DomNumberSequence {
|
|||
keypoints: v
|
||||
.keypoints
|
||||
.iter()
|
||||
.cloned()
|
||||
.copied()
|
||||
.map(DomNumberSequenceKeypoint::from)
|
||||
.collect(),
|
||||
}
|
|
@ -3,14 +3,16 @@ use core::fmt;
|
|||
use mlua::prelude::*;
|
||||
use rbx_dom_weak::types::NumberSequenceKeypoint as DomNumberSequenceKeypoint;
|
||||
|
||||
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use crate::exports::LuaExportsTable;
|
||||
|
||||
use super::super::*;
|
||||
|
||||
/**
|
||||
An implementation of the [NumberSequenceKeypoint](https://create.roblox.com/docs/reference/engine/datatypes/NumberSequenceKeypoint) Roblox datatype.
|
||||
|
||||
This implements all documented properties, methods & constructors of the NumberSequenceKeypoint class as of March 2023.
|
||||
This implements all documented properties, methods & constructors of the `NumberSequenceKeypoint` class as of March 2023.
|
||||
*/
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct NumberSequenceKeypoint {
|
|
@ -3,14 +3,16 @@ use core::fmt;
|
|||
use mlua::prelude::*;
|
||||
use rbx_dom_weak::types::CustomPhysicalProperties as DomCustomPhysicalProperties;
|
||||
|
||||
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use crate::exports::LuaExportsTable;
|
||||
|
||||
use super::{super::*, EnumItem};
|
||||
|
||||
/**
|
||||
An implementation of the [PhysicalProperties](https://create.roblox.com/docs/reference/engine/datatypes/PhysicalProperties) Roblox datatype.
|
||||
|
||||
This implements all documented properties, methods & constructors of the PhysicalProperties class as of March 2023.
|
||||
This implements all documented properties, methods & constructors of the `PhysicalProperties` class as of March 2023.
|
||||
*/
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct PhysicalProperties {
|
|
@ -4,7 +4,9 @@ use glam::Vec3;
|
|||
use mlua::prelude::*;
|
||||
use rbx_dom_weak::types::Ray as DomRay;
|
||||
|
||||
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use crate::exports::LuaExportsTable;
|
||||
|
||||
use super::{super::*, Vector3};
|
||||
|
|
@ -5,7 +5,9 @@ use glam::Vec2;
|
|||
use mlua::prelude::*;
|
||||
use rbx_dom_weak::types::Rect as DomRect;
|
||||
|
||||
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use crate::exports::LuaExportsTable;
|
||||
|
||||
use super::{super::*, Vector2};
|
||||
|
|
@ -4,7 +4,9 @@ use glam::{Mat4, Vec3};
|
|||
use mlua::prelude::*;
|
||||
use rbx_dom_weak::types::Region3 as DomRegion3;
|
||||
|
||||
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use crate::exports::LuaExportsTable;
|
||||
|
||||
use super::{super::*, CFrame, Vector3};
|
||||
|
|
@ -4,7 +4,9 @@ use glam::IVec3;
|
|||
use mlua::prelude::*;
|
||||
use rbx_dom_weak::types::Region3int16 as DomRegion3int16;
|
||||
|
||||
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use crate::exports::LuaExportsTable;
|
||||
|
||||
use super::{super::*, Vector3int16};
|
||||
|
|
@ -4,14 +4,16 @@ use std::ops;
|
|||
use mlua::prelude::*;
|
||||
use rbx_dom_weak::types::UDim as DomUDim;
|
||||
|
||||
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use crate::exports::LuaExportsTable;
|
||||
|
||||
use super::super::*;
|
||||
|
||||
/**
|
||||
An implementation of the [UDim](https://create.roblox.com/docs/reference/engine/datatypes/UDim) Roblox datatype.
|
||||
|
||||
This implements all documented properties, methods & constructors of the UDim class as of March 2023.
|
||||
This implements all documented properties, methods & constructors of the `UDim` class as of March 2023.
|
||||
*/
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct UDim {
|
|
@ -1,3 +1,5 @@
|
|||
#![allow(clippy::items_after_statements)]
|
||||
|
||||
use core::fmt;
|
||||
use std::ops;
|
||||
|
||||
|
@ -5,14 +7,16 @@ use glam::Vec2;
|
|||
use mlua::prelude::*;
|
||||
use rbx_dom_weak::types::UDim2 as DomUDim2;
|
||||
|
||||
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use crate::exports::LuaExportsTable;
|
||||
|
||||
use super::{super::*, UDim};
|
||||
|
||||
/**
|
||||
An implementation of the [UDim2](https://create.roblox.com/docs/reference/engine/datatypes/UDim2) Roblox datatype.
|
||||
|
||||
This implements all documented properties, methods & constructors of the UDim2 class as of March 2023.
|
||||
This implements all documented properties, methods & constructors of the `UDim2` class as of March 2023.
|
||||
*/
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct UDim2 {
|
|
@ -5,7 +5,9 @@ use glam::{Vec2, Vec3};
|
|||
use mlua::prelude::*;
|
||||
use rbx_dom_weak::types::Vector2 as DomVector2;
|
||||
|
||||
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use crate::exports::LuaExportsTable;
|
||||
|
||||
use super::super::*;
|
||||
|
||||
|
@ -50,6 +52,9 @@ impl LuaUserData for Vector2 {
|
|||
|
||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
// Methods
|
||||
methods.add_method("Angle", |_, this, rhs: LuaUserDataRef<Vector2>| {
|
||||
Ok(this.0.angle_between(rhs.0))
|
||||
});
|
||||
methods.add_method("Cross", |_, this, rhs: LuaUserDataRef<Vector2>| {
|
||||
let this_v3 = Vec3::new(this.0.x, this.0.y, 0f32);
|
||||
let rhs_v3 = Vec3::new(rhs.0.x, rhs.0.y, 0f32);
|
||||
|
@ -58,6 +63,14 @@ impl LuaUserData for Vector2 {
|
|||
methods.add_method("Dot", |_, this, rhs: LuaUserDataRef<Vector2>| {
|
||||
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(
|
||||
"Lerp",
|
||||
|_, this, (rhs, alpha): (LuaUserDataRef<Vector2>, f32)| {
|
||||
|
@ -70,6 +83,10 @@ impl LuaUserData for Vector2 {
|
|||
methods.add_method("Min", |_, this, rhs: LuaUserDataRef<Vector2>| {
|
||||
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
|
||||
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
||||
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
||||
|
@ -78,6 +95,7 @@ impl LuaUserData for Vector2 {
|
|||
methods.add_meta_method(LuaMetaMethod::Sub, userdata_impl_sub);
|
||||
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::IDiv, userdata_impl_idiv_f32);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -136,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 {
|
||||
fn from(v: DomVector2) -> Self {
|
||||
Vector2(Vec2 { x: v.x, y: v.y })
|
|
@ -5,7 +5,9 @@ use glam::IVec2;
|
|||
use mlua::prelude::*;
|
||||
use rbx_dom_weak::types::Vector2int16 as DomVector2int16;
|
||||
|
||||
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use crate::exports::LuaExportsTable;
|
||||
|
||||
use super::super::*;
|
||||
|
|
@ -5,10 +5,9 @@ use glam::Vec3;
|
|||
use mlua::prelude::*;
|
||||
use rbx_dom_weak::types::Vector3 as DomVector3;
|
||||
|
||||
use crate::{
|
||||
lune::util::TableBuilder,
|
||||
roblox::{datatypes::util::round_float_decimal, exports::LuaExportsTable},
|
||||
};
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use crate::{datatypes::util::round_float_decimal, exports::LuaExportsTable};
|
||||
|
||||
use super::{super::*, EnumItem};
|
||||
|
||||
|
@ -37,8 +36,7 @@ impl LuaExportsTable<'_> for Vector3 {
|
|||
"Z" => Vector3(Vec3::Z),
|
||||
name => {
|
||||
return Err(LuaError::RuntimeError(format!(
|
||||
"Axis '{}' is not known",
|
||||
name
|
||||
"Axis '{name}' is not known",
|
||||
)))
|
||||
}
|
||||
})
|
||||
|
@ -61,8 +59,7 @@ impl LuaExportsTable<'_> for Vector3 {
|
|||
"Back" => Vector3(Vec3::Z),
|
||||
name => {
|
||||
return Err(LuaError::RuntimeError(format!(
|
||||
"NormalId '{}' is not known",
|
||||
name
|
||||
"NormalId '{name}' is not known",
|
||||
)))
|
||||
}
|
||||
})
|
||||
|
@ -136,6 +133,10 @@ impl LuaUserData for Vector3 {
|
|||
methods.add_method("Min", |_, this, rhs: LuaUserDataRef<Vector3>| {
|
||||
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
|
||||
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
||||
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
||||
|
@ -144,6 +145,7 @@ impl LuaUserData for Vector3 {
|
|||
methods.add_meta_method(LuaMetaMethod::Sub, userdata_impl_sub);
|
||||
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::IDiv, userdata_impl_idiv_f32);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -202,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 {
|
||||
fn from(v: DomVector3) -> Self {
|
||||
Vector3(Vec3 {
|
|
@ -5,7 +5,9 @@ use glam::IVec3;
|
|||
use mlua::prelude::*;
|
||||
use rbx_dom_weak::types::Vector3int16 as DomVector3int16;
|
||||
|
||||
use crate::{lune::util::TableBuilder, roblox::exports::LuaExportsTable};
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use crate::exports::LuaExportsTable;
|
||||
|
||||
use super::super::*;
|
||||
|
|
@ -2,7 +2,7 @@ use std::path::Path;
|
|||
|
||||
use rbx_dom_weak::WeakDom;
|
||||
|
||||
use crate::roblox::shared::instance::class_is_a_service;
|
||||
use crate::shared::instance::class_is_a_service;
|
||||
|
||||
/**
|
||||
A document kind specifier.
|
||||
|
@ -58,13 +58,14 @@ impl DocumentKind {
|
|||
|
||||
Returns `None` if the given dom is empty and as such can not have its kind inferred.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn from_weak_dom(dom: &WeakDom) -> Option<Self> {
|
||||
let mut has_top_level_child = false;
|
||||
let mut has_top_level_service = false;
|
||||
for child_ref in dom.root().children() {
|
||||
if let Some(child_inst) = dom.get_by_ref(*child_ref) {
|
||||
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;
|
||||
break;
|
||||
}
|
|
@ -15,7 +15,7 @@ pub use kind::*;
|
|||
|
||||
use postprocessing::*;
|
||||
|
||||
use crate::roblox::instance::{data_model, Instance};
|
||||
use crate::instance::{data_model, Instance};
|
||||
|
||||
pub type DocumentResult<T> = Result<T, DocumentError>;
|
||||
|
||||
|
@ -78,6 +78,7 @@ impl Document {
|
|||
| Model | Binary | `rbxm` |
|
||||
| Model | Xml | `rbxmx` |
|
||||
*/
|
||||
#[must_use]
|
||||
#[rustfmt::skip]
|
||||
pub fn canonical_extension(kind: DocumentKind, format: DocumentFormat) -> &'static str {
|
||||
match (kind, format) {
|
||||
|
@ -113,6 +114,10 @@ impl Document {
|
|||
Note that detection of model vs place file is heavily dependent on the structure
|
||||
of the file, and a model file with services in it will detect as a place file, so
|
||||
if possible using [`Document::from_bytes`] with an explicit kind should be preferred.
|
||||
|
||||
# Errors
|
||||
|
||||
Errors if the given bytes are not a valid roblox file.
|
||||
*/
|
||||
pub fn from_bytes_auto(bytes: impl AsRef<[u8]>) -> DocumentResult<Self> {
|
||||
let (format, dom) = Self::from_bytes_inner(bytes)?;
|
||||
|
@ -125,6 +130,10 @@ impl Document {
|
|||
|
||||
This will automatically handle and detect if the document
|
||||
should be decoded using a roblox binary or roblox xml format.
|
||||
|
||||
# Errors
|
||||
|
||||
Errors if the given bytes are not a valid roblox file or not of the given kind.
|
||||
*/
|
||||
pub fn from_bytes(bytes: impl AsRef<[u8]>, kind: DocumentKind) -> DocumentResult<Self> {
|
||||
let (format, dom) = Self::from_bytes_inner(bytes)?;
|
||||
|
@ -138,6 +147,10 @@ impl Document {
|
|||
This will use the same format that the document was created
|
||||
with, meaning if the document is a binary document the output
|
||||
will be binary, and vice versa for xml and other future formats.
|
||||
|
||||
# Errors
|
||||
|
||||
Errors if the document can not be encoded.
|
||||
*/
|
||||
pub fn to_bytes(&self) -> DocumentResult<Vec<u8>> {
|
||||
self.to_bytes_with_format(self.format)
|
||||
|
@ -146,6 +159,10 @@ impl Document {
|
|||
/**
|
||||
Encodes the document as a vector of bytes, to
|
||||
be written to a file or sent over the network.
|
||||
|
||||
# Errors
|
||||
|
||||
Errors if the document can not be encoded.
|
||||
*/
|
||||
pub fn to_bytes_with_format(&self, format: DocumentFormat) -> DocumentResult<Vec<u8>> {
|
||||
let mut bytes = Vec::new();
|
||||
|
@ -172,6 +189,7 @@ impl Document {
|
|||
/**
|
||||
Gets the kind this document was created with.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn kind(&self) -> DocumentKind {
|
||||
self.kind
|
||||
}
|
||||
|
@ -179,6 +197,7 @@ impl Document {
|
|||
/**
|
||||
Gets the format this document was created with.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn format(&self) -> DocumentFormat {
|
||||
self.format
|
||||
}
|
||||
|
@ -186,14 +205,17 @@ impl Document {
|
|||
/**
|
||||
Gets the file extension for this document.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn extension(&self) -> &'static str {
|
||||
Self::canonical_extension(self.kind, self.format)
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a DataModel instance out of this place document.
|
||||
Creates a `DataModel` instance out of this place document.
|
||||
|
||||
Will error if the document is not a place.
|
||||
# Errors
|
||||
|
||||
Errors if the document is not a place.
|
||||
*/
|
||||
pub fn into_data_model_instance(mut self) -> DocumentResult<Instance> {
|
||||
if self.kind != DocumentKind::Place {
|
||||
|
@ -219,7 +241,9 @@ impl Document {
|
|||
/**
|
||||
Creates an array of instances out of this model document.
|
||||
|
||||
Will error if the document is not a model.
|
||||
# Errors
|
||||
|
||||
Errors if the document is not a model.
|
||||
*/
|
||||
pub fn into_instance_array(mut self) -> DocumentResult<Vec<Instance>> {
|
||||
if self.kind != DocumentKind::Model {
|
||||
|
@ -237,9 +261,11 @@ impl Document {
|
|||
}
|
||||
|
||||
/**
|
||||
Creates a place document out of a DataModel instance.
|
||||
Creates a place document out of a `DataModel` instance.
|
||||
|
||||
Will error if the instance is not a DataModel.
|
||||
# Errors
|
||||
|
||||
Errors if the instance is not a `DataModel`.
|
||||
*/
|
||||
pub fn from_data_model_instance(i: Instance) -> DocumentResult<Self> {
|
||||
if i.get_class_name() != data_model::CLASS_NAME {
|
||||
|
@ -266,7 +292,9 @@ impl Document {
|
|||
/**
|
||||
Creates a model document out of an array of instances.
|
||||
|
||||
Will error if any of the instances is a DataModel.
|
||||
# Errors
|
||||
|
||||
Errors if any of the instances is a `DataModel`.
|
||||
*/
|
||||
pub fn from_instance_array(v: Vec<Instance>) -> DocumentResult<Self> {
|
||||
for i in &v {
|
|
@ -1,9 +1,9 @@
|
|||
use rbx_dom_weak::{
|
||||
types::{Ref as DomRef, VariantType as DomType},
|
||||
Instance as DomInstance, WeakDom,
|
||||
ustr, Instance as DomInstance, WeakDom,
|
||||
};
|
||||
|
||||
use crate::roblox::shared::instance::class_is_a;
|
||||
use crate::shared::instance::class_is_a;
|
||||
|
||||
pub fn postprocess_dom_for_place(_dom: &mut WeakDom) {
|
||||
// Nothing here yet
|
||||
|
@ -18,8 +18,8 @@ pub fn postprocess_dom_for_model(dom: &mut WeakDom) {
|
|||
remove_matching_prop(inst, DomType::UniqueId, "HistoryId");
|
||||
// Similar story with ScriptGuid - this is used
|
||||
// in the studio-only cloud script drafts feature
|
||||
if class_is_a(&inst.class, "LuaSourceContainer").unwrap_or(false) {
|
||||
inst.properties.remove("ScriptGuid");
|
||||
if class_is_a(inst.class, "LuaSourceContainer").unwrap_or(false) {
|
||||
inst.properties.remove(&ustr("ScriptGuid"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -41,7 +41,8 @@ where
|
|||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
#![allow(clippy::items_after_statements)]
|
||||
|
||||
use mlua::prelude::*;
|
||||
|
||||
use rbx_dom_weak::{
|
||||
|
@ -5,7 +7,7 @@ use rbx_dom_weak::{
|
|||
Instance as DomInstance,
|
||||
};
|
||||
|
||||
use crate::roblox::{
|
||||
use crate::{
|
||||
datatypes::{
|
||||
attributes::{ensure_valid_attribute_name, ensure_valid_attribute_value},
|
||||
conversion::{DomValueToLua, LuaToDomValue},
|
||||
|
@ -17,6 +19,7 @@ use crate::roblox::{
|
|||
|
||||
use super::{data_model, registry::InstanceRegistry, Instance};
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub fn add_methods<'lua, M: LuaUserDataMethods<'lua, Instance>>(m: &mut M) {
|
||||
m.add_meta_method(LuaMetaMethod::ToString, |lua, this, ()| {
|
||||
ensure_not_destroyed(this)?;
|
||||
|
@ -68,7 +71,7 @@ pub fn add_methods<'lua, M: LuaUserDataMethods<'lua, Instance>>(m: &mut M) {
|
|||
"FindFirstAncestorWhichIsA",
|
||||
|lua, this, class_name: String| {
|
||||
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)
|
||||
},
|
||||
);
|
||||
|
@ -101,7 +104,7 @@ pub fn add_methods<'lua, M: LuaUserDataMethods<'lua, Instance>>(m: &mut M) {
|
|||
|lua, this, (class_name, recursive): (String, Option<bool>)| {
|
||||
ensure_not_destroyed(this)?;
|
||||
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)) {
|
||||
this.find_descendant(predicate).into_lua(lua)
|
||||
} else {
|
||||
|
@ -110,8 +113,7 @@ pub fn add_methods<'lua, M: LuaUserDataMethods<'lua, Instance>>(m: &mut M) {
|
|||
},
|
||||
);
|
||||
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(
|
||||
"IsAncestorOf",
|
||||
|
@ -142,7 +144,7 @@ pub fn add_methods<'lua, M: LuaUserDataMethods<'lua, Instance>>(m: &mut M) {
|
|||
ensure_not_destroyed(this)?;
|
||||
let attributes = this.get_attributes();
|
||||
let tab = lua.create_table_with_capacity(0, attributes.len())?;
|
||||
for (key, value) in attributes.into_iter() {
|
||||
for (key, value) in attributes {
|
||||
tab.set(key, LuaValue::dom_value_to_lua(lua, &value)?)?;
|
||||
}
|
||||
Ok(tab)
|
||||
|
@ -152,13 +154,18 @@ pub fn add_methods<'lua, M: LuaUserDataMethods<'lua, Instance>>(m: &mut M) {
|
|||
|lua, this, (attribute_name, lua_value): (String, LuaValue)| {
|
||||
ensure_not_destroyed(this)?;
|
||||
ensure_valid_attribute_name(&attribute_name)?;
|
||||
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(())
|
||||
if lua_value.is_nil() || lua_value.is_null() {
|
||||
this.remove_attribute(attribute_name);
|
||||
Ok(())
|
||||
} else {
|
||||
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()),
|
||||
}
|
||||
},
|
||||
);
|
||||
|
@ -209,26 +216,26 @@ fn instance_property_get<'lua>(
|
|||
this: &Instance,
|
||||
prop_name: String,
|
||||
) -> LuaResult<LuaValue<'lua>> {
|
||||
ensure_not_destroyed(this)?;
|
||||
|
||||
match prop_name.as_str() {
|
||||
"ClassName" => return this.get_class_name().into_lua(lua),
|
||||
"Name" => {
|
||||
return this.get_name().into_lua(lua);
|
||||
}
|
||||
"Parent" => {
|
||||
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 DomValue::Enum(enum_value) = prop {
|
||||
let enum_name = info.enum_name.ok_or_else(|| {
|
||||
LuaError::RuntimeError(format!(
|
||||
"Failed to get property '{}' - encountered unknown enum",
|
||||
prop_name
|
||||
"Failed to get property '{prop_name}' - encountered unknown enum",
|
||||
))
|
||||
})?;
|
||||
EnumItem::from_enum_name_and_value(&enum_name, enum_value.to_u32())
|
||||
|
@ -246,8 +253,7 @@ fn instance_property_get<'lua>(
|
|||
EnumItem::from_enum_name_and_value(&enum_name, enum_value)
|
||||
.ok_or_else(|| {
|
||||
LuaError::RuntimeError(format!(
|
||||
"Failed to get property '{}' - Enum.{} does not contain numeric value {}",
|
||||
prop_name, enum_name, enum_value
|
||||
"Failed to get property '{prop_name}' - Enum.{enum_name} does not contain numeric value {enum_value}",
|
||||
))
|
||||
})?
|
||||
.into_lua(lua)
|
||||
|
@ -258,26 +264,23 @@ fn instance_property_get<'lua>(
|
|||
Ok(LuaValue::Nil)
|
||||
} else {
|
||||
Err(LuaError::RuntimeError(format!(
|
||||
"Failed to get property '{}' - missing default value",
|
||||
prop_name
|
||||
"Failed to get property '{prop_name}' - missing default value",
|
||||
)))
|
||||
}
|
||||
} else {
|
||||
Err(LuaError::RuntimeError(format!(
|
||||
"Failed to get property '{}' - malformed property info",
|
||||
prop_name
|
||||
"Failed to get property '{prop_name}' - malformed property info",
|
||||
)))
|
||||
}
|
||||
} else if let Some(inst) = this.find_child(|inst| inst.name == prop_name) {
|
||||
Ok(LuaValue::UserData(lua.create_userdata(inst)?))
|
||||
} 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) {
|
||||
Ok(LuaValue::Function(method))
|
||||
} else {
|
||||
Err(LuaError::RuntimeError(format!(
|
||||
"{} is not a valid member of {}",
|
||||
prop_name, this
|
||||
"{prop_name} is not a valid member of {this}",
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
@ -318,13 +321,13 @@ fn instance_property_set<'lua>(
|
|||
}
|
||||
type Parent<'lua> = Option<LuaUserDataRef<'lua, Instance>>;
|
||||
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(());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
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 {
|
||||
match LuaUserDataRef::<EnumItem>::from_lua(prop_value, lua) {
|
||||
Ok(given_enum) if given_enum.parent.desc.name == enum_name => {
|
||||
|
@ -347,16 +350,14 @@ fn instance_property_set<'lua>(
|
|||
}
|
||||
} else {
|
||||
Err(LuaError::RuntimeError(format!(
|
||||
"Failed to set property '{}' - malformed property info",
|
||||
prop_name
|
||||
"Failed to set property '{prop_name}' - malformed property info",
|
||||
)))
|
||||
}
|
||||
} 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 {
|
||||
Err(LuaError::RuntimeError(format!(
|
||||
"{} is not a valid member of {}",
|
||||
prop_name, this
|
||||
"{prop_name} is not a valid member of {this}",
|
||||
)))
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
use mlua::prelude::*;
|
||||
|
||||
use crate::roblox::shared::{
|
||||
use crate::shared::{
|
||||
classes::{
|
||||
add_class_restricted_getter, add_class_restricted_method,
|
||||
get_or_create_property_ref_instance,
|
||||
|
@ -26,40 +26,39 @@ pub fn add_methods<'lua, M: LuaUserDataMethods<'lua, Instance>>(m: &mut M) {
|
|||
|
||||
### See Also
|
||||
* [`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> {
|
||||
get_or_create_property_ref_instance(this, "Workspace", "Workspace")
|
||||
}
|
||||
|
||||
/**
|
||||
Gets or creates a service for this DataModel.
|
||||
Gets or creates a service for this `DataModel`.
|
||||
|
||||
### See Also
|
||||
* [`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> {
|
||||
if matches!(class_is_a_service(&service_name), None | Some(false)) {
|
||||
Err(LuaError::RuntimeError(format!(
|
||||
"'{}' is not a valid service name",
|
||||
service_name
|
||||
"'{service_name}' is not a valid service name",
|
||||
)))
|
||||
} else if let Some(service) = this.find_child(|child| child.class == service_name) {
|
||||
Ok(service)
|
||||
} else {
|
||||
let service = Instance::new_orphaned(service_name);
|
||||
service.set_parent(Some(this.clone()));
|
||||
service.set_parent(Some(*this));
|
||||
Ok(service)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Gets a service for this DataModel, if it exists.
|
||||
Gets a service for this `DataModel`, if it exists.
|
||||
|
||||
### See Also
|
||||
* [`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(
|
||||
_: &Lua,
|
||||
|
@ -68,8 +67,7 @@ fn data_model_find_service(
|
|||
) -> LuaResult<Option<Instance>> {
|
||||
if matches!(class_is_a_service(&service_name), None | Some(false)) {
|
||||
Err(LuaError::RuntimeError(format!(
|
||||
"'{}' is not a valid service name",
|
||||
service_name
|
||||
"'{service_name}' is not a valid service name",
|
||||
)))
|
||||
} else if let Some(service) = this.find_child(|child| child.class == service_name) {
|
||||
Ok(Some(service))
|
|
@ -1,3 +1,5 @@
|
|||
#![allow(clippy::missing_panics_doc)]
|
||||
|
||||
use std::{
|
||||
collections::{BTreeMap, VecDeque},
|
||||
fmt,
|
||||
|
@ -9,13 +11,14 @@ use mlua::prelude::*;
|
|||
use once_cell::sync::Lazy;
|
||||
use rbx_dom_weak::{
|
||||
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 crate::{
|
||||
lune::util::TableBuilder,
|
||||
roblox::exports::LuaExportsTable,
|
||||
roblox::shared::instance::{class_exists, class_is_a},
|
||||
exports::LuaExportsTable,
|
||||
shared::instance::{class_exists, class_is_a},
|
||||
};
|
||||
|
||||
pub(crate) mod base;
|
||||
|
@ -31,10 +34,10 @@ const PROPERTY_NAME_TAGS: &str = "Tags";
|
|||
static INTERNAL_DOM: Lazy<Mutex<WeakDom>> =
|
||||
Lazy::new(|| Mutex::new(WeakDom::new(DomInstanceBuilder::new("ROOT"))));
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Instance {
|
||||
pub(crate) dom_ref: DomRef,
|
||||
pub(crate) class_name: String,
|
||||
pub(crate) class_name: Ustr,
|
||||
}
|
||||
|
||||
impl Instance {
|
||||
|
@ -42,47 +45,37 @@ impl Instance {
|
|||
Creates a new `Instance` from an existing dom object ref.
|
||||
|
||||
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,
|
||||
any existing lock must first be released to prevent any deadlocking.
|
||||
*/
|
||||
pub(crate) fn new(dom_ref: DomRef) -> Self {
|
||||
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||
|
||||
let instance = dom
|
||||
.get_by_ref(dom_ref)
|
||||
.expect("Failed to find instance in document");
|
||||
|
||||
if instance.referent() == dom.root_ref() {
|
||||
panic!("Instances can not be created from dom roots")
|
||||
}
|
||||
|
||||
Self {
|
||||
dom_ref,
|
||||
class_name: instance.class.clone(),
|
||||
}
|
||||
#[must_use]
|
||||
pub fn new(dom_ref: DomRef) -> Self {
|
||||
Self::new_opt(dom_ref).expect("Failed to find instance in document")
|
||||
}
|
||||
|
||||
/**
|
||||
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,
|
||||
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");
|
||||
|
||||
if let Some(instance) = dom.get_by_ref(dom_ref) {
|
||||
if instance.referent() == dom.root_ref() {
|
||||
panic!("Instances can not be created from dom roots")
|
||||
}
|
||||
assert!(
|
||||
!(instance.referent() == dom.root_ref()),
|
||||
"Instances can not be created from dom roots"
|
||||
);
|
||||
|
||||
Some(Self {
|
||||
dom_ref,
|
||||
class_name: instance.class.clone(),
|
||||
class_name: instance.class,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
|
@ -92,24 +85,25 @@ impl Instance {
|
|||
/**
|
||||
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,
|
||||
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 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_ref = dom.insert(dom_root, instance);
|
||||
|
||||
Self {
|
||||
dom_ref,
|
||||
class_name: class_name.to_string(),
|
||||
class_name: ustr(class_name),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -117,10 +111,11 @@ impl Instance {
|
|||
Creates a new orphaned `Instance` by transferring
|
||||
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.
|
||||
*/
|
||||
#[must_use]
|
||||
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 dom_root = dom.root_ref();
|
||||
|
@ -146,6 +141,12 @@ impl Instance {
|
|||
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(
|
||||
referents: &[DomRef],
|
||||
external_dom: &mut WeakDom,
|
||||
|
@ -154,7 +155,7 @@ impl Instance {
|
|||
|
||||
let cloned = dom.clone_multiple_into_external(referents, external_dom);
|
||||
|
||||
for referent in cloned.iter() {
|
||||
for referent in &cloned {
|
||||
external_dom.transfer_within(*referent, external_dom.root_ref());
|
||||
}
|
||||
|
||||
|
@ -169,9 +170,10 @@ impl Instance {
|
|||
|
||||
### See Also
|
||||
* [`Clone`](https://create.roblox.com/docs/reference/engine/classes/Instance#Clone)
|
||||
on the Roblox Developer Hub
|
||||
on the Roblox Developer Hub
|
||||
*/
|
||||
pub fn clone_instance(&self) -> Instance {
|
||||
#[must_use]
|
||||
pub fn clone_instance(&self) -> Self {
|
||||
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||
let new_ref = dom.clone_within(self.dom_ref);
|
||||
drop(dom); // Self::new needs mutex handle, drop it first
|
||||
|
@ -192,7 +194,7 @@ impl Instance {
|
|||
|
||||
### See Also
|
||||
* [`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 {
|
||||
if self.is_destroyed() {
|
||||
|
@ -219,7 +221,7 @@ impl Instance {
|
|||
### See Also
|
||||
* [`Instance::Destroy`] for more info about what happens when an instance gets destroyed
|
||||
* [`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) {
|
||||
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||
|
@ -239,10 +241,10 @@ impl Instance {
|
|||
|
||||
### See Also
|
||||
* [`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 {
|
||||
class_is_a(&self.class_name, class_name).unwrap_or(false)
|
||||
class_is_a(self.class_name, class_name).unwrap_or(false)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -252,8 +254,9 @@ impl Instance {
|
|||
|
||||
### See Also
|
||||
* [`ClassName`](https://create.roblox.com/docs/reference/engine/classes/Instance#ClassName)
|
||||
on the Roblox Developer Hub
|
||||
on the Roblox Developer Hub
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn get_class_name(&self) -> &str {
|
||||
self.class_name.as_str()
|
||||
}
|
||||
|
@ -263,7 +266,7 @@ impl Instance {
|
|||
|
||||
### See Also
|
||||
* [`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 {
|
||||
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||
|
@ -279,14 +282,14 @@ impl Instance {
|
|||
|
||||
### See Also
|
||||
* [`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>) {
|
||||
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||
|
||||
dom.get_by_ref_mut(self.dom_ref)
|
||||
.expect("Failed to find instance in document")
|
||||
.name = name.into()
|
||||
.name = name.into();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -294,15 +297,12 @@ impl Instance {
|
|||
|
||||
### See Also
|
||||
* [`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> {
|
||||
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||
|
||||
let parent_ref = dom
|
||||
.get_by_ref(self.dom_ref)
|
||||
.expect("Failed to find instance in document")
|
||||
.parent();
|
||||
let parent_ref = dom.get_by_ref(self.dom_ref)?.parent();
|
||||
|
||||
if parent_ref == dom.root_ref() {
|
||||
None
|
||||
|
@ -317,18 +317,16 @@ impl Instance {
|
|||
|
||||
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
|
||||
* [`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>) {
|
||||
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||
|
||||
let parent_ref = parent
|
||||
.map(|parent| parent.dom_ref)
|
||||
.unwrap_or_else(|| dom.root_ref());
|
||||
let parent_ref = parent.map_or_else(|| dom.root_ref(), |parent| parent.dom_ref);
|
||||
|
||||
dom.transfer_within(self.dom_ref, parent_ref);
|
||||
}
|
||||
|
@ -343,7 +341,7 @@ impl Instance {
|
|||
.get_by_ref(self.dom_ref)
|
||||
.expect("Failed to find instance in document")
|
||||
.properties
|
||||
.get(name.as_ref())
|
||||
.get(&ustr(name.as_ref()))
|
||||
.cloned()
|
||||
}
|
||||
|
||||
|
@ -360,7 +358,7 @@ impl Instance {
|
|||
.get_by_ref_mut(self.dom_ref)
|
||||
.expect("Failed to find instance in document")
|
||||
.properties
|
||||
.insert(name.as_ref().to_string(), value);
|
||||
.insert(ustr(name.as_ref()), value);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -368,7 +366,7 @@ impl Instance {
|
|||
|
||||
### See Also
|
||||
* [`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> {
|
||||
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||
|
@ -376,7 +374,7 @@ impl Instance {
|
|||
.get_by_ref(self.dom_ref)
|
||||
.expect("Failed to find instance in document");
|
||||
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()
|
||||
} else {
|
||||
|
@ -389,7 +387,7 @@ impl Instance {
|
|||
|
||||
### See Also
|
||||
* [`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> {
|
||||
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||
|
@ -397,7 +395,7 @@ impl Instance {
|
|||
.get_by_ref(self.dom_ref)
|
||||
.expect("Failed to find instance in document");
|
||||
if let Some(DomValue::Attributes(attributes)) =
|
||||
inst.properties.get(PROPERTY_NAME_ATTRIBUTES)
|
||||
inst.properties.get(&ustr(PROPERTY_NAME_ATTRIBUTES))
|
||||
{
|
||||
attributes.clone().into_iter().collect()
|
||||
} else {
|
||||
|
@ -410,7 +408,7 @@ impl Instance {
|
|||
|
||||
### See Also
|
||||
* [`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) {
|
||||
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||
|
@ -424,36 +422,59 @@ impl Instance {
|
|||
value => value,
|
||||
};
|
||||
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);
|
||||
} else {
|
||||
let mut attributes = DomAttributes::new();
|
||||
attributes.insert(name.as_ref().to_string(), value);
|
||||
inst.properties.insert(
|
||||
PROPERTY_NAME_ATTRIBUTES.to_string(),
|
||||
ustr(PROPERTY_NAME_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.
|
||||
|
||||
### See Also
|
||||
* [`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>) {
|
||||
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::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());
|
||||
} else {
|
||||
inst.properties.insert(
|
||||
PROPERTY_NAME_TAGS.to_string(),
|
||||
ustr(PROPERTY_NAME_TAGS),
|
||||
DomValue::Tags(vec![name.as_ref().to_string()].into()),
|
||||
);
|
||||
}
|
||||
|
@ -464,14 +485,14 @@ impl Instance {
|
|||
|
||||
### See Also
|
||||
* [`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> {
|
||||
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||
let inst = dom
|
||||
.get_by_ref(self.dom_ref)
|
||||
.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()
|
||||
} else {
|
||||
Vec::new()
|
||||
|
@ -483,14 +504,14 @@ impl Instance {
|
|||
|
||||
### See Also
|
||||
* [`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 {
|
||||
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||
let inst = dom
|
||||
.get_by_ref(self.dom_ref)
|
||||
.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();
|
||||
tags.iter().any(|tag| tag == name)
|
||||
} else {
|
||||
|
@ -503,21 +524,19 @@ impl Instance {
|
|||
|
||||
### See Also
|
||||
* [`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>) {
|
||||
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::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 mut new_tags = tags.iter().map(ToString::to_string).collect::<Vec<_>>();
|
||||
new_tags.retain(|tag| tag != name);
|
||||
inst.properties.insert(
|
||||
PROPERTY_NAME_TAGS.to_string(),
|
||||
DomValue::Tags(new_tags.into()),
|
||||
);
|
||||
inst.properties
|
||||
.insert(ustr(PROPERTY_NAME_TAGS), DomValue::Tags(new_tags.into()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -529,7 +548,7 @@ impl Instance {
|
|||
|
||||
### See Also
|
||||
* [`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> {
|
||||
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||
|
@ -552,7 +571,7 @@ impl Instance {
|
|||
|
||||
### See Also
|
||||
* [`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> {
|
||||
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||
|
@ -586,7 +605,7 @@ impl Instance {
|
|||
|
||||
### See Also
|
||||
* [`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 {
|
||||
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||
|
@ -663,9 +682,8 @@ impl Instance {
|
|||
if predicate(ancestor) {
|
||||
drop(dom); // Self::new needs mutex handle, drop it first
|
||||
return Some(Self::new(ancestor_ref));
|
||||
} else {
|
||||
ancestor_ref = ancestor.parent();
|
||||
}
|
||||
ancestor_ref = ancestor.parent();
|
||||
}
|
||||
|
||||
None
|
||||
|
@ -677,7 +695,7 @@ impl Instance {
|
|||
|
||||
### See Also
|
||||
* [`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>
|
||||
where
|
||||
|
@ -699,9 +717,8 @@ impl Instance {
|
|||
let queue_ref = queue_item.referent();
|
||||
drop(dom); // Self::new needs mutex handle, drop it first
|
||||
return Some(Self::new(queue_ref));
|
||||
} else {
|
||||
queue.extend(queue_item.children())
|
||||
}
|
||||
queue.extend(queue_item.children());
|
||||
}
|
||||
|
||||
None
|
||||
|
@ -717,8 +734,7 @@ impl LuaExportsTable<'_> for Instance {
|
|||
Instance::new_orphaned(class_name).into_lua(lua)
|
||||
} else {
|
||||
Err(LuaError::RuntimeError(format!(
|
||||
"Failed to create Instance - '{}' is not a valid class name",
|
||||
class_name
|
||||
"Failed to create Instance - '{class_name}' is not a valid class name",
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
@ -756,7 +772,7 @@ impl LuaUserData for Instance {
|
|||
|
||||
impl Hash for Instance {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.dom_ref.hash(state)
|
||||
self.dom_ref.hash(state);
|
||||
}
|
||||
}
|
||||
|
|
@ -51,6 +51,13 @@ impl InstanceRegistry {
|
|||
.expect("Missing InstanceRegistry in app data")
|
||||
}
|
||||
|
||||
/**
|
||||
Inserts a method into the instance registry.
|
||||
|
||||
# Errors
|
||||
|
||||
- If the method already exists in the registry.
|
||||
*/
|
||||
pub fn insert_method<'lua>(
|
||||
lua: &'lua Lua,
|
||||
class_name: &str,
|
||||
|
@ -80,6 +87,13 @@ impl InstanceRegistry {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/**
|
||||
Inserts a property getter into the instance registry.
|
||||
|
||||
# Errors
|
||||
|
||||
- If the property already exists in the registry.
|
||||
*/
|
||||
pub fn insert_property_getter<'lua>(
|
||||
lua: &'lua Lua,
|
||||
class_name: &str,
|
||||
|
@ -109,6 +123,13 @@ impl InstanceRegistry {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/**
|
||||
Inserts a property setter into the instance registry.
|
||||
|
||||
# Errors
|
||||
|
||||
- If the property already exists in the registry.
|
||||
*/
|
||||
pub fn insert_property_setter<'lua>(
|
||||
lua: &'lua Lua,
|
||||
class_name: &str,
|
||||
|
@ -138,6 +159,12 @@ impl InstanceRegistry {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/**
|
||||
Finds a method in the instance registry.
|
||||
|
||||
Returns `None` if the method is not found.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn find_method<'lua>(
|
||||
lua: &'lua Lua,
|
||||
instance: &Instance,
|
||||
|
@ -159,6 +186,12 @@ impl InstanceRegistry {
|
|||
})
|
||||
}
|
||||
|
||||
/**
|
||||
Finds a property getter in the instance registry.
|
||||
|
||||
Returns `None` if the property getter is not found.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn find_property_getter<'lua>(
|
||||
lua: &'lua Lua,
|
||||
instance: &Instance,
|
||||
|
@ -180,6 +213,12 @@ impl InstanceRegistry {
|
|||
})
|
||||
}
|
||||
|
||||
/**
|
||||
Finds a property setter in the instance registry.
|
||||
|
||||
Returns `None` if the property setter is not found.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn find_property_setter<'lua>(
|
||||
lua: &'lua Lua,
|
||||
instance: &Instance,
|
||||
|
@ -202,6 +241,16 @@ impl InstanceRegistry {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Gets the class name chain for a given class name.
|
||||
|
||||
The chain starts with the given class name and ends with the root class.
|
||||
|
||||
# Panics
|
||||
|
||||
Panics if the class name is not valid.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn class_name_chain(class_name: &str) -> Vec<&str> {
|
||||
let db = rbx_reflection_database::get();
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
use mlua::prelude::*;
|
||||
use rbx_dom_weak::types::{MaterialColors, TerrainMaterials, Variant};
|
||||
|
||||
use crate::roblox::{
|
||||
use crate::{
|
||||
datatypes::types::{Color3, EnumItem},
|
||||
shared::classes::{add_class_restricted_method, add_class_restricted_method_mut},
|
||||
};
|
||||
|
@ -23,13 +23,12 @@ pub fn add_methods<'lua, M: LuaUserDataMethods<'lua, Instance>>(methods: &mut M)
|
|||
CLASS_NAME,
|
||||
"SetMaterialColor",
|
||||
terrain_set_material_color,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
fn get_or_create_material_colors(instance: &Instance) -> MaterialColors {
|
||||
if let Some(Variant::MaterialColors(material_colors)) = instance.get_property("MaterialColors")
|
||||
{
|
||||
material_colors
|
||||
if let Some(Variant::MaterialColors(inner)) = instance.get_property("MaterialColors") {
|
||||
inner
|
||||
} else {
|
||||
MaterialColors::default()
|
||||
}
|
||||
|
@ -40,7 +39,7 @@ fn get_or_create_material_colors(instance: &Instance) -> MaterialColors {
|
|||
|
||||
### See Also
|
||||
* [`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> {
|
||||
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
|
||||
* [`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(
|
||||
_: &Lua,
|
|
@ -1,8 +1,6 @@
|
|||
use mlua::prelude::*;
|
||||
|
||||
use crate::roblox::shared::classes::{
|
||||
add_class_restricted_getter, get_or_create_property_ref_instance,
|
||||
};
|
||||
use crate::shared::classes::{add_class_restricted_getter, get_or_create_property_ref_instance};
|
||||
|
||||
use super::Instance;
|
||||
|
||||
|
@ -18,7 +16,7 @@ pub fn add_fields<'lua, F: LuaUserDataFields<'lua, Instance>>(f: &mut F) {
|
|||
|
||||
### See Also
|
||||
* [`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> {
|
||||
get_or_create_property_ref_instance(this, "Terrain", "Terrain")
|
||||
|
@ -29,7 +27,7 @@ fn workspace_get_terrain(_: &Lua, this: &Instance) -> LuaResult<Instance> {
|
|||
|
||||
### See Also
|
||||
* [`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> {
|
||||
get_or_create_property_ref_instance(this, "CurrentCamera", "Camera")
|
|
@ -1,6 +1,8 @@
|
|||
#![allow(clippy::cargo_common_metadata)]
|
||||
|
||||
use mlua::prelude::*;
|
||||
|
||||
use crate::lune::util::TableBuilder;
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
pub mod datatypes;
|
||||
pub mod document;
|
||||
|
@ -46,6 +48,16 @@ fn create_all_exports(lua: &Lua) -> LuaResult<Vec<(&'static str, LuaValue)>> {
|
|||
])
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a table containing all the Roblox datatypes, classes, and singletons.
|
||||
|
||||
Note that this is not guaranteed to contain any value unless indexed directly,
|
||||
it may be optimized to use lazy initialization in the future.
|
||||
|
||||
# Errors
|
||||
|
||||
Errors when out of memory or when a value cannot be created.
|
||||
*/
|
||||
pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
|
||||
// FUTURE: We can probably create these lazily as users
|
||||
// index the main exports (this return value) table and
|
|
@ -7,7 +7,7 @@ use rbx_dom_weak::types::Variant as DomVariant;
|
|||
use rbx_reflection::{ClassDescriptor, DataType};
|
||||
|
||||
use super::{property::DatabaseProperty, utils::*};
|
||||
use crate::roblox::datatypes::{
|
||||
use crate::datatypes::{
|
||||
conversion::DomValueToLua, types::EnumItem, userdata_impl_eq, userdata_impl_to_string,
|
||||
};
|
||||
|
||||
|
@ -28,6 +28,7 @@ impl DatabaseClass {
|
|||
/**
|
||||
Get the name of this class.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn get_name(&self) -> String {
|
||||
self.0.name.to_string()
|
||||
}
|
||||
|
@ -37,6 +38,7 @@ impl DatabaseClass {
|
|||
|
||||
May be `None` if no parent class exists.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn get_superclass(&self) -> Option<String> {
|
||||
let sup = self.0.superclass.as_ref()?;
|
||||
Some(sup.to_string())
|
||||
|
@ -45,6 +47,7 @@ impl DatabaseClass {
|
|||
/**
|
||||
Get all known properties for this class.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn get_properties(&self) -> HashMap<String, DatabaseProperty> {
|
||||
self.0
|
||||
.properties
|
||||
|
@ -56,6 +59,7 @@ impl DatabaseClass {
|
|||
/**
|
||||
Get all default values for properties of this class.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn get_defaults(&self) -> HashMap<String, DomVariant> {
|
||||
self.0
|
||||
.default_properties
|
||||
|
@ -71,7 +75,12 @@ impl DatabaseClass {
|
|||
to players at runtime, and top-level class categories.
|
||||
*/
|
||||
pub fn get_tags_str(&self) -> Vec<&'static str> {
|
||||
self.0.tags.iter().map(class_tag_to_str).collect::<Vec<_>>()
|
||||
self.0
|
||||
.tags
|
||||
.iter()
|
||||
.copied()
|
||||
.map(class_tag_to_str)
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -135,14 +144,12 @@ fn make_enum_value(inner: DbClass, name: impl AsRef<str>, value: u32) -> LuaResu
|
|||
let name = name.as_ref();
|
||||
let enum_name = find_enum_name(inner, name).ok_or_else(|| {
|
||||
LuaError::RuntimeError(format!(
|
||||
"Failed to get default property '{}' - No enum descriptor was found",
|
||||
name
|
||||
"Failed to get default property '{name}' - No enum descriptor was found",
|
||||
))
|
||||
})?;
|
||||
EnumItem::from_enum_name_and_value(&enum_name, value).ok_or_else(|| {
|
||||
LuaError::RuntimeError(format!(
|
||||
"Failed to get default property '{}' - Enum.{} does not contain numeric value {}",
|
||||
name, enum_name, value
|
||||
"Failed to get default property '{name}' - Enum.{enum_name} does not contain numeric value {value}",
|
||||
))
|
||||
})
|
||||
}
|
|
@ -4,7 +4,7 @@ use mlua::prelude::*;
|
|||
|
||||
use rbx_reflection::EnumDescriptor;
|
||||
|
||||
use crate::roblox::datatypes::{userdata_impl_eq, userdata_impl_to_string};
|
||||
use crate::datatypes::{userdata_impl_eq, userdata_impl_to_string};
|
||||
|
||||
type DbEnum = &'static EnumDescriptor<'static>;
|
||||
|
||||
|
@ -23,6 +23,7 @@ impl DatabaseEnum {
|
|||
/**
|
||||
Get the name of this enum.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn get_name(&self) -> String {
|
||||
self.0.name.to_string()
|
||||
}
|
||||
|
@ -31,8 +32,9 @@ impl DatabaseEnum {
|
|||
Get all known members of this enum.
|
||||
|
||||
Note that this is a direct map of name -> enum values,
|
||||
and does not actually use the EnumItem datatype itself.
|
||||
and does not actually use the `EnumItem` datatype itself.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn get_items(&self) -> HashMap<String, u32> {
|
||||
self.0
|
||||
.items
|
|
@ -4,7 +4,7 @@ use mlua::prelude::*;
|
|||
|
||||
use rbx_reflection::ReflectionDatabase;
|
||||
|
||||
use crate::roblox::datatypes::userdata_impl_eq;
|
||||
use crate::datatypes::userdata_impl_eq;
|
||||
|
||||
mod class;
|
||||
mod enums;
|
||||
|
@ -30,6 +30,7 @@ impl Database {
|
|||
/**
|
||||
Creates a new database struct, referencing the bundled reflection database.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
@ -40,6 +41,7 @@ impl Database {
|
|||
This will follow the format `x.y.z.w`, which most
|
||||
commonly looks something like `0.567.0.123456789`.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn get_version(&self) -> String {
|
||||
let [x, y, z, w] = self.0.version;
|
||||
format!("{x}.{y}.{z}.{w}")
|
||||
|
@ -48,15 +50,17 @@ impl Database {
|
|||
/**
|
||||
Retrieves a list of all currently known enum names.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn get_enum_names(&self) -> Vec<String> {
|
||||
self.0.enums.keys().map(|e| e.to_string()).collect()
|
||||
self.0.enums.keys().map(ToString::to_string).collect()
|
||||
}
|
||||
|
||||
/**
|
||||
Retrieves a list of all currently known class names.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn get_class_names(&self) -> Vec<String> {
|
||||
self.0.classes.keys().map(|e| e.to_string()).collect()
|
||||
self.0.classes.keys().map(ToString::to_string).collect()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -108,14 +112,17 @@ impl Database {
|
|||
|
||||
impl LuaUserData for Database {
|
||||
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("Version", |_, this| Ok(this.get_version()))
|
||||
fields.add_field_method_get("Version", |_, this| Ok(this.get_version()));
|
||||
}
|
||||
|
||||
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);
|
||||
methods.add_method("GetEnumNames", |_, this, _: ()| Ok(this.get_enum_names()));
|
||||
methods.add_method("GetClassNames", |_, this, _: ()| Ok(this.get_class_names()));
|
||||
methods.add_method("GetEnumNames", |_, this, (): ()| Ok(this.get_enum_names()));
|
||||
methods.add_method(
|
||||
"GetClassNames",
|
||||
|_, this, (): ()| Ok(this.get_class_names()),
|
||||
);
|
||||
methods.add_method("GetEnum", |_, this, name: String| Ok(this.get_enum(name)));
|
||||
methods.add_method("GetClass", |_, this, name: String| Ok(this.get_class(name)));
|
||||
methods.add_method("FindEnum", |_, this, name: String| Ok(this.find_enum(name)));
|
|
@ -5,7 +5,7 @@ use mlua::prelude::*;
|
|||
use rbx_reflection::{ClassDescriptor, PropertyDescriptor};
|
||||
|
||||
use super::utils::*;
|
||||
use crate::roblox::datatypes::{userdata_impl_eq, userdata_impl_to_string};
|
||||
use crate::datatypes::{userdata_impl_eq, userdata_impl_to_string};
|
||||
|
||||
type DbClass = &'static ClassDescriptor<'static>;
|
||||
type DbProp = &'static PropertyDescriptor<'static>;
|
||||
|
@ -25,6 +25,7 @@ impl DatabaseProperty {
|
|||
/**
|
||||
Get the name of this property.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn get_name(&self) -> String {
|
||||
self.1.name.to_string()
|
||||
}
|
||||
|
@ -36,6 +37,7 @@ impl DatabaseProperty {
|
|||
|
||||
For enums this will be a string formatted as `Enum.EnumName`.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn get_datatype_name(&self) -> String {
|
||||
data_type_to_str(self.1.data_type.clone())
|
||||
}
|
||||
|
@ -45,8 +47,9 @@ impl DatabaseProperty {
|
|||
|
||||
All properties are writable and readable in Lune even if scriptability is not.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn get_scriptability_str(&self) -> &'static str {
|
||||
scriptability_to_str(&self.1.scriptability)
|
||||
scriptability_to_str(self.1.scriptability)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -59,6 +62,7 @@ impl DatabaseProperty {
|
|||
self.1
|
||||
.tags
|
||||
.iter()
|
||||
.copied()
|
||||
.map(property_tag_to_str)
|
||||
.collect::<Vec<_>>()
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
use rbx_reflection::{ClassTag, DataType, PropertyTag, Scriptability};
|
||||
|
||||
use crate::roblox::datatypes::extension::DomValueExt;
|
||||
use crate::datatypes::extension::DomValueExt;
|
||||
|
||||
pub fn data_type_to_str(data_type: DataType) -> String {
|
||||
match data_type {
|
||||
|
@ -17,7 +17,7 @@ pub fn data_type_to_str(data_type: DataType) -> String {
|
|||
NOTE: Remember to add any new strings here to typedefs too!
|
||||
*/
|
||||
|
||||
pub fn scriptability_to_str(scriptability: &Scriptability) -> &'static str {
|
||||
pub fn scriptability_to_str(scriptability: Scriptability) -> &'static str {
|
||||
match scriptability {
|
||||
Scriptability::None => "None",
|
||||
Scriptability::Custom => "Custom",
|
||||
|
@ -28,7 +28,7 @@ pub fn scriptability_to_str(scriptability: &Scriptability) -> &'static str {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn property_tag_to_str(tag: &PropertyTag) -> &'static str {
|
||||
pub fn property_tag_to_str(tag: PropertyTag) -> &'static str {
|
||||
match tag {
|
||||
PropertyTag::Deprecated => "Deprecated",
|
||||
PropertyTag::Hidden => "Hidden",
|
||||
|
@ -41,7 +41,7 @@ pub fn property_tag_to_str(tag: &PropertyTag) -> &'static str {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn class_tag_to_str(tag: &ClassTag) -> &'static str {
|
||||
pub fn class_tag_to_str(tag: ClassTag) -> &'static str {
|
||||
match tag {
|
||||
ClassTag::Deprecated => "Deprecated",
|
||||
ClassTag::NotBrowsable => "NotBrowsable",
|
|
@ -2,7 +2,7 @@ use mlua::prelude::*;
|
|||
|
||||
use rbx_dom_weak::types::Variant as DomValue;
|
||||
|
||||
use crate::roblox::instance::Instance;
|
||||
use crate::instance::Instance;
|
||||
|
||||
use super::instance::class_is_a;
|
||||
|
||||
|
@ -20,8 +20,7 @@ pub(crate) fn add_class_restricted_getter<'lua, F: LuaUserDataFields<'lua, Insta
|
|||
field_getter(lua, this)
|
||||
} else {
|
||||
Err(LuaError::RuntimeError(format!(
|
||||
"{} is not a valid member of {}",
|
||||
field_name, class_name
|
||||
"{field_name} is not a valid member of {class_name}",
|
||||
)))
|
||||
}
|
||||
});
|
||||
|
@ -42,8 +41,7 @@ pub(crate) fn add_class_restricted_setter<'lua, F: LuaUserDataFields<'lua, Insta
|
|||
field_getter(lua, this, value)
|
||||
} else {
|
||||
Err(LuaError::RuntimeError(format!(
|
||||
"{} is not a valid member of {}",
|
||||
field_name, class_name
|
||||
"{field_name} is not a valid member of {class_name}",
|
||||
)))
|
||||
}
|
||||
});
|
||||
|
@ -64,8 +62,7 @@ pub(crate) fn add_class_restricted_method<'lua, M: LuaUserDataMethods<'lua, Inst
|
|||
method(lua, this, args)
|
||||
} else {
|
||||
Err(LuaError::RuntimeError(format!(
|
||||
"{} is not a valid member of {}",
|
||||
method_name, class_name
|
||||
"{method_name} is not a valid member of {class_name}",
|
||||
)))
|
||||
}
|
||||
});
|
||||
|
@ -92,8 +89,7 @@ pub(crate) fn add_class_restricted_method_mut<
|
|||
method(lua, this, args)
|
||||
} else {
|
||||
Err(LuaError::RuntimeError(format!(
|
||||
"{} is not a valid member of {}",
|
||||
method_name, class_name
|
||||
"{method_name} is not a valid member of {class_name}",
|
||||
)))
|
||||
}
|
||||
});
|
||||
|
@ -102,7 +98,7 @@ pub(crate) fn add_class_restricted_method_mut<
|
|||
/**
|
||||
Gets or creates the instance child with the given reference prop name and class name.
|
||||
|
||||
Note that the class name here must be an exact match, it is not checked using IsA.
|
||||
Note that the class name here must be an exact match, it is not checked using `IsA`.
|
||||
|
||||
The instance may be in one of several states but this function will guarantee that the
|
||||
property reference is correct and that the instance exists after it has been called:
|
||||
|
@ -126,7 +122,7 @@ pub(crate) fn get_or_create_property_ref_instance(
|
|||
Ok(inst)
|
||||
} else {
|
||||
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));
|
||||
Ok(inst)
|
||||
}
|
|
@ -60,12 +60,12 @@ pub(crate) fn find_property_info(
|
|||
value_type: Some(*value_type),
|
||||
..Default::default()
|
||||
},
|
||||
_ => Default::default(),
|
||||
_ => PropertyInfo::default(),
|
||||
});
|
||||
break;
|
||||
} else if let Some(sup) = &class.superclass {
|
||||
// No property found, we should look at the superclass
|
||||
class_name = Cow::Borrowed(sup)
|
||||
class_name = Cow::Borrowed(sup);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
@ -87,7 +87,7 @@ pub(crate) fn find_property_info(
|
|||
break;
|
||||
} else if let Some(sup) = &class.superclass {
|
||||
// No default value found, we should look at the superclass
|
||||
class_name = Cow::Borrowed(sup)
|
||||
class_name = Cow::Borrowed(sup);
|
||||
} else {
|
||||
break;
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
#![allow(clippy::missing_errors_doc)]
|
||||
|
||||
use std::{any::type_name, cell::RefCell, fmt, ops};
|
||||
|
||||
use mlua::prelude::*;
|
||||
|
@ -5,21 +7,29 @@ use mlua::prelude::*;
|
|||
// Utility functions
|
||||
|
||||
type ListWriter = dyn Fn(&mut fmt::Formatter<'_>, bool, &str) -> fmt::Result;
|
||||
|
||||
#[must_use]
|
||||
pub fn make_list_writer() -> Box<ListWriter> {
|
||||
let first = RefCell::new(true);
|
||||
Box::new(move |f, flag, literal| {
|
||||
if flag {
|
||||
if first.take() {
|
||||
write!(f, "{}", literal)?;
|
||||
write!(f, "{literal}")?;
|
||||
} else {
|
||||
write!(f, ", {}", literal)?;
|
||||
write!(f, ", {literal}")?;
|
||||
}
|
||||
}
|
||||
Ok::<_, fmt::Error>(())
|
||||
})
|
||||
}
|
||||
|
||||
// Userdata metamethod implementations
|
||||
/*
|
||||
Userdata metamethod implementations
|
||||
|
||||
Note that many of these return [`LuaResult`] even though they don't
|
||||
return any errors - this is for consistency reasons and to make it
|
||||
easier to add these blanket implementations to [`LuaUserData`] impls.
|
||||
*/
|
||||
|
||||
pub fn userdata_impl_to_string<D>(_: &Lua, datatype: &D, _: ()) -> LuaResult<String>
|
||||
where
|
||||
|
@ -139,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>
|
||||
where
|
||||
D: LuaUserData + ops::Div<D, Output = D> + ops::Div<i32, Output = D> + Copy + 'static,
|
22
crates/lune-std-datetime/Cargo.toml
Normal file
22
crates/lune-std-datetime/Cargo.toml
Normal file
|
@ -0,0 +1,22 @@
|
|||
[package]
|
||||
name = "lune-std-datetime"
|
||||
version = "0.1.3"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/lune-org/lune"
|
||||
description = "Lune standard library - DateTime"
|
||||
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
mlua = { version = "0.9.9", features = ["luau"] }
|
||||
|
||||
thiserror = "1.0"
|
||||
chrono = "0.4.38"
|
||||
chrono_lc = "0.1.6"
|
||||
|
||||
lune-utils = { version = "0.1.3", path = "../lune-utils" }
|
|
@ -6,31 +6,8 @@ use chrono::prelude::*;
|
|||
use chrono::DateTime as ChronoDateTime;
|
||||
use chrono_lc::LocaleDate;
|
||||
|
||||
use crate::lune::util::TableBuilder;
|
||||
|
||||
mod error;
|
||||
mod values;
|
||||
|
||||
use error::*;
|
||||
use values::*;
|
||||
|
||||
pub fn create(lua: &Lua) -> LuaResult<LuaTable> {
|
||||
TableBuilder::new(lua)?
|
||||
.with_function("fromIsoDate", |_, iso_date: String| {
|
||||
Ok(DateTime::from_iso_date(iso_date)?)
|
||||
})?
|
||||
.with_function("fromLocalTime", |_, values| {
|
||||
Ok(DateTime::from_local_time(&values)?)
|
||||
})?
|
||||
.with_function("fromUniversalTime", |_, values| {
|
||||
Ok(DateTime::from_universal_time(&values)?)
|
||||
})?
|
||||
.with_function("fromUnixTimestamp", |_, timestamp| {
|
||||
Ok(DateTime::from_unix_timestamp_float(timestamp)?)
|
||||
})?
|
||||
.with_function("now", |_, ()| Ok(DateTime::now()))?
|
||||
.build_readonly()
|
||||
}
|
||||
use crate::result::{DateTimeError, DateTimeResult};
|
||||
use crate::values::DateTimeValues;
|
||||
|
||||
const DEFAULT_FORMAT: &str = "%Y-%m-%d %H:%M:%S";
|
||||
const DEFAULT_LOCALE: &str = "en";
|
||||
|
@ -49,6 +26,7 @@ impl DateTime {
|
|||
|
||||
See [`chrono::DateTime::now`] for additional details.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn now() -> Self {
|
||||
Self { inner: Utc::now() }
|
||||
}
|
||||
|
@ -66,6 +44,10 @@ impl DateTime {
|
|||
```
|
||||
|
||||
See [`chrono::DateTime::from_timestamp`] for additional details.
|
||||
|
||||
# Errors
|
||||
|
||||
Returns an error if the input value is out of range.
|
||||
*/
|
||||
pub fn from_unix_timestamp_float(unix_timestamp: f64) -> DateTimeResult<Self> {
|
||||
let whole = unix_timestamp.trunc() as i64;
|
||||
|
@ -84,6 +66,10 @@ impl DateTime {
|
|||
|
||||
See [`chrono::NaiveDate::from_ymd_opt`] and [`chrono::NaiveTime::from_hms_milli_opt`]
|
||||
for additional details and cases where this constructor may return an error.
|
||||
|
||||
# Errors
|
||||
|
||||
Returns an error if the date or time values are invalid.
|
||||
*/
|
||||
pub fn from_universal_time(values: &DateTimeValues) -> DateTimeResult<Self> {
|
||||
let date = NaiveDate::from_ymd_opt(values.year, values.month, values.day)
|
||||
|
@ -108,6 +94,10 @@ impl DateTime {
|
|||
|
||||
See [`chrono::NaiveDate::from_ymd_opt`] and [`chrono::NaiveTime::from_hms_milli_opt`]
|
||||
for additional details and cases where this constructor may return an error.
|
||||
|
||||
# Errors
|
||||
|
||||
Returns an error if the date or time values are invalid or ambiguous.
|
||||
*/
|
||||
pub fn from_local_time(values: &DateTimeValues) -> DateTimeResult<Self> {
|
||||
let date = NaiveDate::from_ymd_opt(values.year, values.month, values.day)
|
||||
|
@ -138,6 +128,7 @@ impl DateTime {
|
|||
|
||||
See [`chrono_lc::DateTime::formatl`] for additional details.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn format_string_local(&self, format: Option<&str>, locale: Option<&str>) -> String {
|
||||
self.inner
|
||||
.with_timezone(&Local)
|
||||
|
@ -156,6 +147,7 @@ impl DateTime {
|
|||
|
||||
See [`chrono_lc::DateTime::formatl`] for additional details.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn format_string_universal(&self, format: Option<&str>, locale: Option<&str>) -> String {
|
||||
self.inner
|
||||
.with_timezone(&Utc)
|
||||
|
@ -171,6 +163,10 @@ impl DateTime {
|
|||
`1996-12-19T16:39:57-08:00`, into a new `DateTime` struct.
|
||||
|
||||
See [`chrono::DateTime::parse_from_rfc3339`] for additional details.
|
||||
|
||||
# Errors
|
||||
|
||||
Returns an error if the input string is not a valid RFC 3339 date-time.
|
||||
*/
|
||||
pub fn from_iso_date(iso_date: impl AsRef<str>) -> DateTimeResult<Self> {
|
||||
let inner = ChronoDateTime::parse_from_rfc3339(iso_date.as_ref())?.with_timezone(&Utc);
|
||||
|
@ -181,6 +177,7 @@ impl DateTime {
|
|||
Extracts individual date & time values from this
|
||||
`DateTime`, using the current local time zone.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn to_local_time(self) -> DateTimeValues {
|
||||
DateTimeValues::from(self.inner.with_timezone(&Local))
|
||||
}
|
||||
|
@ -189,6 +186,7 @@ impl DateTime {
|
|||
Extracts individual date & time values from this
|
||||
`DateTime`, using the universal (UTC) time zone.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn to_universal_time(self) -> DateTimeValues {
|
||||
DateTimeValues::from(self.inner.with_timezone(&Utc))
|
||||
}
|
||||
|
@ -198,6 +196,7 @@ impl DateTime {
|
|||
|
||||
See [`chrono::DateTime::to_rfc3339`] for additional details.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn to_iso_date(self) -> String {
|
||||
self.inner.to_rfc3339()
|
||||
}
|
36
crates/lune-std-datetime/src/lib.rs
Normal file
36
crates/lune-std-datetime/src/lib.rs
Normal file
|
@ -0,0 +1,36 @@
|
|||
#![allow(clippy::cargo_common_metadata)]
|
||||
|
||||
use mlua::prelude::*;
|
||||
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
mod date_time;
|
||||
mod result;
|
||||
mod values;
|
||||
|
||||
pub use self::date_time::DateTime;
|
||||
|
||||
/**
|
||||
Creates the `datetime` standard library module.
|
||||
|
||||
# Errors
|
||||
|
||||
Errors when out of memory.
|
||||
*/
|
||||
pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
|
||||
TableBuilder::new(lua)?
|
||||
.with_function("fromIsoDate", |_, iso_date: String| {
|
||||
Ok(DateTime::from_iso_date(iso_date)?)
|
||||
})?
|
||||
.with_function("fromLocalTime", |_, values| {
|
||||
Ok(DateTime::from_local_time(&values)?)
|
||||
})?
|
||||
.with_function("fromUniversalTime", |_, values| {
|
||||
Ok(DateTime::from_universal_time(&values)?)
|
||||
})?
|
||||
.with_function("fromUnixTimestamp", |_, timestamp| {
|
||||
Ok(DateTime::from_unix_timestamp_float(timestamp)?)
|
||||
})?
|
||||
.with_function("now", |_, ()| Ok(DateTime::now()))?
|
||||
.build_readonly()
|
||||
}
|
|
@ -2,9 +2,9 @@ use mlua::prelude::*;
|
|||
|
||||
use chrono::prelude::*;
|
||||
|
||||
use crate::lune::util::TableBuilder;
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use super::error::{DateTimeError, DateTimeResult};
|
||||
use super::result::{DateTimeError, DateTimeResult};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct DateTimeValues {
|
||||
|
@ -60,10 +60,10 @@ 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,
|
||||
since we generally want to convert into lua when we know we have
|
||||
a fixed point in time, and we guarantee that it doesn't change
|
||||
*/
|
||||
|
@ -117,9 +117,9 @@ impl IntoLua<'_> for DateTimeValues {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Conversion methods between chrono's timezone-aware DateTime to
|
||||
and from our non-timezone-aware DateTimeValues values struct
|
||||
/*
|
||||
Conversion methods between chrono's timezone-aware `DateTime` to
|
||||
and from our non-timezone-aware `DateTimeValues` values struct
|
||||
*/
|
||||
|
||||
impl<T: TimeZone> From<DateTime<T>> for DateTimeValues {
|
23
crates/lune-std-fs/Cargo.toml
Normal file
23
crates/lune-std-fs/Cargo.toml
Normal file
|
@ -0,0 +1,23 @@
|
|||
[package]
|
||||
name = "lune-std-fs"
|
||||
version = "0.1.2"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/lune-org/lune"
|
||||
description = "Lune standard library - FS"
|
||||
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
mlua = { version = "0.9.9", features = ["luau"] }
|
||||
|
||||
bstr = "1.9"
|
||||
|
||||
tokio = { version = "1", default-features = false, features = ["fs"] }
|
||||
|
||||
lune-utils = { version = "0.1.3", path = "../lune-utils" }
|
||||
lune-std-datetime = { version = "0.1.2", path = "../lune-std-datetime" }
|
|
@ -13,7 +13,7 @@ pub struct CopyContents {
|
|||
pub files: Vec<(usize, PathBuf)>,
|
||||
}
|
||||
|
||||
async fn get_contents_at(root: PathBuf, _options: FsWriteOptions) -> LuaResult<CopyContents> {
|
||||
async fn get_contents_at(root: PathBuf, _: FsWriteOptions) -> LuaResult<CopyContents> {
|
||||
let mut dirs = Vec::new();
|
||||
let mut files = Vec::new();
|
||||
|
||||
|
@ -53,11 +53,11 @@ async fn get_contents_at(root: PathBuf, _options: FsWriteOptions) -> LuaResult<C
|
|||
|
||||
// Ensure that all directory and file paths are relative to the root path
|
||||
// SAFETY: Since we only ever push dirs and files relative to the root, unwrap is safe
|
||||
for (_, dir) in dirs.iter_mut() {
|
||||
*dir = dir.strip_prefix(&normalized_root).unwrap().to_path_buf()
|
||||
for (_, dir) in &mut dirs {
|
||||
*dir = dir.strip_prefix(&normalized_root).unwrap().to_path_buf();
|
||||
}
|
||||
for (_, file) in files.iter_mut() {
|
||||
*file = file.strip_prefix(&normalized_root).unwrap().to_path_buf()
|
||||
for (_, file) in &mut files {
|
||||
*file = file.strip_prefix(&normalized_root).unwrap().to_path_buf();
|
||||
}
|
||||
|
||||
// FUTURE: Deduplicate paths such that these directories:
|
52
src/lune/builtins/fs/mod.rs → crates/lune-std-fs/src/lib.rs
Normal file → Executable file
52
src/lune/builtins/fs/mod.rs → crates/lune-std-fs/src/lib.rs
Normal file → Executable file
|
@ -1,20 +1,30 @@
|
|||
use std::io::ErrorKind as IoErrorKind;
|
||||
use std::path::{PathBuf, MAIN_SEPARATOR};
|
||||
#![allow(clippy::cargo_common_metadata)]
|
||||
|
||||
use std::io::ErrorKind as IoErrorKind;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use bstr::{BString, ByteSlice};
|
||||
use mlua::prelude::*;
|
||||
use tokio::fs;
|
||||
|
||||
use crate::lune::util::TableBuilder;
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
mod copy;
|
||||
mod metadata;
|
||||
mod options;
|
||||
|
||||
use copy::copy;
|
||||
use metadata::FsMetadata;
|
||||
use options::FsWriteOptions;
|
||||
use self::copy::copy;
|
||||
use self::metadata::FsMetadata;
|
||||
use self::options::FsWriteOptions;
|
||||
|
||||
pub fn create(lua: &Lua) -> LuaResult<LuaTable> {
|
||||
/**
|
||||
Creates the `fs` standard library module.
|
||||
|
||||
# Errors
|
||||
|
||||
Errors when out of memory.
|
||||
*/
|
||||
pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
|
||||
TableBuilder::new(lua)?
|
||||
.with_async_function("readFile", fs_read_file)?
|
||||
.with_async_function("readDir", fs_read_dir)?
|
||||
|
@ -32,6 +42,7 @@ pub fn create(lua: &Lua) -> LuaResult<LuaTable> {
|
|||
|
||||
async fn fs_read_file(lua: &Lua, path: String) -> LuaResult<LuaString> {
|
||||
let bytes = fs::read(&path).await.into_lua_err()?;
|
||||
|
||||
lua.create_string(bytes)
|
||||
}
|
||||
|
||||
|
@ -39,33 +50,20 @@ async fn fs_read_dir(_: &Lua, path: String) -> LuaResult<Vec<String>> {
|
|||
let mut dir_strings = Vec::new();
|
||||
let mut dir = fs::read_dir(&path).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() {
|
||||
dir_strings.push(dir_path_str.to_owned());
|
||||
if let Some(dir_name_str) = dir_entry.file_name().to_str() {
|
||||
dir_strings.push(dir_name_str.to_owned());
|
||||
} else {
|
||||
return Err(LuaError::RuntimeError(format!(
|
||||
"File path could not be converted into a string: '{}'",
|
||||
dir_entry.path().display()
|
||||
"File name could not be converted into a string: '{}'",
|
||||
dir_entry.file_name().to_string_lossy()
|
||||
)));
|
||||
}
|
||||
}
|
||||
let mut dir_string_prefix = path;
|
||||
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)
|
||||
Ok(dir_strings)
|
||||
}
|
||||
|
||||
async fn fs_write_file(_: &Lua, (path, contents): (String, LuaString<'_>)) -> LuaResult<()> {
|
||||
fs::write(&path, &contents.as_bytes()).await.into_lua_err()
|
||||
async fn fs_write_file(_: &Lua, (path, contents): (String, BString)) -> LuaResult<()> {
|
||||
fs::write(&path, contents.as_bytes()).await.into_lua_err()
|
||||
}
|
||||
|
||||
async fn fs_write_dir(_: &Lua, path: String) -> LuaResult<()> {
|
|
@ -8,7 +8,7 @@ use std::{
|
|||
|
||||
use mlua::prelude::*;
|
||||
|
||||
use crate::lune::builtins::datetime::DateTime;
|
||||
use lune_std_datetime::DateTime;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum FsMetadataKind {
|
18
crates/lune-std-luau/Cargo.toml
Normal file
18
crates/lune-std-luau/Cargo.toml
Normal file
|
@ -0,0 +1,18 @@
|
|||
[package]
|
||||
name = "lune-std-luau"
|
||||
version = "0.1.2"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/lune-org/lune"
|
||||
description = "Lune standard library - Luau"
|
||||
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
mlua = { version = "0.9.9", features = ["luau", "luau-jit"] }
|
||||
|
||||
lune-utils = { version = "0.1.3", path = "../lune-utils" }
|
90
crates/lune-std-luau/src/lib.rs
Normal file
90
crates/lune-std-luau/src/lib.rs
Normal file
|
@ -0,0 +1,90 @@
|
|||
#![allow(clippy::cargo_common_metadata)]
|
||||
|
||||
use mlua::prelude::*;
|
||||
|
||||
use lune_utils::{jit::JitStatus, TableBuilder};
|
||||
|
||||
mod options;
|
||||
|
||||
use self::options::{LuauCompileOptions, LuauLoadOptions};
|
||||
|
||||
const BYTECODE_ERROR_BYTE: u8 = 0;
|
||||
|
||||
/**
|
||||
Creates the `luau` standard library module.
|
||||
|
||||
# Errors
|
||||
|
||||
Errors when out of memory.
|
||||
*/
|
||||
pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
|
||||
TableBuilder::new(lua)?
|
||||
.with_function("compile", compile_source)?
|
||||
.with_function("load", load_source)?
|
||||
.build_readonly()
|
||||
}
|
||||
|
||||
fn compile_source<'lua>(
|
||||
lua: &'lua Lua,
|
||||
(source, options): (LuaString<'lua>, LuauCompileOptions),
|
||||
) -> LuaResult<LuaString<'lua>> {
|
||||
let bytecode = options.into_compiler().compile(source);
|
||||
|
||||
match bytecode.first() {
|
||||
Some(&BYTECODE_ERROR_BYTE) => Err(LuaError::RuntimeError(
|
||||
String::from_utf8_lossy(&bytecode).into_owned(),
|
||||
)),
|
||||
Some(_) => lua.create_string(bytecode),
|
||||
None => panic!("Compiling resulted in empty bytecode"),
|
||||
}
|
||||
}
|
||||
|
||||
fn load_source<'lua>(
|
||||
lua: &'lua Lua,
|
||||
(source, options): (LuaString<'lua>, LuauLoadOptions),
|
||||
) -> LuaResult<LuaFunction<'lua>> {
|
||||
let mut chunk = lua.load(source.as_bytes()).set_name(options.debug_name);
|
||||
let env_changed = options.environment.is_some();
|
||||
|
||||
if let Some(custom_environment) = options.environment {
|
||||
let environment = lua.create_table()?;
|
||||
|
||||
// Inject all globals into the environment
|
||||
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));
|
||||
}
|
||||
|
||||
// Inject the custom environment
|
||||
for pair in custom_environment.pairs() {
|
||||
let (key, value): (LuaValue, LuaValue) = pair?;
|
||||
environment.set(key, value)?;
|
||||
}
|
||||
|
||||
chunk = chunk.set_environment(environment);
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
|
@ -1,8 +1,14 @@
|
|||
#![allow(clippy::struct_field_names)]
|
||||
|
||||
use mlua::prelude::*;
|
||||
use mlua::Compiler as LuaCompiler;
|
||||
|
||||
const DEFAULT_DEBUG_NAME: &str = "luau.load(...)";
|
||||
|
||||
/**
|
||||
Options for compiling Lua source code.
|
||||
*/
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct LuauCompileOptions {
|
||||
pub(crate) optimization_level: u8,
|
||||
pub(crate) coverage_level: u8,
|
||||
|
@ -76,6 +82,8 @@ impl<'lua> FromLua<'lua> for LuauCompileOptions {
|
|||
pub struct LuauLoadOptions<'lua> {
|
||||
pub(crate) debug_name: String,
|
||||
pub(crate) environment: Option<LuaTable<'lua>>,
|
||||
pub(crate) inject_globals: bool,
|
||||
pub(crate) codegen_enabled: bool,
|
||||
}
|
||||
|
||||
impl Default for LuauLoadOptions<'_> {
|
||||
|
@ -83,6 +91,8 @@ impl Default for LuauLoadOptions<'_> {
|
|||
Self {
|
||||
debug_name: DEFAULT_DEBUG_NAME.to_string(),
|
||||
environment: None,
|
||||
inject_globals: true,
|
||||
codegen_enabled: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -102,11 +112,21 @@ impl<'lua> FromLua<'lua> for LuauLoadOptions<'lua> {
|
|||
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
|
||||
}
|
||||
LuaValue::String(s) => Self {
|
||||
debug_name: s.to_string_lossy().to_string(),
|
||||
environment: None,
|
||||
inject_globals: true,
|
||||
codegen_enabled: false,
|
||||
},
|
||||
_ => {
|
||||
return Err(LuaError::FromLuaConversionError {
|
39
crates/lune-std-net/Cargo.toml
Normal file
39
crates/lune-std-net/Cargo.toml
Normal file
|
@ -0,0 +1,39 @@
|
|||
[package]
|
||||
name = "lune-std-net"
|
||||
version = "0.1.2"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/lune-org/lune"
|
||||
description = "Lune standard library - Net"
|
||||
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
mlua = { version = "0.9.9", features = ["luau"] }
|
||||
mlua-luau-scheduler = { version = "0.0.2", path = "../mlua-luau-scheduler" }
|
||||
|
||||
bstr = "1.9"
|
||||
futures-util = "0.3"
|
||||
hyper = { version = "1.1", features = ["full"] }
|
||||
hyper-util = { version = "0.1", features = ["full"] }
|
||||
http = "1.0"
|
||||
http-body-util = { version = "0.1" }
|
||||
hyper-tungstenite = { version = "0.13" }
|
||||
reqwest = { version = "0.11", default-features = false, features = [
|
||||
"rustls-tls",
|
||||
] }
|
||||
tokio-tungstenite = { version = "0.21", features = ["rustls-tls-webpki-roots"] }
|
||||
urlencoding = "2.1"
|
||||
|
||||
tokio = { version = "1", default-features = false, features = [
|
||||
"sync",
|
||||
"net",
|
||||
"macros",
|
||||
] }
|
||||
|
||||
lune-utils = { version = "0.1.3", path = "../lune-utils" }
|
||||
lune-std-serde = { version = "0.1.2", path = "../lune-std-serde" }
|
|
@ -4,10 +4,8 @@ use mlua::prelude::*;
|
|||
|
||||
use reqwest::header::{HeaderMap, HeaderName, HeaderValue, CONTENT_ENCODING};
|
||||
|
||||
use crate::lune::{
|
||||
builtins::serde::compress_decompress::{decompress, CompressDecompressFormat},
|
||||
util::TableBuilder,
|
||||
};
|
||||
use lune_std_serde::{decompress, CompressDecompressFormat};
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use super::{config::RequestConfig, util::header_map_to_table};
|
||||
|
||||
|
@ -103,7 +101,7 @@ impl NetClient {
|
|||
.and_then(|(_, value)| value.to_str().ok())
|
||||
.and_then(CompressDecompressFormat::detect_from_header_str);
|
||||
if let Some(format) = decompress_format {
|
||||
res_bytes = decompress(format, res_bytes).await?;
|
||||
res_bytes = decompress(res_bytes, format).await?;
|
||||
res_decompressed = true;
|
||||
}
|
||||
}
|
|
@ -1,12 +1,16 @@
|
|||
use std::{collections::HashMap, net::Ipv4Addr};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
net::{IpAddr, Ipv4Addr},
|
||||
};
|
||||
|
||||
use bstr::{BString, ByteSlice};
|
||||
use mlua::prelude::*;
|
||||
|
||||
use reqwest::Method;
|
||||
|
||||
use super::util::table_to_hash_map;
|
||||
|
||||
const DEFAULT_IP_ADDRESS: Ipv4Addr = Ipv4Addr::new(127, 0, 0, 1);
|
||||
const DEFAULT_IP_ADDRESS: IpAddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
|
||||
|
||||
const WEB_SOCKET_UPDGRADE_REQUEST_HANDLER: &str = r#"
|
||||
return {
|
||||
|
@ -79,7 +83,7 @@ impl FromLua<'_> for RequestConfig {
|
|||
query: HashMap::new(),
|
||||
headers: HashMap::new(),
|
||||
body: None,
|
||||
options: Default::default(),
|
||||
options: RequestConfigOptions::default(),
|
||||
})
|
||||
} else if let LuaValue::Table(tab) = value {
|
||||
// If we got a table we are able to configure the entire request
|
||||
|
@ -104,10 +108,11 @@ impl FromLua<'_> for RequestConfig {
|
|||
Err(_) => HashMap::new(),
|
||||
};
|
||||
// Extract body
|
||||
let body = match tab.get::<_, LuaString>("body") {
|
||||
let body = match tab.get::<_, BString>("body") {
|
||||
Ok(config_body) => Some(config_body.as_bytes().to_owned()),
|
||||
Err(_) => None,
|
||||
};
|
||||
|
||||
// Convert method string into proper enum
|
||||
let method = method.trim().to_ascii_uppercase();
|
||||
let method = match method.as_ref() {
|
||||
|
@ -155,7 +160,7 @@ impl FromLua<'_> for RequestConfig {
|
|||
|
||||
#[derive(Debug)]
|
||||
pub struct ServeConfig<'a> {
|
||||
pub address: Ipv4Addr,
|
||||
pub address: IpAddr,
|
||||
pub handle_request: LuaFunction<'a>,
|
||||
pub handle_web_socket: Option<LuaFunction<'a>>,
|
||||
}
|
||||
|
@ -175,7 +180,7 @@ impl<'lua> FromLua<'lua> for ServeConfig<'lua> {
|
|||
let handle_request: Option<LuaFunction> = t.get("handleRequest")?;
|
||||
let handle_web_socket: Option<LuaFunction> = t.get("handleWebSocket")?;
|
||||
if handle_request.is_some() || handle_web_socket.is_some() {
|
||||
let address: Ipv4Addr = match &address {
|
||||
let address: IpAddr = match &address {
|
||||
Some(addr) => {
|
||||
let addr_str = addr.to_str()?;
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
#![allow(unused_variables)]
|
||||
#![allow(clippy::cargo_common_metadata)]
|
||||
|
||||
use bstr::BString;
|
||||
use mlua::prelude::*;
|
||||
use mlua_luau_scheduler::LuaSpawnExt;
|
||||
|
||||
|
@ -9,7 +10,7 @@ mod server;
|
|||
mod util;
|
||||
mod websocket;
|
||||
|
||||
use crate::lune::util::TableBuilder;
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use self::{
|
||||
client::{NetClient, NetClientBuilder},
|
||||
|
@ -19,11 +20,18 @@ use self::{
|
|||
websocket::NetWebSocket,
|
||||
};
|
||||
|
||||
use super::serde::encode_decode::{EncodeDecodeConfig, EncodeDecodeFormat};
|
||||
use lune_std_serde::{decode, encode, EncodeDecodeConfig, EncodeDecodeFormat};
|
||||
|
||||
pub fn create(lua: &Lua) -> LuaResult<LuaTable> {
|
||||
/**
|
||||
Creates the `net` standard library module.
|
||||
|
||||
# Errors
|
||||
|
||||
Errors when out of memory.
|
||||
*/
|
||||
pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
|
||||
NetClientBuilder::new()
|
||||
.headers(&[("User-Agent", create_user_agent_header())])?
|
||||
.headers(&[("User-Agent", create_user_agent_header(lua)?)])?
|
||||
.build()?
|
||||
.into_registry(lua);
|
||||
TableBuilder::new(lua)?
|
||||
|
@ -41,12 +49,13 @@ fn net_json_encode<'lua>(
|
|||
lua: &'lua Lua,
|
||||
(val, pretty): (LuaValue<'lua>, Option<bool>),
|
||||
) -> LuaResult<LuaString<'lua>> {
|
||||
EncodeDecodeConfig::from((EncodeDecodeFormat::Json, pretty.unwrap_or_default()))
|
||||
.serialize_to_string(lua, val)
|
||||
let config = EncodeDecodeConfig::from((EncodeDecodeFormat::Json, pretty.unwrap_or_default()));
|
||||
encode(val, lua, config)
|
||||
}
|
||||
|
||||
fn net_json_decode<'lua>(lua: &'lua Lua, json: LuaString<'lua>) -> LuaResult<LuaValue<'lua>> {
|
||||
EncodeDecodeConfig::from(EncodeDecodeFormat::Json).deserialize_from_string(lua, json)
|
||||
fn net_json_decode(lua: &Lua, json: BString) -> LuaResult<LuaValue> {
|
||||
let config = EncodeDecodeConfig::from(EncodeDecodeFormat::Json);
|
||||
decode(json, lua, config)
|
||||
}
|
||||
|
||||
async fn net_request(lua: &Lua, config: RequestConfig) -> LuaResult<LuaTable> {
|
||||
|
@ -56,9 +65,9 @@ async fn net_request(lua: &Lua, config: RequestConfig) -> LuaResult<LuaTable> {
|
|||
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()?;
|
||||
NetWebSocket::new(ws).into_lua_table(lua)
|
||||
NetWebSocket::new(ws).into_lua(lua)
|
||||
}
|
||||
|
||||
async fn net_serve<'lua>(
|
|
@ -10,7 +10,7 @@ use tokio::{net::TcpListener, pin};
|
|||
use mlua::prelude::*;
|
||||
use mlua_luau_scheduler::LuaSpawnExt;
|
||||
|
||||
use crate::lune::util::TableBuilder;
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use super::config::ServeConfig;
|
||||
|
||||
|
@ -81,7 +81,7 @@ pub async fn serve<'lua>(
|
|||
|
||||
// Wait for either a new connection or a shutdown signal
|
||||
tokio::select! {
|
||||
_ = fut_accept => {}
|
||||
() = fut_accept => {}
|
||||
res = fut_shutdown => {
|
||||
// NOTE: We will only get a RecvError here if the serve handle is dropped,
|
||||
// this means lua has garbage collected it and the user does not want
|
||||
|
@ -97,8 +97,8 @@ pub async fn serve<'lua>(
|
|||
TableBuilder::new(lua)?
|
||||
.with_value("ip", addr.ip().to_string())?
|
||||
.with_value("port", addr.port())?
|
||||
.with_function("stop", move |lua, _: ()| match shutdown_tx.send(true) {
|
||||
Ok(_) => Ok(()),
|
||||
.with_function("stop", move |_, (): ()| match shutdown_tx.send(true) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(_) => Err(LuaError::runtime("Server already stopped")),
|
||||
})?
|
||||
.build_readonly()
|
56
crates/lune-std-net/src/server/request.rs
Normal file
56
crates/lune-std-net/src/server/request.rs
Normal file
|
@ -0,0 +1,56 @@
|
|||
use std::{collections::HashMap, net::SocketAddr};
|
||||
|
||||
use http::request::Parts;
|
||||
|
||||
use mlua::prelude::*;
|
||||
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
pub(super) struct LuaRequest {
|
||||
pub(super) _remote_addr: SocketAddr,
|
||||
pub(super) head: Parts,
|
||||
pub(super) body: Vec<u8>,
|
||||
}
|
||||
|
||||
impl LuaRequest {
|
||||
pub fn into_lua_table(self, lua: &Lua) -> LuaResult<LuaTable> {
|
||||
let method = self.head.method.as_str().to_string();
|
||||
let path = self.head.uri.path().to_string();
|
||||
let body = lua.create_string(&self.body)?;
|
||||
|
||||
#[allow(clippy::mutable_key_type)]
|
||||
let query: HashMap<LuaString, LuaString> = self
|
||||
.head
|
||||
.uri
|
||||
.query()
|
||||
.unwrap_or_default()
|
||||
.split('&')
|
||||
.filter_map(|q| q.split_once('='))
|
||||
.map(|(k, v)| {
|
||||
let k = lua.create_string(k)?;
|
||||
let v = lua.create_string(v)?;
|
||||
Ok((k, v))
|
||||
})
|
||||
.collect::<LuaResult<_>>()?;
|
||||
|
||||
#[allow(clippy::mutable_key_type)]
|
||||
let headers: HashMap<LuaString, LuaString> = self
|
||||
.head
|
||||
.headers
|
||||
.iter()
|
||||
.map(|(k, v)| {
|
||||
let k = lua.create_string(k.as_str())?;
|
||||
let v = lua.create_string(v.as_bytes())?;
|
||||
Ok((k, v))
|
||||
})
|
||||
.collect::<LuaResult<_>>()?;
|
||||
|
||||
TableBuilder::new(lua)?
|
||||
.with_value("method", method)?
|
||||
.with_value("path", path)?
|
||||
.with_value("query", query)?
|
||||
.with_value("headers", headers)?
|
||||
.with_value("body", body)?
|
||||
.build()
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use bstr::{BString, ByteSlice};
|
||||
use http_body_util::Full;
|
||||
use hyper::{
|
||||
body::Bytes,
|
||||
|
@ -56,7 +57,7 @@ impl FromLua<'_> for LuaResponse {
|
|||
LuaValue::Table(t) => {
|
||||
let status: Option<u16> = t.get("status")?;
|
||||
let headers: Option<LuaTable> = t.get("headers")?;
|
||||
let body: Option<LuaString> = t.get("body")?;
|
||||
let body: Option<BString> = t.get("body")?;
|
||||
|
||||
let mut headers_map = HeaderMap::new();
|
||||
if let Some(headers) = headers {
|
|
@ -40,13 +40,13 @@ impl Service<Request<Incoming>> for Svc {
|
|||
lua.spawn_local(async move {
|
||||
let sock = sock.await.unwrap();
|
||||
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 =
|
||||
keys.websocket_handler(&lua_inner).unwrap().unwrap();
|
||||
|
||||
lua_inner
|
||||
.push_thread_back(handler_websocket, lua_tab)
|
||||
.push_thread_back(handler_websocket, lua_val)
|
||||
.unwrap();
|
||||
});
|
||||
|
||||
|
@ -66,8 +66,9 @@ impl Service<Request<Incoming>> for Svc {
|
|||
head,
|
||||
body,
|
||||
};
|
||||
let lua_req_table = lua_req.into_lua_table(&lua)?;
|
||||
|
||||
let thread_id = lua.push_thread_back(handler_request, lua_req)?;
|
||||
let thread_id = lua.push_thread_back(handler_request, lua_req_table)?;
|
||||
lua.track_thread(thread_id);
|
||||
lua.wait_for_thread(thread_id).await;
|
||||
let thread_res = lua
|
|
@ -5,14 +5,21 @@ use reqwest::header::HeaderMap;
|
|||
|
||||
use mlua::prelude::*;
|
||||
|
||||
use crate::lune::util::TableBuilder;
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
pub fn create_user_agent_header() -> String {
|
||||
let (github_owner, github_repo) = env!("CARGO_PKG_REPOSITORY")
|
||||
.trim_start_matches("https://github.com/")
|
||||
.split_once('/')
|
||||
.unwrap();
|
||||
format!("{github_owner}-{github_repo}-cli")
|
||||
pub fn create_user_agent_header(lua: &Lua) -> LuaResult<String> {
|
||||
let version_global = lua
|
||||
.globals()
|
||||
.get::<_, LuaString>("_VERSION")
|
||||
.expect("Missing _VERSION global");
|
||||
|
||||
let version_global_str = version_global
|
||||
.to_str()
|
||||
.context("Invalid utf8 found in _VERSION global")?;
|
||||
|
||||
let (package_name, full_version) = version_global_str.split_once(' ').unwrap();
|
||||
|
||||
Ok(format!("{}/{}", package_name.to_lowercase(), full_version))
|
||||
}
|
||||
|
||||
pub fn header_map_to_table(
|
||||
|
@ -21,7 +28,7 @@ pub fn header_map_to_table(
|
|||
remove_content_headers: bool,
|
||||
) -> LuaResult<LuaTable> {
|
||||
let mut res_headers: HashMap<String, Vec<String>> = HashMap::new();
|
||||
for (name, value) in headers.iter() {
|
||||
for (name, value) in &headers {
|
||||
let name = name.as_str();
|
||||
let value = value.to_str().unwrap().to_owned();
|
||||
if let Some(existing) = res_headers.get_mut(name) {
|
|
@ -3,6 +3,7 @@ use std::sync::{
|
|||
Arc,
|
||||
};
|
||||
|
||||
use bstr::{BString, ByteSlice};
|
||||
use mlua::prelude::*;
|
||||
|
||||
use futures_util::{
|
||||
|
@ -22,29 +23,6 @@ use hyper_tungstenite::{
|
|||
WebSocketStream,
|
||||
};
|
||||
|
||||
use crate::lune::util::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)]
|
||||
pub struct NetWebSocket<T> {
|
||||
close_code_exists: Arc<AtomicBool>,
|
||||
|
@ -124,25 +102,6 @@ where
|
|||
let mut ws = self.write_stream.lock().await;
|
||||
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>
|
||||
|
@ -154,13 +113,13 @@ where
|
|||
}
|
||||
|
||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_async_method("close", |lua, this, code: Option<u16>| async move {
|
||||
methods.add_async_method("close", |_, this, code: Option<u16>| async move {
|
||||
this.close(code).await
|
||||
});
|
||||
|
||||
methods.add_async_method(
|
||||
"send",
|
||||
|_, this, (string, as_binary): (LuaString, Option<bool>)| async move {
|
||||
|_, this, (string, as_binary): (BString, Option<bool>)| async move {
|
||||
this.send(if as_binary.unwrap_or_default() {
|
||||
WsMessage::Binary(string.as_bytes().to_vec())
|
||||
} else {
|
||||
|
@ -171,7 +130,7 @@ where
|
|||
},
|
||||
);
|
||||
|
||||
methods.add_async_method("next", |lua, this, _: ()| async move {
|
||||
methods.add_async_method("next", |lua, this, (): ()| async move {
|
||||
let msg = this.next().await?;
|
||||
|
||||
if let Some(WsMessage::Close(Some(frame))) = msg.as_ref() {
|
34
crates/lune-std-process/Cargo.toml
Normal file
34
crates/lune-std-process/Cargo.toml
Normal file
|
@ -0,0 +1,34 @@
|
|||
[package]
|
||||
name = "lune-std-process"
|
||||
version = "0.1.3"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/lune-org/lune"
|
||||
description = "Lune standard library - Process"
|
||||
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
mlua = { version = "0.9.9", features = ["luau"] }
|
||||
mlua-luau-scheduler = { version = "0.0.2", path = "../mlua-luau-scheduler" }
|
||||
|
||||
directories = "5.0"
|
||||
pin-project = "1.0"
|
||||
os_str_bytes = { version = "7.0", features = ["conversions"] }
|
||||
|
||||
bstr = "1.9"
|
||||
bytes = "1.6.0"
|
||||
|
||||
tokio = { version = "1", default-features = false, features = [
|
||||
"io-std",
|
||||
"io-util",
|
||||
"process",
|
||||
"rt",
|
||||
"sync",
|
||||
] }
|
||||
|
||||
lune-utils = { version = "0.1.3", path = "../lune-utils" }
|
289
crates/lune-std-process/src/lib.rs
Normal file
289
crates/lune-std-process/src/lib.rs
Normal file
|
@ -0,0 +1,289 @@
|
|||
#![allow(clippy::cargo_common_metadata)]
|
||||
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
env::{
|
||||
self,
|
||||
consts::{ARCH, OS},
|
||||
},
|
||||
path::MAIN_SEPARATOR,
|
||||
process::Stdio,
|
||||
rc::Rc,
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use mlua::prelude::*;
|
||||
|
||||
use lune_utils::TableBuilder;
|
||||
use mlua_luau_scheduler::{Functions, LuaSpawnExt};
|
||||
use options::ProcessSpawnOptionsStdio;
|
||||
use os_str_bytes::RawOsString;
|
||||
use stream::{ChildProcessReader, ChildProcessWriter};
|
||||
use tokio::{io::AsyncWriteExt, process::Child, sync::RwLock};
|
||||
|
||||
mod options;
|
||||
mod stream;
|
||||
mod tee_writer;
|
||||
mod wait_for_child;
|
||||
|
||||
use self::options::ProcessSpawnOptions;
|
||||
use self::wait_for_child::wait_for_child;
|
||||
|
||||
use lune_utils::path::get_current_dir;
|
||||
|
||||
/**
|
||||
Creates the `process` standard library module.
|
||||
|
||||
# Errors
|
||||
|
||||
Errors when out of memory.
|
||||
*/
|
||||
#[allow(clippy::missing_panics_doc)]
|
||||
pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
|
||||
let mut cwd_str = get_current_dir()
|
||||
.to_str()
|
||||
.expect("cwd should be valid UTF-8")
|
||||
.to_string();
|
||||
if !cwd_str.ends_with(MAIN_SEPARATOR) {
|
||||
cwd_str.push(MAIN_SEPARATOR);
|
||||
}
|
||||
// Create constants for OS & processor architecture
|
||||
let os = lua.create_string(OS.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
|
||||
let args_vec = lua
|
||||
.app_data_ref::<Vec<String>>()
|
||||
.ok_or_else(|| LuaError::runtime("Missing args vec in Lua app data"))?
|
||||
.clone();
|
||||
let args_tab = TableBuilder::new(lua)?
|
||||
.with_sequential_values(args_vec)?
|
||||
.build_readonly()?;
|
||||
// Create proxied table for env that gets & sets real env vars
|
||||
let env_tab = TableBuilder::new(lua)?
|
||||
.with_metatable(
|
||||
TableBuilder::new(lua)?
|
||||
.with_function(LuaMetaMethod::Index.name(), process_env_get)?
|
||||
.with_function(LuaMetaMethod::NewIndex.name(), process_env_set)?
|
||||
.with_function(LuaMetaMethod::Iter.name(), process_env_iter)?
|
||||
.build_readonly()?,
|
||||
)?
|
||||
.build_readonly()?;
|
||||
// Create our process exit function, the scheduler crate provides this
|
||||
let fns = Functions::new(lua)?;
|
||||
let process_exit = fns.exit;
|
||||
// Create the full process table
|
||||
TableBuilder::new(lua)?
|
||||
.with_value("os", os)?
|
||||
.with_value("arch", arch)?
|
||||
.with_value("endianness", endianness)?
|
||||
.with_value("args", args_tab)?
|
||||
.with_value("cwd", cwd_str)?
|
||||
.with_value("env", env_tab)?
|
||||
.with_value("exit", process_exit)?
|
||||
.with_async_function("exec", process_exec)?
|
||||
.with_function("create", process_create)?
|
||||
.build_readonly()
|
||||
}
|
||||
|
||||
fn process_env_get<'lua>(
|
||||
lua: &'lua Lua,
|
||||
(_, key): (LuaValue<'lua>, String),
|
||||
) -> LuaResult<LuaValue<'lua>> {
|
||||
match env::var_os(key) {
|
||||
Some(value) => {
|
||||
let raw_value = RawOsString::new(value);
|
||||
Ok(LuaValue::String(
|
||||
lua.create_string(raw_value.to_raw_bytes())?,
|
||||
))
|
||||
}
|
||||
None => Ok(LuaValue::Nil),
|
||||
}
|
||||
}
|
||||
|
||||
fn process_env_set<'lua>(
|
||||
_: &'lua Lua,
|
||||
(_, key, value): (LuaValue<'lua>, String, Option<String>),
|
||||
) -> LuaResult<()> {
|
||||
// Make sure key is valid, otherwise set_var will panic
|
||||
if key.is_empty() {
|
||||
Err(LuaError::RuntimeError("Key must not be empty".to_string()))
|
||||
} else if key.contains('=') {
|
||||
Err(LuaError::RuntimeError(
|
||||
"Key must not contain the equals character '='".to_string(),
|
||||
))
|
||||
} else if key.contains('\0') {
|
||||
Err(LuaError::RuntimeError(
|
||||
"Key must not contain the NUL character".to_string(),
|
||||
))
|
||||
} else if let Some(value) = value {
|
||||
// Make sure value is valid, otherwise set_var will panic
|
||||
if value.contains('\0') {
|
||||
Err(LuaError::RuntimeError(
|
||||
"Value must not contain the NUL character".to_string(),
|
||||
))
|
||||
} else {
|
||||
env::set_var(&key, &value);
|
||||
Ok(())
|
||||
}
|
||||
} else {
|
||||
env::remove_var(&key);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn process_env_iter<'lua>(
|
||||
lua: &'lua Lua,
|
||||
(_, ()): (LuaValue<'lua>, ()),
|
||||
) -> LuaResult<LuaFunction<'lua>> {
|
||||
let mut vars = env::vars_os().collect::<Vec<_>>().into_iter();
|
||||
lua.create_function_mut(move |lua, (): ()| match vars.next() {
|
||||
Some((key, value)) => {
|
||||
let raw_key = RawOsString::new(key);
|
||||
let raw_value = RawOsString::new(value);
|
||||
Ok((
|
||||
LuaValue::String(lua.create_string(raw_key.to_raw_bytes())?),
|
||||
LuaValue::String(lua.create_string(raw_value.to_raw_bytes())?),
|
||||
))
|
||||
}
|
||||
None => Ok((LuaValue::Nil, LuaValue::Nil)),
|
||||
})
|
||||
}
|
||||
|
||||
async fn process_exec(
|
||||
lua: &Lua,
|
||||
(program, args, options): (String, Option<Vec<String>>, ProcessSpawnOptions),
|
||||
) -> LuaResult<LuaTable> {
|
||||
let res = lua
|
||||
.spawn(async move {
|
||||
let cmd = spawn_command_with_stdin(program, args, options.clone()).await?;
|
||||
wait_for_child(cmd, options.stdio.stdout, options.stdio.stderr).await
|
||||
})
|
||||
.await?;
|
||||
|
||||
/*
|
||||
NOTE: If an exit code was not given by the child process,
|
||||
we default to 1 if it yielded any error output, otherwise 0
|
||||
|
||||
An exit code may be missing if the process was terminated by
|
||||
some external signal, which is the only time we use this default
|
||||
*/
|
||||
let code = res
|
||||
.status
|
||||
.code()
|
||||
.unwrap_or(i32::from(!res.stderr.is_empty()));
|
||||
|
||||
// Construct and return a readonly lua table with results
|
||||
TableBuilder::new(lua)?
|
||||
.with_value("ok", code == 0)?
|
||||
.with_value("code", code)?
|
||||
.with_value("stdout", lua.create_string(&res.stdout)?)?
|
||||
.with_value("stderr", lua.create_string(&res.stderr)?)?
|
||||
.build_readonly()
|
||||
}
|
||||
|
||||
#[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,
|
||||
args: Option<Vec<String>>,
|
||||
mut options: ProcessSpawnOptions,
|
||||
) -> LuaResult<Child> {
|
||||
let stdin = options.stdio.stdin.take();
|
||||
|
||||
let mut child = spawn_command(program, args, options)?;
|
||||
|
||||
if let Some(stdin) = stdin {
|
||||
let mut child_stdin = child.stdin.take().unwrap();
|
||||
child_stdin.write_all(&stdin).await.into_lua_err()?;
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
use std::{fmt, process::Stdio, str::FromStr};
|
||||
|
||||
use itertools::Itertools;
|
||||
use mlua::prelude::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
|
@ -55,6 +54,7 @@ impl FromStr for ProcessSpawnOptionsStdioKind {
|
|||
ProcessSpawnOptionsStdioKind::all()
|
||||
.iter()
|
||||
.map(|k| format!("'{k}'"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
)))
|
||||
}
|
|
@ -56,7 +56,7 @@ impl<'lua> FromLua<'lua> for ProcessSpawnOptions {
|
|||
"Invalid value for option 'cwd' - failed to get home directory",
|
||||
)
|
||||
})?;
|
||||
cwd = user_dirs.home_dir().join(stripped)
|
||||
cwd = user_dirs.home_dir().join(stripped);
|
||||
}
|
||||
if !cwd.exists() {
|
||||
return Err(LuaError::runtime(
|
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
|
||||
W: AsyncWrite + Unpin,
|
||||
{
|
|
@ -24,8 +24,7 @@ where
|
|||
R: AsyncRead + Unpin,
|
||||
{
|
||||
Ok(match kind {
|
||||
ProcessSpawnOptionsStdioKind::None => Vec::new(),
|
||||
ProcessSpawnOptionsStdioKind::Forward => Vec::new(),
|
||||
ProcessSpawnOptionsStdioKind::None | ProcessSpawnOptionsStdioKind::Forward => Vec::new(),
|
||||
ProcessSpawnOptionsStdioKind::Default => {
|
||||
let mut read_from =
|
||||
read_from.expect("read_from must be Some when stdio kind is Default");
|
21
crates/lune-std-regex/Cargo.toml
Normal file
21
crates/lune-std-regex/Cargo.toml
Normal file
|
@ -0,0 +1,21 @@
|
|||
[package]
|
||||
name = "lune-std-regex"
|
||||
version = "0.1.2"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/lune-org/lune"
|
||||
description = "Lune standard library - RegEx"
|
||||
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
mlua = { version = "0.9.9", features = ["luau"] }
|
||||
|
||||
regex = "1.10"
|
||||
self_cell = "1.0"
|
||||
|
||||
lune-utils = { version = "0.1.3", path = "../lune-utils" }
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue