mirror of
https://github.com/lune-org/lune.git
synced 2025-05-04 10:43:57 +01:00
Compare commits
94 commits
Author | SHA1 | Date | |
---|---|---|---|
|
df56cd58e7 | ||
|
66e3b58cd7 | ||
|
fb33d1812d | ||
|
0ddaaaefb5 | ||
|
2e5b3bb5eb | ||
|
6645631c46 | ||
|
120048ae95 | ||
|
2d8e58b028 | ||
|
b1fc60023d | ||
|
1429450a64 | ||
|
d2a89f41c8 | ||
|
9c9b90d70d | ||
|
d425d2568a | ||
|
4c2bbcf425 | ||
|
461ca24c33 | ||
|
7fd390dead | ||
|
c35eaa7899 | ||
|
b57fa6fad3 | ||
|
3e80a0a1c4 | ||
|
ac8c809a20 | ||
|
4079842a33 | ||
|
464c431697 | ||
|
39f6319bdb | ||
|
62910f02ab | ||
|
1f43ff89f7 | ||
|
e234eab813 | ||
|
fc12e15a9a | ||
|
c3f483b3dd | ||
|
98e68629c9 | ||
|
0954730646 | ||
|
27409f1bf4 | ||
|
c13728fce4 | ||
|
7cbeafc5b3 | ||
|
5a23cf04fa | ||
|
f6d2afb003 | ||
|
b74568eea0 | ||
|
387080cf45 | ||
|
1119f0d46b | ||
|
551120fba1 | ||
|
88494fedcb | ||
|
0a3b57697d | ||
|
43f11cd9f5 | ||
|
c163c6f157 | ||
|
4ce2d3a80f | ||
|
74375ff708 | ||
|
a673f80c95 | ||
|
acd0f126e5 | ||
|
0d653450de | ||
|
b147e83d3c | ||
|
58a65afc2c | ||
|
b1d60d904c | ||
|
6b45f44d1f | ||
|
c4374a0e18 | ||
|
8059026251 | ||
|
2a701e919c | ||
|
aeaebf4290 | ||
|
54115430f5 | ||
|
524a5c0777 | ||
|
c091b05f6c | ||
|
8ffbb328f3 | ||
|
d6f4cb289e | ||
|
6252bc218e | ||
|
ad8822c1e7 | ||
|
83fbcbb255 | ||
|
02fb2dc662 | ||
|
dd7f6d613b | ||
|
27e3efca97 | ||
|
8bd1a9b77d | ||
|
bb8c4bce82 | ||
|
6902ecaa7c | ||
|
dc08b91314 | ||
|
822dd19393 | ||
6cd0234a5f | |||
|
19e7f57284 | ||
|
5d1401cdf6 | ||
|
91af86cca2 | ||
|
c935149c1e | ||
|
e5bda57665 | ||
|
ef294f207c | ||
|
f89d02a60d | ||
|
d090cd2420 | ||
|
99c17795c1 | ||
|
138221b93e | ||
|
8abfc21181 | ||
309c461e11 | |||
|
93fa14d832 | ||
df4fb9be91 | |||
eaac9ff53a | |||
|
0d2f5539b6 | ||
|
0f4cac29aa | ||
|
010cd36375 | ||
|
c17da72815 | ||
|
ff83c401b8 | ||
|
a007fa94a6 |
282 changed files with 8450 additions and 4834 deletions
30
.github/workflows/ci.yaml
vendored
30
.github/workflows/ci.yaml
vendored
|
@ -2,15 +2,16 @@ name: CI
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
pull_request:
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
jobs:
|
env:
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
|
||||||
|
jobs:
|
||||||
fmt:
|
fmt:
|
||||||
name: Check formatting
|
name: Check formatting
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
@ -23,11 +24,8 @@ jobs:
|
||||||
with:
|
with:
|
||||||
components: rustfmt
|
components: rustfmt
|
||||||
|
|
||||||
- name: Install Just
|
|
||||||
uses: extractions/setup-just@v2
|
|
||||||
|
|
||||||
- name: Install Tooling
|
- name: Install Tooling
|
||||||
uses: ok-nick/setup-aftman@v0.4.2
|
uses: CompeyDev/setup-rokit@v0.1.2
|
||||||
|
|
||||||
- name: Check Formatting
|
- name: Check Formatting
|
||||||
run: just fmt-check
|
run: just fmt-check
|
||||||
|
@ -40,11 +38,8 @@ jobs:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install Just
|
|
||||||
uses: extractions/setup-just@v2
|
|
||||||
|
|
||||||
- name: Install Tooling
|
- name: Install Tooling
|
||||||
uses: ok-nick/setup-aftman@v0.4.2
|
uses: CompeyDev/setup-rokit@v0.1.2
|
||||||
|
|
||||||
- name: Analyze
|
- name: Analyze
|
||||||
run: just analyze
|
run: just analyze
|
||||||
|
@ -85,23 +80,26 @@ jobs:
|
||||||
components: clippy
|
components: clippy
|
||||||
targets: ${{ matrix.cargo-target }}
|
targets: ${{ matrix.cargo-target }}
|
||||||
|
|
||||||
|
- name: Install binstall
|
||||||
|
uses: cargo-bins/cargo-binstall@main
|
||||||
|
|
||||||
|
- name: Install nextest
|
||||||
|
run: cargo binstall cargo-nextest
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
cargo build \
|
cargo build --workspace \
|
||||||
--workspace \
|
|
||||||
--locked --all-features \
|
--locked --all-features \
|
||||||
--target ${{ matrix.cargo-target }}
|
--target ${{ matrix.cargo-target }}
|
||||||
|
|
||||||
- name: Lint
|
- name: Lint
|
||||||
run: |
|
run: |
|
||||||
cargo clippy \
|
cargo clippy --workspace \
|
||||||
--workspace \
|
|
||||||
--locked --all-features \
|
--locked --all-features \
|
||||||
--target ${{ matrix.cargo-target }}
|
--target ${{ matrix.cargo-target }}
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: |
|
run: |
|
||||||
cargo test \
|
cargo nextest run --no-fail-fast \
|
||||||
--lib --workspace \
|
|
||||||
--locked --all-features \
|
--locked --all-features \
|
||||||
--target ${{ matrix.cargo-target }}
|
--target ${{ matrix.cargo-target }}
|
||||||
|
|
60
.github/workflows/release.yaml
vendored
60
.github/workflows/release.yaml
vendored
|
@ -27,23 +27,23 @@ jobs:
|
||||||
file: crates/lune/Cargo.toml
|
file: crates/lune/Cargo.toml
|
||||||
field: package.version
|
field: package.version
|
||||||
|
|
||||||
# dry-run:
|
dry-run:
|
||||||
# name: Dry-run
|
name: Dry-run
|
||||||
# needs: ["init"]
|
needs: ["init"]
|
||||||
# runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
# steps:
|
steps:
|
||||||
# - name: Checkout repository
|
- name: Checkout repository
|
||||||
# uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
# - name: Install Rust
|
- name: Install Rust
|
||||||
# uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
|
||||||
# - name: Publish (dry-run)
|
- name: Publish (dry-run)
|
||||||
# uses: katyo/publish-crates@v2
|
uses: katyo/publish-crates@v2
|
||||||
# with:
|
with:
|
||||||
# dry-run: true
|
dry-run: true
|
||||||
# check-repo: true
|
check-repo: true
|
||||||
# registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||||
|
|
||||||
build:
|
build:
|
||||||
needs: ["init"] # , "dry-run"]
|
needs: ["init"] # , "dry-run"]
|
||||||
|
@ -139,20 +139,20 @@ jobs:
|
||||||
files: ./releases/*.zip
|
files: ./releases/*.zip
|
||||||
draft: true
|
draft: true
|
||||||
|
|
||||||
# release-crates:
|
release-crates:
|
||||||
# name: Release (crates.io)
|
name: Release (crates.io)
|
||||||
# runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
# needs: ["init", "dry-run", "build"]
|
needs: ["init", "dry-run", "build"]
|
||||||
# steps:
|
steps:
|
||||||
# - name: Checkout repository
|
- name: Checkout repository
|
||||||
# uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
# - name: Install Rust
|
- name: Install Rust
|
||||||
# uses: dtolnay/rust-toolchain@stable
|
uses: dtolnay/rust-toolchain@stable
|
||||||
|
|
||||||
# - name: Publish crates
|
- name: Publish crates
|
||||||
# uses: katyo/publish-crates@v2
|
uses: katyo/publish-crates@v2
|
||||||
# with:
|
with:
|
||||||
# dry-run: false
|
dry-run: false
|
||||||
# check-repo: true
|
check-repo: true
|
||||||
# registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||||
|
|
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -21,7 +21,12 @@ lune.yml
|
||||||
luneDocs.json
|
luneDocs.json
|
||||||
luneTypes.d.luau
|
luneTypes.d.luau
|
||||||
|
|
||||||
|
# Dirs generated by runtime or build scripts
|
||||||
|
|
||||||
|
/types
|
||||||
|
|
||||||
# Files generated by runtime or build scripts
|
# Files generated by runtime or build scripts
|
||||||
|
|
||||||
scripts/brick_color.rs
|
scripts/brick_color.rs
|
||||||
scripts/font_enum_map.rs
|
scripts/font_enum_map.rs
|
||||||
scripts/physical_properties_enum_map.rs
|
scripts/physical_properties_enum_map.rs
|
||||||
|
|
|
@ -45,7 +45,7 @@ test-bin *ARGS:
|
||||||
fmt:
|
fmt:
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
stylua .lune scripts tests types \
|
stylua .lune crates scripts tests \
|
||||||
--glob "tests/**/*.luau" \
|
--glob "tests/**/*.luau" \
|
||||||
--glob "!tests/roblox/rbx-test-files/**"
|
--glob "!tests/roblox/rbx-test-files/**"
|
||||||
cargo fmt
|
cargo fmt
|
||||||
|
@ -55,7 +55,7 @@ fmt:
|
||||||
fmt-check:
|
fmt-check:
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
stylua .lune scripts tests types \
|
stylua .lune crates scripts tests \
|
||||||
--glob "tests/**/*.luau" \
|
--glob "tests/**/*.luau" \
|
||||||
--glob "!tests/roblox/rbx-test-files/**"
|
--glob "!tests/roblox/rbx-test-files/**"
|
||||||
cargo fmt --check
|
cargo fmt --check
|
||||||
|
@ -65,10 +65,11 @@ fmt-check:
|
||||||
analyze:
|
analyze:
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
lune run scripts/analyze_copy_typedefs
|
||||||
luau-lsp analyze \
|
luau-lsp analyze \
|
||||||
--settings=".vscode/settings.json" \
|
--settings=".vscode/settings.json" \
|
||||||
--ignore="tests/roblox/rbx-test-files/**" \
|
--ignore="tests/roblox/rbx-test-files/**" \
|
||||||
.lune scripts tests types
|
.lune crates scripts tests
|
||||||
|
|
||||||
# Zips up the built binary into a single zip file
|
# Zips up the built binary into a single zip file
|
||||||
[no-exit-message]
|
[no-exit-message]
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
local fs = require("@lune/fs")
|
local fs = require("@lune/fs")
|
||||||
local net = require("@lune/net")
|
local net = require("@lune/net")
|
||||||
local process = require("@lune/process")
|
local process = require("@lune/process")
|
||||||
|
local serde = require("@lune/serde")
|
||||||
local stdio = require("@lune/stdio")
|
local stdio = require("@lune/stdio")
|
||||||
local task = require("@lune/task")
|
local task = require("@lune/task")
|
||||||
|
|
||||||
|
@ -129,7 +130,7 @@ end
|
||||||
]]
|
]]
|
||||||
|
|
||||||
print("Sending 4 pings to google 🌏")
|
print("Sending 4 pings to google 🌏")
|
||||||
local result = process.spawn("ping", {
|
local result = process.exec("ping", {
|
||||||
"google.com",
|
"google.com",
|
||||||
"-c 4",
|
"-c 4",
|
||||||
})
|
})
|
||||||
|
@ -145,10 +146,8 @@ local result = process.spawn("ping", {
|
||||||
|
|
||||||
if result.ok then
|
if result.ok then
|
||||||
assert(#result.stdout > 0, "Result output was empty")
|
assert(#result.stdout > 0, "Result output was empty")
|
||||||
local min, avg, max, stddev = string.match(
|
local min, avg, max, stddev =
|
||||||
result.stdout,
|
string.match(result.stdout, "min/avg/max/stddev = ([%d%.]+)/([%d%.]+)/([%d%.]+)/([%d%.]+) ms")
|
||||||
"min/avg/max/stddev = ([%d%.]+)/([%d%.]+)/([%d%.]+)/([%d%.]+) ms"
|
|
||||||
)
|
|
||||||
print(string.format("Minimum ping time: %.3fms", assert(tonumber(min))))
|
print(string.format("Minimum ping time: %.3fms", assert(tonumber(min))))
|
||||||
print(string.format("Maximum ping time: %.3fms", assert(tonumber(max))))
|
print(string.format("Maximum ping time: %.3fms", assert(tonumber(max))))
|
||||||
print(string.format("Average ping time: %.3fms", assert(tonumber(avg))))
|
print(string.format("Average ping time: %.3fms", assert(tonumber(avg))))
|
||||||
|
@ -172,7 +171,7 @@ local apiResult = net.request({
|
||||||
headers = {
|
headers = {
|
||||||
["Content-Type"] = "application/json",
|
["Content-Type"] = "application/json",
|
||||||
} :: { [string]: string },
|
} :: { [string]: string },
|
||||||
body = net.jsonEncode({
|
body = serde.encode("json", {
|
||||||
title = "foo",
|
title = "foo",
|
||||||
body = "bar",
|
body = "bar",
|
||||||
}),
|
}),
|
||||||
|
@ -192,7 +191,7 @@ type ApiResponse = {
|
||||||
userId: number,
|
userId: number,
|
||||||
}
|
}
|
||||||
|
|
||||||
local apiResponse: ApiResponse = net.jsonDecode(apiResult.body)
|
local apiResponse: ApiResponse = serde.decode("json", apiResult.body)
|
||||||
assert(apiResponse.title == "foo", "Invalid json response")
|
assert(apiResponse.title == "foo", "Invalid json response")
|
||||||
assert(apiResponse.body == "bar", "Invalid json response")
|
assert(apiResponse.body == "bar", "Invalid json response")
|
||||||
print("Got valid JSON response with changes applied")
|
print("Got valid JSON response with changes applied")
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
local net = require("@lune/net")
|
local net = require("@lune/net")
|
||||||
local process = require("@lune/process")
|
local process = require("@lune/process")
|
||||||
local task = require("@lune/task")
|
|
||||||
|
|
||||||
local PORT = if process.env.PORT ~= nil and #process.env.PORT > 0
|
local PORT = if process.env.PORT ~= nil and #process.env.PORT > 0
|
||||||
then assert(tonumber(process.env.PORT), "Failed to parse port from env")
|
then assert(tonumber(process.env.PORT), "Failed to parse port from env")
|
||||||
|
@ -11,6 +10,10 @@ local PORT = if process.env.PORT ~= nil and #process.env.PORT > 0
|
||||||
|
|
||||||
-- Create our responder functions
|
-- Create our responder functions
|
||||||
|
|
||||||
|
local function root(_request: net.ServeRequest): string
|
||||||
|
return `Hello from Lune server!`
|
||||||
|
end
|
||||||
|
|
||||||
local function pong(request: net.ServeRequest): string
|
local function pong(request: net.ServeRequest): string
|
||||||
return `Pong!\n{request.path}\n{request.body}`
|
return `Pong!\n{request.path}\n{request.body}`
|
||||||
end
|
end
|
||||||
|
@ -29,10 +32,12 @@ local function notFound(_request: net.ServeRequest): net.ServeResponse
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Run the server on port 8080
|
-- Run the server on the port forever
|
||||||
|
|
||||||
local handle = net.serve(PORT, function(request)
|
net.serve(PORT, function(request)
|
||||||
if string.sub(request.path, 1, 5) == "/ping" then
|
if request.path == "/" then
|
||||||
|
return root(request)
|
||||||
|
elseif string.sub(request.path, 1, 5) == "/ping" then
|
||||||
return pong(request)
|
return pong(request)
|
||||||
elseif string.sub(request.path, 1, 7) == "/teapot" then
|
elseif string.sub(request.path, 1, 7) == "/teapot" then
|
||||||
return teapot(request)
|
return teapot(request)
|
||||||
|
@ -42,12 +47,4 @@ local handle = net.serve(PORT, function(request)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
print(`Listening on port {PORT} 🚀`)
|
print(`Listening on port {PORT} 🚀`)
|
||||||
|
print("Press Ctrl+C to stop")
|
||||||
-- Exit our example after a small delay, if you copy this
|
|
||||||
-- example just remove this part to keep the server running
|
|
||||||
|
|
||||||
task.delay(2, function()
|
|
||||||
print("Shutting down...")
|
|
||||||
task.wait(1)
|
|
||||||
handle.stop()
|
|
||||||
end)
|
|
||||||
|
|
|
@ -28,8 +28,8 @@ end)
|
||||||
|
|
||||||
for _ = 1, 5 do
|
for _ = 1, 5 do
|
||||||
local start = os.clock()
|
local start = os.clock()
|
||||||
socket.send(tostring(1))
|
socket:send(tostring(1))
|
||||||
local response = socket.next()
|
local response = socket:next()
|
||||||
local elapsed = os.clock() - start
|
local elapsed = os.clock() - start
|
||||||
print(`Got response '{response}' in {elapsed * 1_000} milliseconds`)
|
print(`Got response '{response}' in {elapsed * 1_000} milliseconds`)
|
||||||
task.wait(1 - elapsed)
|
task.wait(1 - elapsed)
|
||||||
|
@ -38,7 +38,7 @@ end
|
||||||
-- Everything went well, and we are done with the socket, so we can close it
|
-- Everything went well, and we are done with the socket, so we can close it
|
||||||
|
|
||||||
print("Closing web socket...")
|
print("Closing web socket...")
|
||||||
socket.close()
|
socket:close()
|
||||||
|
|
||||||
task.cancel(forceExit)
|
task.cancel(forceExit)
|
||||||
print("Done! 🌙")
|
print("Done! 🌙")
|
||||||
|
|
|
@ -15,9 +15,9 @@ local handle = net.serve(PORT, {
|
||||||
handleWebSocket = function(socket)
|
handleWebSocket = function(socket)
|
||||||
print("Got new web socket connection!")
|
print("Got new web socket connection!")
|
||||||
repeat
|
repeat
|
||||||
local message = socket.next()
|
local message = socket:next()
|
||||||
if message ~= nil then
|
if message ~= nil then
|
||||||
socket.send("Echo - " .. message)
|
socket:send("Echo - " .. message)
|
||||||
end
|
end
|
||||||
until message == nil
|
until message == nil
|
||||||
print("Web socket disconnected.")
|
print("Web socket disconnected.")
|
||||||
|
|
149
CHANGELOG.md
149
CHANGELOG.md
|
@ -8,6 +8,155 @@ All notable changes to this project will be documented in this file.
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added support for non-UTF8 strings in arguments to `process.exec` and `process.spawn`
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Improved cross-platform compatibility and correctness for values in `process.args` and `process.env`, especially on Windows
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed various crashes during require that had the error `cannot mutably borrow app data container`
|
||||||
|
|
||||||
|
## `0.9.2` - April 30th, 2025
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Improved performance of `net.request` and `net.serve` when handling large request bodies
|
||||||
|
- Improved performance and memory usage of `task.spawn`, `task.defer`, and `task.delay`
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed accidental breakage of `net.request` in version `0.9.1`
|
||||||
|
|
||||||
|
## `0.9.1` - April 29th, 2025
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added support for automatic decompression of HTTP requests in `net.serve` ([#310])
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed `net.serve` no longer serving requests if the returned `ServeHandle` is discarded ([#310])
|
||||||
|
- Fixed `net.serve` having various performance issues ([#310])
|
||||||
|
- Fixed Lune still running after cancelling a task such as `task.delay(5, ...)` and all tasks having completed
|
||||||
|
|
||||||
|
[#310]: https://github.com/lune-org/lune/pull/310
|
||||||
|
|
||||||
|
## `0.9.0` - April 25th, 2025
|
||||||
|
|
||||||
|
The next major version of Lune has finally been released!
|
||||||
|
|
||||||
|
This release has been a long time coming, and many breaking changes have been made.
|
||||||
|
If you are an existing Lune user upgrading to this version, you will **most likely** be affected.
|
||||||
|
The full list of breaking changes can be found on below.
|
||||||
|
|
||||||
|
### Breaking changes & additions
|
||||||
|
|
||||||
|
- The behavior of `require` has changed, according to the latest Luau RFCs and specifications.
|
||||||
|
|
||||||
|
For the full details, feel free to read documentation [here](https://github.com/luau-lang/rfcs), otherwise, the most notable changes here are:
|
||||||
|
|
||||||
|
- Paths passed to require must start with either `./`, `../` or `@` - require statements such as `require("foo")` **will now error** and must be changed to `require("./foo")`.
|
||||||
|
- The behavior of require from within `init.luau` and `init.lua` files has changed - previously `require("./foo")` would resolve
|
||||||
|
to the file or directory `foo` _as a **sibling** of the init file_, but will now resolve to the file or directory `foo` _which is a sibling of the **parent directory** of the init file_.
|
||||||
|
To require files inside of the same directory as the init file, the new `@self` alias must be used - like `require("@self/foo")`.
|
||||||
|
|
||||||
|
- The main `lune run` subcommand will no longer sink flags passed to it - `lune run --` will now *literally* pass the string `--` as the first
|
||||||
|
value in `process.args`, and `--` is no longer necessary to be able to pass flag arguments such as `--foo` and `-b` properly to your Lune programs.
|
||||||
|
|
||||||
|
- Two new process spawning functions - `process.create` and `process.exec` - replace the previous `process.spawn` API. ([#211])
|
||||||
|
|
||||||
|
To migrate from `process.spawn`, use the new `process.exec` API which retains the same behavior as the old function, with slight changes in how the `stdin` option is passed.
|
||||||
|
|
||||||
|
The new `process.create` function is a non-blocking process creation API and can be used to interactively
|
||||||
|
read and write to standard input and output streams of the child process.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local child = process.create("program", {
|
||||||
|
"first-argument",
|
||||||
|
"second-argument"
|
||||||
|
})
|
||||||
|
|
||||||
|
-- Writing to stdin
|
||||||
|
child.stdin:write("Hello from Lune!")
|
||||||
|
|
||||||
|
-- Reading partial data from stdout
|
||||||
|
local data = child.stdout:read()
|
||||||
|
print(data)
|
||||||
|
|
||||||
|
-- Reading the full stdout
|
||||||
|
local full = child.stdout:readToEnd()
|
||||||
|
print(full)
|
||||||
|
```
|
||||||
|
|
||||||
|
- Removed `net.jsonEncode` and `net.jsonDecode` - please use the equivalent `serde.encode("json", ...)` and `serde.decode("json", ...)` instead
|
||||||
|
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
- Various changes have been made to the Lune Rust crates:
|
||||||
|
|
||||||
|
- `Runtime::run` now returns a more useful value instead of an `ExitCode` ([#178])
|
||||||
|
- All Lune standard library crates now export a `typedefs` function that returns the source code for the respective standard library module type definitions
|
||||||
|
- All Lune crates now depend on `mlua` version `0.10` or above
|
||||||
|
- Most Lune crates have been migrated to the `smol` and `async-*` ecosystem instead of `tokio`, with a full migration expected soon (this will not break public types)
|
||||||
|
- The `roblox` crate re-export has been removed from the main `lune` crate - please depend on `lune-roblox` crate directly instead
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added functions for getting Roblox Studio locations to the `roblox` standard library ([#284])
|
||||||
|
- Added support for the `Content` datatype in the `roblox` standard library ([#305])
|
||||||
|
- Added support for `EnumItem` instance attributes in the `roblox` standard library ([#306])
|
||||||
|
- Added support for RFC 2822 dates in the `datetime` standard library using `fromRfc2822` ([#285]) - the `fromIsoDate`
|
||||||
|
function has also been deprecated (not removed yet) and `fromRfc3339` should instead be preferred for any new work.
|
||||||
|
- Added a `readLine` function to the `stdio` standard library for reading line-by-line from stdin.
|
||||||
|
- Added a way to disable JIT by setting the `LUNE_LUAU_JIT` environment variable to `false` before running Lune.
|
||||||
|
- Added `process.endianness` constant ([#267])
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Documentation comments for several standard library properties have been improved ([#248], [#250])
|
||||||
|
- Error messages no longer contain redundant or duplicate stack trace information
|
||||||
|
- Updated to Luau version `0.663`
|
||||||
|
- Updated to rbx-dom database version `0.670`
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed deadlock in `stdio.format` calls in `__tostring` metamethods ([#288])
|
||||||
|
- Fixed `task.wait` and `task.delay` not being guaranteed to yield when duration is set to zero or very small values
|
||||||
|
- Fixed `__tostring` metamethods sometimes not being respected in `print` and `stdio.format` calls
|
||||||
|
|
||||||
|
[#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
|
||||||
|
[#265]: https://github.com/lune-org/lune/pull/265
|
||||||
|
[#267]: https://github.com/lune-org/lune/pull/267
|
||||||
|
[#284]: https://github.com/lune-org/lune/pull/284
|
||||||
|
[#285]: https://github.com/lune-org/lune/pull/285
|
||||||
|
[#288]: https://github.com/lune-org/lune/pull/288
|
||||||
|
[#305]: https://github.com/lune-org/lune/pull/305
|
||||||
|
[#306]: https://github.com/lune-org/lune/pull/306
|
||||||
|
|
||||||
|
## `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
|
## `0.8.7` - August 10th, 2024
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
2560
Cargo.lock
generated
2560
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -1,3 +0,0 @@
|
||||||
[tools]
|
|
||||||
luau-lsp = "JohnnyMorganz/luau-lsp@1.32.1"
|
|
||||||
stylua = "JohnnyMorganz/StyLua@0.20.0"
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "lune-roblox"
|
name = "lune-roblox"
|
||||||
version = "0.1.3"
|
version = "0.2.2"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
repository = "https://github.com/lune-org/lune"
|
repository = "https://github.com/lune-org/lune"
|
||||||
|
@ -13,17 +13,16 @@ path = "src/lib.rs"
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
mlua = { version = "0.9.9", features = ["luau"] }
|
mlua = { version = "0.10.3", features = ["luau"] }
|
||||||
|
|
||||||
glam = "0.27"
|
glam = "0.30"
|
||||||
rand = "0.8"
|
rand = "0.9"
|
||||||
thiserror = "1.0"
|
thiserror = "2.0"
|
||||||
once_cell = "1.17"
|
|
||||||
|
|
||||||
rbx_binary = "0.7.3"
|
rbx_binary = "1.0"
|
||||||
rbx_dom_weak = "2.6.0"
|
rbx_dom_weak = "3.0"
|
||||||
rbx_reflection = "4.4.0"
|
rbx_reflection = "5.0"
|
||||||
rbx_reflection_database = "0.2.9"
|
rbx_reflection_database = "1.0"
|
||||||
rbx_xml = "0.13.2"
|
rbx_xml = "1.0"
|
||||||
|
|
||||||
lune-utils = { version = "0.1.3", path = "../lune-utils" }
|
lune-utils = { version = "0.2.2", path = "../lune-utils" }
|
||||||
|
|
|
@ -47,6 +47,7 @@ pub fn ensure_valid_attribute_value(value: &DomValue) -> LuaResult<()> {
|
||||||
| DomType::CFrame
|
| DomType::CFrame
|
||||||
| DomType::Color3
|
| DomType::Color3
|
||||||
| DomType::ColorSequence
|
| DomType::ColorSequence
|
||||||
|
| DomType::EnumItem
|
||||||
| DomType::Float32
|
| DomType::Float32
|
||||||
| DomType::Float64
|
| DomType::Float64
|
||||||
| DomType::Font
|
| DomType::Font
|
||||||
|
|
|
@ -6,7 +6,7 @@ use crate::{datatypes::extension::DomValueExt, instance::Instance};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
pub(crate) trait LuaToDomValue<'lua> {
|
pub(crate) trait LuaToDomValue {
|
||||||
/**
|
/**
|
||||||
Converts a lua value into a weak dom value.
|
Converts a lua value into a weak dom value.
|
||||||
|
|
||||||
|
@ -15,16 +15,16 @@ pub(crate) trait LuaToDomValue<'lua> {
|
||||||
*/
|
*/
|
||||||
fn lua_to_dom_value(
|
fn lua_to_dom_value(
|
||||||
&self,
|
&self,
|
||||||
lua: &'lua Lua,
|
lua: &Lua,
|
||||||
variant_type: Option<DomType>,
|
variant_type: Option<DomType>,
|
||||||
) -> DomConversionResult<DomValue>;
|
) -> DomConversionResult<DomValue>;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) trait DomValueToLua<'lua>: Sized {
|
pub(crate) trait DomValueToLua: Sized {
|
||||||
/**
|
/**
|
||||||
Converts a weak dom value into a lua value.
|
Converts a weak dom value into a lua value.
|
||||||
*/
|
*/
|
||||||
fn dom_value_to_lua(lua: &'lua Lua, variant: &DomValue) -> DomConversionResult<Self>;
|
fn dom_value_to_lua(lua: &Lua, variant: &DomValue) -> DomConversionResult<Self>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -37,8 +37,8 @@ pub(crate) trait DomValueToLua<'lua>: Sized {
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
impl<'lua> DomValueToLua<'lua> for LuaValue<'lua> {
|
impl DomValueToLua for LuaValue {
|
||||||
fn dom_value_to_lua(lua: &'lua Lua, variant: &DomValue) -> DomConversionResult<Self> {
|
fn dom_value_to_lua(lua: &Lua, variant: &DomValue) -> DomConversionResult<Self> {
|
||||||
use rbx_dom_weak::types as dom;
|
use rbx_dom_weak::types as dom;
|
||||||
|
|
||||||
match LuaAnyUserData::dom_value_to_lua(lua, variant) {
|
match LuaAnyUserData::dom_value_to_lua(lua, variant) {
|
||||||
|
@ -51,7 +51,7 @@ impl<'lua> DomValueToLua<'lua> for LuaValue<'lua> {
|
||||||
DomValue::Float32(n) => Ok(LuaValue::Number(*n as f64)),
|
DomValue::Float32(n) => Ok(LuaValue::Number(*n as f64)),
|
||||||
DomValue::String(s) => Ok(LuaValue::String(lua.create_string(s)?)),
|
DomValue::String(s) => Ok(LuaValue::String(lua.create_string(s)?)),
|
||||||
DomValue::BinaryString(s) => Ok(LuaValue::String(lua.create_string(s)?)),
|
DomValue::BinaryString(s) => Ok(LuaValue::String(lua.create_string(s)?)),
|
||||||
DomValue::Content(s) => Ok(LuaValue::String(
|
DomValue::ContentId(s) => Ok(LuaValue::String(
|
||||||
lua.create_string(AsRef::<str>::as_ref(s))?,
|
lua.create_string(AsRef::<str>::as_ref(s))?,
|
||||||
)),
|
)),
|
||||||
|
|
||||||
|
@ -76,10 +76,10 @@ impl<'lua> DomValueToLua<'lua> for LuaValue<'lua> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'lua> LuaToDomValue<'lua> for LuaValue<'lua> {
|
impl LuaToDomValue for LuaValue {
|
||||||
fn lua_to_dom_value(
|
fn lua_to_dom_value(
|
||||||
&self,
|
&self,
|
||||||
lua: &'lua Lua,
|
lua: &Lua,
|
||||||
variant_type: Option<DomType>,
|
variant_type: Option<DomType>,
|
||||||
) -> DomConversionResult<DomValue> {
|
) -> DomConversionResult<DomValue> {
|
||||||
use rbx_dom_weak::types as dom;
|
use rbx_dom_weak::types as dom;
|
||||||
|
@ -102,10 +102,10 @@ impl<'lua> LuaToDomValue<'lua> for LuaValue<'lua> {
|
||||||
Ok(DomValue::String(s.to_str()?.to_string()))
|
Ok(DomValue::String(s.to_str()?.to_string()))
|
||||||
}
|
}
|
||||||
(LuaValue::String(s), DomType::BinaryString) => {
|
(LuaValue::String(s), DomType::BinaryString) => {
|
||||||
Ok(DomValue::BinaryString(s.as_ref().into()))
|
Ok(DomValue::BinaryString(s.as_bytes().to_vec().into()))
|
||||||
}
|
}
|
||||||
(LuaValue::String(s), DomType::Content) => {
|
(LuaValue::String(s), DomType::ContentId) => {
|
||||||
Ok(DomValue::Content(s.to_str()?.to_string().into()))
|
Ok(DomValue::ContentId(s.to_str()?.to_string().into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: Some values are either optional or default and we
|
// NOTE: Some values are either optional or default and we
|
||||||
|
@ -186,9 +186,9 @@ macro_rules! userdata_to_dom {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'lua> DomValueToLua<'lua> for LuaAnyUserData<'lua> {
|
impl DomValueToLua for LuaAnyUserData {
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
fn dom_value_to_lua(lua: &'lua Lua, variant: &DomValue) -> DomConversionResult<Self> {
|
fn dom_value_to_lua(lua: &Lua, variant: &DomValue) -> DomConversionResult<Self> {
|
||||||
use super::types::*;
|
use super::types::*;
|
||||||
|
|
||||||
use rbx_dom_weak::types as dom;
|
use rbx_dom_weak::types as dom;
|
||||||
|
@ -200,6 +200,8 @@ impl<'lua> DomValueToLua<'lua> for LuaAnyUserData<'lua> {
|
||||||
DomValue::Color3(value) => dom_to_userdata!(lua, value => Color3),
|
DomValue::Color3(value) => dom_to_userdata!(lua, value => Color3),
|
||||||
DomValue::Color3uint8(value) => dom_to_userdata!(lua, value => Color3),
|
DomValue::Color3uint8(value) => dom_to_userdata!(lua, value => Color3),
|
||||||
DomValue::ColorSequence(value) => dom_to_userdata!(lua, value => ColorSequence),
|
DomValue::ColorSequence(value) => dom_to_userdata!(lua, value => ColorSequence),
|
||||||
|
DomValue::Content(value) => dom_to_userdata!(lua, value => Content),
|
||||||
|
DomValue::EnumItem(value) => dom_to_userdata!(lua, value => EnumItem),
|
||||||
DomValue::Faces(value) => dom_to_userdata!(lua, value => Faces),
|
DomValue::Faces(value) => dom_to_userdata!(lua, value => Faces),
|
||||||
DomValue::Font(value) => dom_to_userdata!(lua, value => Font),
|
DomValue::Font(value) => dom_to_userdata!(lua, value => Font),
|
||||||
DomValue::NumberRange(value) => dom_to_userdata!(lua, value => NumberRange),
|
DomValue::NumberRange(value) => dom_to_userdata!(lua, value => NumberRange),
|
||||||
|
@ -233,13 +235,9 @@ impl<'lua> DomValueToLua<'lua> for LuaAnyUserData<'lua> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'lua> LuaToDomValue<'lua> for LuaAnyUserData<'lua> {
|
impl LuaToDomValue for LuaAnyUserData {
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
fn lua_to_dom_value(
|
fn lua_to_dom_value(&self, _: &Lua, variant_type: Option<DomType>) -> DomConversionResult<DomValue> {
|
||||||
&self,
|
|
||||||
_: &'lua Lua,
|
|
||||||
variant_type: Option<DomType>,
|
|
||||||
) -> DomConversionResult<DomValue> {
|
|
||||||
use super::types::*;
|
use super::types::*;
|
||||||
|
|
||||||
use rbx_dom_weak::types as dom;
|
use rbx_dom_weak::types as dom;
|
||||||
|
@ -256,7 +254,8 @@ impl<'lua> LuaToDomValue<'lua> for LuaAnyUserData<'lua> {
|
||||||
DomType::Color3 => userdata_to_dom!(self as Color3 => dom::Color3),
|
DomType::Color3 => userdata_to_dom!(self as Color3 => dom::Color3),
|
||||||
DomType::Color3uint8 => userdata_to_dom!(self as Color3 => dom::Color3uint8),
|
DomType::Color3uint8 => userdata_to_dom!(self as Color3 => dom::Color3uint8),
|
||||||
DomType::ColorSequence => userdata_to_dom!(self as ColorSequence => dom::ColorSequence),
|
DomType::ColorSequence => userdata_to_dom!(self as ColorSequence => dom::ColorSequence),
|
||||||
DomType::Enum => userdata_to_dom!(self as EnumItem => dom::Enum),
|
DomType::Content => userdata_to_dom!(self as Content => dom::Content),
|
||||||
|
DomType::EnumItem => userdata_to_dom!(self as EnumItem => dom::EnumItem),
|
||||||
DomType::Faces => userdata_to_dom!(self as Faces => dom::Faces),
|
DomType::Faces => userdata_to_dom!(self as Faces => dom::Faces),
|
||||||
DomType::Font => userdata_to_dom!(self as Font => dom::Font),
|
DomType::Font => userdata_to_dom!(self as Font => dom::Font),
|
||||||
DomType::NumberRange => userdata_to_dom!(self as NumberRange => dom::NumberRange),
|
DomType::NumberRange => userdata_to_dom!(self as NumberRange => dom::NumberRange),
|
||||||
|
@ -276,13 +275,13 @@ impl<'lua> LuaToDomValue<'lua> for LuaAnyUserData<'lua> {
|
||||||
// NOTE: The none and default variants of these types are handled in
|
// NOTE: The none and default variants of these types are handled in
|
||||||
// LuaToDomValue for the LuaValue type instead, allowing for nil/default
|
// LuaToDomValue for the LuaValue type instead, allowing for nil/default
|
||||||
DomType::OptionalCFrame => {
|
DomType::OptionalCFrame => {
|
||||||
return match self.borrow::<CFrame>() {
|
match self.borrow::<CFrame>() {
|
||||||
Err(_) => unreachable!("Invalid use of conversion method, should be using LuaValue"),
|
Err(_) => unreachable!("Invalid use of conversion method, should be using LuaValue"),
|
||||||
Ok(value) => Ok(DomValue::OptionalCFrame(Some(dom::CFrame::from(*value)))),
|
Ok(value) => Ok(DomValue::OptionalCFrame(Some(dom::CFrame::from(*value)))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
DomType::PhysicalProperties => {
|
DomType::PhysicalProperties => {
|
||||||
return match self.borrow::<PhysicalProperties>() {
|
match self.borrow::<PhysicalProperties>() {
|
||||||
Err(_) => unreachable!("Invalid use of conversion method, should be using LuaValue"),
|
Err(_) => unreachable!("Invalid use of conversion method, should be using LuaValue"),
|
||||||
Ok(value) => {
|
Ok(value) => {
|
||||||
let props = dom::CustomPhysicalProperties::from(*value);
|
let props = dom::CustomPhysicalProperties::from(*value);
|
||||||
|
@ -314,7 +313,7 @@ impl<'lua> LuaToDomValue<'lua> for LuaAnyUserData<'lua> {
|
||||||
value if value.is::<CFrame>() => userdata_to_dom!(value as CFrame => dom::CFrame),
|
value if value.is::<CFrame>() => userdata_to_dom!(value as CFrame => dom::CFrame),
|
||||||
value if value.is::<Color3>() => userdata_to_dom!(value as Color3 => dom::Color3),
|
value if value.is::<Color3>() => userdata_to_dom!(value as Color3 => dom::Color3),
|
||||||
value if value.is::<ColorSequence>() => userdata_to_dom!(value as ColorSequence => dom::ColorSequence),
|
value if value.is::<ColorSequence>() => userdata_to_dom!(value as ColorSequence => dom::ColorSequence),
|
||||||
value if value.is::<Enum>() => userdata_to_dom!(value as EnumItem => dom::Enum),
|
value if value.is::<EnumItem>() => userdata_to_dom!(value as EnumItem => dom::EnumItem),
|
||||||
value if value.is::<Faces>() => userdata_to_dom!(value as Faces => dom::Faces),
|
value if value.is::<Faces>() => userdata_to_dom!(value as Faces => dom::Faces),
|
||||||
value if value.is::<Font>() => userdata_to_dom!(value as Font => dom::Font),
|
value if value.is::<Font>() => userdata_to_dom!(value as Font => dom::Font),
|
||||||
value if value.is::<Instance>() => userdata_to_dom!(value as Instance => dom::Ref),
|
value if value.is::<Instance>() => userdata_to_dom!(value as Instance => dom::Ref),
|
||||||
|
|
|
@ -19,7 +19,9 @@ impl DomValueExt for DomType {
|
||||||
Color3uint8 => "Color3uint8",
|
Color3uint8 => "Color3uint8",
|
||||||
ColorSequence => "ColorSequence",
|
ColorSequence => "ColorSequence",
|
||||||
Content => "Content",
|
Content => "Content",
|
||||||
|
ContentId => "ContentId",
|
||||||
Enum => "Enum",
|
Enum => "Enum",
|
||||||
|
EnumItem => "EnumItem",
|
||||||
Faces => "Faces",
|
Faces => "Faces",
|
||||||
Float32 => "Float32",
|
Float32 => "Float32",
|
||||||
Float64 => "Float64",
|
Float64 => "Float64",
|
||||||
|
|
|
@ -21,11 +21,11 @@ pub struct Axes {
|
||||||
pub(crate) z: bool,
|
pub(crate) z: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaExportsTable<'_> for Axes {
|
impl LuaExportsTable for Axes {
|
||||||
const EXPORT_NAME: &'static str = "Axes";
|
const EXPORT_NAME: &'static str = "Axes";
|
||||||
|
|
||||||
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
|
fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {
|
||||||
let axes_new = |_, args: LuaMultiValue| {
|
let axes_new = |_: &Lua, args: LuaMultiValue| {
|
||||||
let mut x = false;
|
let mut x = false;
|
||||||
let mut y = false;
|
let mut y = false;
|
||||||
let mut z = false;
|
let mut z = false;
|
||||||
|
@ -76,7 +76,7 @@ impl LuaExportsTable<'_> for Axes {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaUserData for Axes {
|
impl LuaUserData for Axes {
|
||||||
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
||||||
fields.add_field_method_get("X", |_, this| Ok(this.x));
|
fields.add_field_method_get("X", |_, this| Ok(this.x));
|
||||||
fields.add_field_method_get("Y", |_, this| Ok(this.y));
|
fields.add_field_method_get("Y", |_, this| Ok(this.y));
|
||||||
fields.add_field_method_get("Z", |_, this| Ok(this.z));
|
fields.add_field_method_get("Z", |_, this| Ok(this.z));
|
||||||
|
@ -88,7 +88,7 @@ impl LuaUserData for Axes {
|
||||||
fields.add_field_method_get("Back", |_, this| Ok(this.z));
|
fields.add_field_method_get("Back", |_, this| Ok(this.z));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||||
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
||||||
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
|
|
||||||
use mlua::prelude::*;
|
use mlua::prelude::*;
|
||||||
use rand::seq::SliceRandom;
|
use rand::prelude::*;
|
||||||
use rbx_dom_weak::types::BrickColor as DomBrickColor;
|
use rbx_dom_weak::types::BrickColor as DomBrickColor;
|
||||||
|
|
||||||
use lune_utils::TableBuilder;
|
use lune_utils::TableBuilder;
|
||||||
|
@ -24,16 +24,16 @@ pub struct BrickColor {
|
||||||
pub(crate) rgb: (u8, u8, u8),
|
pub(crate) rgb: (u8, u8, u8),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaExportsTable<'_> for BrickColor {
|
impl LuaExportsTable for BrickColor {
|
||||||
const EXPORT_NAME: &'static str = "BrickColor";
|
const EXPORT_NAME: &'static str = "BrickColor";
|
||||||
|
|
||||||
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
|
fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {
|
||||||
type ArgsNumber = u16;
|
type ArgsNumber = u16;
|
||||||
type ArgsName = String;
|
type ArgsName = String;
|
||||||
type ArgsRgb = (u8, u8, u8);
|
type ArgsRgb = (u8, u8, u8);
|
||||||
type ArgsColor3<'lua> = LuaUserDataRef<'lua, Color3>;
|
type ArgsColor3 = LuaUserDataRef<Color3>;
|
||||||
|
|
||||||
let brick_color_new = |lua, args: LuaMultiValue| {
|
let brick_color_new = |lua: &Lua, args: LuaMultiValue| {
|
||||||
if let Ok(number) = ArgsNumber::from_lua_multi(args.clone(), lua) {
|
if let Ok(number) = ArgsNumber::from_lua_multi(args.clone(), lua) {
|
||||||
Ok(color_from_number(number))
|
Ok(color_from_number(number))
|
||||||
} else if let Ok(name) = ArgsName::from_lua_multi(args.clone(), lua) {
|
} else if let Ok(name) = ArgsName::from_lua_multi(args.clone(), lua) {
|
||||||
|
@ -50,7 +50,7 @@ impl LuaExportsTable<'_> for BrickColor {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let brick_color_palette = |_, index: u16| {
|
let brick_color_palette = |_: &Lua, index: u16| {
|
||||||
if index == 0 {
|
if index == 0 {
|
||||||
Err(LuaError::RuntimeError("Invalid index".to_string()))
|
Err(LuaError::RuntimeError("Invalid index".to_string()))
|
||||||
} else if let Some(number) = BRICK_COLOR_PALETTE.get((index - 1) as usize) {
|
} else if let Some(number) = BRICK_COLOR_PALETTE.get((index - 1) as usize) {
|
||||||
|
@ -60,8 +60,8 @@ impl LuaExportsTable<'_> for BrickColor {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let brick_color_random = |_, ()| {
|
let brick_color_random = |_: &Lua, ()| {
|
||||||
let number = BRICK_COLOR_PALETTE.choose(&mut rand::thread_rng());
|
let number = BRICK_COLOR_PALETTE.choose(&mut rand::rng());
|
||||||
Ok(color_from_number(*number.unwrap()))
|
Ok(color_from_number(*number.unwrap()))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -71,7 +71,7 @@ impl LuaExportsTable<'_> for BrickColor {
|
||||||
.with_function("random", brick_color_random)?;
|
.with_function("random", brick_color_random)?;
|
||||||
|
|
||||||
for (name, number) in BRICK_COLOR_CONSTRUCTORS {
|
for (name, number) in BRICK_COLOR_CONSTRUCTORS {
|
||||||
let f = |_, ()| Ok(color_from_number(*number));
|
let f = |_: &Lua, ()| Ok(color_from_number(*number));
|
||||||
builder = builder.with_function(*name, f)?;
|
builder = builder.with_function(*name, f)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,7 +80,7 @@ impl LuaExportsTable<'_> for BrickColor {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaUserData for BrickColor {
|
impl LuaUserData for BrickColor {
|
||||||
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
||||||
fields.add_field_method_get("Number", |_, this| Ok(this.number));
|
fields.add_field_method_get("Number", |_, this| Ok(this.number));
|
||||||
fields.add_field_method_get("Name", |_, this| Ok(this.name));
|
fields.add_field_method_get("Name", |_, this| Ok(this.name));
|
||||||
fields.add_field_method_get("R", |_, this| Ok(this.rgb.0 as f32 / 255f32));
|
fields.add_field_method_get("R", |_, this| Ok(this.rgb.0 as f32 / 255f32));
|
||||||
|
@ -92,7 +92,7 @@ impl LuaUserData for BrickColor {
|
||||||
fields.add_field_method_get("Color", |_, this| Ok(Color3::from(*this)));
|
fields.add_field_method_get("Color", |_, this| Ok(Color3::from(*this)));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||||
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
||||||
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,27 +43,28 @@ impl CFrame {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaExportsTable<'_> for CFrame {
|
impl LuaExportsTable for CFrame {
|
||||||
const EXPORT_NAME: &'static str = "CFrame";
|
const EXPORT_NAME: &'static str = "CFrame";
|
||||||
|
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
|
fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {
|
||||||
let cframe_angles = |_, (rx, ry, rz): (f32, f32, f32)| {
|
let cframe_angles = |_: &Lua, (rx, ry, rz): (f32, f32, f32)| {
|
||||||
Ok(CFrame(Mat4::from_euler(EulerRot::XYZ, rx, ry, rz)))
|
Ok(CFrame(Mat4::from_euler(EulerRot::XYZ, rx, ry, rz)))
|
||||||
};
|
};
|
||||||
|
|
||||||
let cframe_from_axis_angle =
|
let cframe_from_axis_angle = |_: &Lua, (v, r): (LuaUserDataRef<Vector3>, f32)| {
|
||||||
|_, (v, r): (LuaUserDataRef<Vector3>, f32)| Ok(CFrame(Mat4::from_axis_angle(v.0, r)));
|
Ok(CFrame(Mat4::from_axis_angle(v.0, r)))
|
||||||
|
};
|
||||||
|
|
||||||
let cframe_from_euler_angles_xyz = |_, (rx, ry, rz): (f32, f32, f32)| {
|
let cframe_from_euler_angles_xyz = |_: &Lua, (rx, ry, rz): (f32, f32, f32)| {
|
||||||
Ok(CFrame(Mat4::from_euler(EulerRot::XYZ, rx, ry, rz)))
|
Ok(CFrame(Mat4::from_euler(EulerRot::XYZ, rx, ry, rz)))
|
||||||
};
|
};
|
||||||
|
|
||||||
let cframe_from_euler_angles_yxz = |_, (rx, ry, rz): (f32, f32, f32)| {
|
let cframe_from_euler_angles_yxz = |_: &Lua, (rx, ry, rz): (f32, f32, f32)| {
|
||||||
Ok(CFrame(Mat4::from_euler(EulerRot::YXZ, ry, rx, rz)))
|
Ok(CFrame(Mat4::from_euler(EulerRot::YXZ, ry, rx, rz)))
|
||||||
};
|
};
|
||||||
|
|
||||||
let cframe_from_matrix = |_,
|
let cframe_from_matrix = |_: &Lua,
|
||||||
(pos, rx, ry, rz): (
|
(pos, rx, ry, rz): (
|
||||||
LuaUserDataRef<Vector3>,
|
LuaUserDataRef<Vector3>,
|
||||||
LuaUserDataRef<Vector3>,
|
LuaUserDataRef<Vector3>,
|
||||||
|
@ -79,11 +80,11 @@ impl LuaExportsTable<'_> for CFrame {
|
||||||
)))
|
)))
|
||||||
};
|
};
|
||||||
|
|
||||||
let cframe_from_orientation = |_, (rx, ry, rz): (f32, f32, f32)| {
|
let cframe_from_orientation = |_: &Lua, (rx, ry, rz): (f32, f32, f32)| {
|
||||||
Ok(CFrame(Mat4::from_euler(EulerRot::YXZ, ry, rx, rz)))
|
Ok(CFrame(Mat4::from_euler(EulerRot::YXZ, ry, rx, rz)))
|
||||||
};
|
};
|
||||||
|
|
||||||
let cframe_look_at = |_,
|
let cframe_look_at = |_: &Lua,
|
||||||
(from, to, up): (
|
(from, to, up): (
|
||||||
LuaUserDataRef<Vector3>,
|
LuaUserDataRef<Vector3>,
|
||||||
LuaUserDataRef<Vector3>,
|
LuaUserDataRef<Vector3>,
|
||||||
|
@ -97,18 +98,18 @@ impl LuaExportsTable<'_> for CFrame {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Dynamic args constructor
|
// Dynamic args constructor
|
||||||
type ArgsPos<'lua> = LuaUserDataRef<'lua, Vector3>;
|
type ArgsPos = LuaUserDataRef<Vector3>;
|
||||||
type ArgsLook<'lua> = (
|
type ArgsLook = (
|
||||||
LuaUserDataRef<'lua, Vector3>,
|
LuaUserDataRef<Vector3>,
|
||||||
LuaUserDataRef<'lua, Vector3>,
|
LuaUserDataRef<Vector3>,
|
||||||
Option<LuaUserDataRef<'lua, Vector3>>,
|
Option<LuaUserDataRef<Vector3>>,
|
||||||
);
|
);
|
||||||
|
|
||||||
type ArgsPosXYZ = (f32, f32, f32);
|
type ArgsPosXYZ = (f32, f32, f32);
|
||||||
type ArgsPosXYZQuat = (f32, f32, f32, f32, f32, f32, f32);
|
type ArgsPosXYZQuat = (f32, f32, f32, f32, f32, f32, f32);
|
||||||
type ArgsMatrix = (f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32);
|
type ArgsMatrix = (f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32, f32);
|
||||||
|
|
||||||
let cframe_new = |lua, args: LuaMultiValue| match args.len() {
|
let cframe_new = |lua: &Lua, args: LuaMultiValue| match args.len() {
|
||||||
0 => Ok(CFrame(Mat4::IDENTITY)),
|
0 => Ok(CFrame(Mat4::IDENTITY)),
|
||||||
|
|
||||||
1 => match ArgsPos::from_lua_multi(args, lua) {
|
1 => match ArgsPos::from_lua_multi(args, lua) {
|
||||||
|
@ -174,7 +175,7 @@ impl LuaExportsTable<'_> for CFrame {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaUserData for CFrame {
|
impl LuaUserData for CFrame {
|
||||||
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
||||||
fields.add_field_method_get("Position", |_, this| Ok(Vector3(this.position())));
|
fields.add_field_method_get("Position", |_, this| Ok(Vector3(this.position())));
|
||||||
fields.add_field_method_get("Rotation", |_, this| {
|
fields.add_field_method_get("Rotation", |_, this| {
|
||||||
Ok(CFrame(Mat4::from_cols(
|
Ok(CFrame(Mat4::from_cols(
|
||||||
|
@ -200,7 +201,7 @@ impl LuaUserData for CFrame {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||||
// Methods
|
// Methods
|
||||||
methods.add_method("Inverse", |_, this, ()| Ok(this.inverse()));
|
methods.add_method("Inverse", |_, this, ()| Ok(this.inverse()));
|
||||||
methods.add_method(
|
methods.add_method(
|
||||||
|
@ -323,10 +324,10 @@ impl LuaUserData for CFrame {
|
||||||
} else if let Ok(vec) = ud.borrow::<Vector3>() {
|
} else if let Ok(vec) = ud.borrow::<Vector3>() {
|
||||||
return lua.create_userdata(*this * *vec);
|
return lua.create_userdata(*this * *vec);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
Err(LuaError::FromLuaConversionError {
|
Err(LuaError::FromLuaConversionError {
|
||||||
from: rhs.type_name(),
|
from: rhs.type_name(),
|
||||||
to: "userdata",
|
to: "userdata".to_string(),
|
||||||
message: Some(format!(
|
message: Some(format!(
|
||||||
"Expected CFrame or Vector3, got {}",
|
"Expected CFrame or Vector3, got {}",
|
||||||
rhs.type_name()
|
rhs.type_name()
|
||||||
|
|
|
@ -28,11 +28,11 @@ pub struct Color3 {
|
||||||
pub(crate) b: f32,
|
pub(crate) b: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaExportsTable<'_> for Color3 {
|
impl LuaExportsTable for Color3 {
|
||||||
const EXPORT_NAME: &'static str = "Color3";
|
const EXPORT_NAME: &'static str = "Color3";
|
||||||
|
|
||||||
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
|
fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {
|
||||||
let color3_from_rgb = |_, (r, g, b): (Option<u8>, Option<u8>, Option<u8>)| {
|
let color3_from_rgb = |_: &Lua, (r, g, b): (Option<u8>, Option<u8>, Option<u8>)| {
|
||||||
Ok(Color3 {
|
Ok(Color3 {
|
||||||
r: (r.unwrap_or_default() as f32) / 255f32,
|
r: (r.unwrap_or_default() as f32) / 255f32,
|
||||||
g: (g.unwrap_or_default() as f32) / 255f32,
|
g: (g.unwrap_or_default() as f32) / 255f32,
|
||||||
|
@ -40,7 +40,7 @@ impl LuaExportsTable<'_> for Color3 {
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
let color3_from_hsv = |_, (h, s, v): (f32, f32, f32)| {
|
let color3_from_hsv = |_: &Lua, (h, s, v): (f32, f32, f32)| {
|
||||||
// https://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c
|
// https://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c
|
||||||
let i = (h * 6.0).floor();
|
let i = (h * 6.0).floor();
|
||||||
let f = h * 6.0 - i;
|
let f = h * 6.0 - i;
|
||||||
|
@ -61,7 +61,7 @@ impl LuaExportsTable<'_> for Color3 {
|
||||||
Ok(Color3 { r, g, b })
|
Ok(Color3 { r, g, b })
|
||||||
};
|
};
|
||||||
|
|
||||||
let color3_from_hex = |_, hex: String| {
|
let color3_from_hex = |_: &Lua, hex: String| {
|
||||||
let trimmed = hex.trim_start_matches('#').to_ascii_uppercase();
|
let trimmed = hex.trim_start_matches('#').to_ascii_uppercase();
|
||||||
let chars = if trimmed.len() == 3 {
|
let chars = if trimmed.len() == 3 {
|
||||||
(
|
(
|
||||||
|
@ -94,7 +94,7 @@ impl LuaExportsTable<'_> for Color3 {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let color3_new = |_, (r, g, b): (Option<f32>, Option<f32>, Option<f32>)| {
|
let color3_new = |_: &Lua, (r, g, b): (Option<f32>, Option<f32>, Option<f32>)| {
|
||||||
Ok(Color3 {
|
Ok(Color3 {
|
||||||
r: r.unwrap_or_default(),
|
r: r.unwrap_or_default(),
|
||||||
g: g.unwrap_or_default(),
|
g: g.unwrap_or_default(),
|
||||||
|
@ -111,14 +111,14 @@ impl LuaExportsTable<'_> for Color3 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'lua> FromLua<'lua> for Color3 {
|
impl FromLua for Color3 {
|
||||||
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
|
fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
|
||||||
if let LuaValue::UserData(ud) = value {
|
if let LuaValue::UserData(ud) = value {
|
||||||
Ok(*ud.borrow::<Color3>()?)
|
Ok(*ud.borrow::<Color3>()?)
|
||||||
} else {
|
} else {
|
||||||
Err(LuaError::FromLuaConversionError {
|
Err(LuaError::FromLuaConversionError {
|
||||||
from: value.type_name(),
|
from: value.type_name(),
|
||||||
to: "Color3",
|
to: "Color3".to_string(),
|
||||||
message: None,
|
message: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -126,13 +126,13 @@ impl<'lua> FromLua<'lua> for Color3 {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaUserData for Color3 {
|
impl LuaUserData for Color3 {
|
||||||
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
||||||
fields.add_field_method_get("R", |_, this| Ok(this.r));
|
fields.add_field_method_get("R", |_, this| Ok(this.r));
|
||||||
fields.add_field_method_get("G", |_, this| Ok(this.g));
|
fields.add_field_method_get("G", |_, this| Ok(this.g));
|
||||||
fields.add_field_method_get("B", |_, this| Ok(this.b));
|
fields.add_field_method_get("B", |_, this| Ok(this.b));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||||
// Methods
|
// Methods
|
||||||
methods.add_method(
|
methods.add_method(
|
||||||
"Lerp",
|
"Lerp",
|
||||||
|
|
|
@ -21,15 +21,15 @@ pub struct ColorSequence {
|
||||||
pub(crate) keypoints: Vec<ColorSequenceKeypoint>,
|
pub(crate) keypoints: Vec<ColorSequenceKeypoint>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaExportsTable<'_> for ColorSequence {
|
impl LuaExportsTable for ColorSequence {
|
||||||
const EXPORT_NAME: &'static str = "ColorSequence";
|
const EXPORT_NAME: &'static str = "ColorSequence";
|
||||||
|
|
||||||
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
|
fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {
|
||||||
type ArgsColor<'lua> = LuaUserDataRef<'lua, Color3>;
|
type ArgsColor = LuaUserDataRef<Color3>;
|
||||||
type ArgsColors<'lua> = (LuaUserDataRef<'lua, Color3>, LuaUserDataRef<'lua, Color3>);
|
type ArgsColors = (LuaUserDataRef<Color3>, LuaUserDataRef<Color3>);
|
||||||
type ArgsKeypoints<'lua> = Vec<LuaUserDataRef<'lua, ColorSequenceKeypoint>>;
|
type ArgsKeypoints = Vec<LuaUserDataRef<ColorSequenceKeypoint>>;
|
||||||
|
|
||||||
let color_sequence_new = |lua, args: LuaMultiValue| {
|
let color_sequence_new = |lua: &Lua, args: LuaMultiValue| {
|
||||||
if let Ok(color) = ArgsColor::from_lua_multi(args.clone(), lua) {
|
if let Ok(color) = ArgsColor::from_lua_multi(args.clone(), lua) {
|
||||||
Ok(ColorSequence {
|
Ok(ColorSequence {
|
||||||
keypoints: vec Roblox datatype.
|
||||||
|
|
||||||
|
This implements all documented properties, methods & constructors of the Content type as of April 2025.
|
||||||
|
*/
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct Content(ContentType);
|
||||||
|
|
||||||
|
impl LuaExportsTable for Content {
|
||||||
|
const EXPORT_NAME: &'static str = "Content";
|
||||||
|
|
||||||
|
fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {
|
||||||
|
let from_uri = |_: &Lua, uri: String| Ok(Self(ContentType::Uri(uri)));
|
||||||
|
|
||||||
|
let from_object = |_: &Lua, obj: LuaUserDataRef<Instance>| {
|
||||||
|
let database = rbx_reflection_database::get();
|
||||||
|
let instance_descriptor = database
|
||||||
|
.classes
|
||||||
|
.get("Instance")
|
||||||
|
.expect("the reflection database should always have Instance in it");
|
||||||
|
let param_descriptor = database.classes.get(obj.get_class_name()).expect(
|
||||||
|
"you should not be able to construct an Instance that is not known to Lune",
|
||||||
|
);
|
||||||
|
if database.has_superclass(param_descriptor, instance_descriptor) {
|
||||||
|
Err(LuaError::runtime("the provided object is a descendant class of 'Instance', expected one that was only an 'Object'"))
|
||||||
|
} else {
|
||||||
|
Ok(Content(ContentType::Object(obj.dom_ref)))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
TableBuilder::new(lua)?
|
||||||
|
.with_value("none", Content(ContentType::None))?
|
||||||
|
.with_function("fromUri", from_uri)?
|
||||||
|
.with_function("fromObject", from_object)?
|
||||||
|
.build_readonly()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LuaUserData for Content {
|
||||||
|
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
||||||
|
fields.add_field_method_get("SourceType", |_, this| {
|
||||||
|
let variant_name = match &this.0 {
|
||||||
|
ContentType::None => "None",
|
||||||
|
ContentType::Uri(_) => "Uri",
|
||||||
|
ContentType::Object(_) => "Object",
|
||||||
|
other => {
|
||||||
|
return Err(LuaError::runtime(format!(
|
||||||
|
"cannot get SourceType: unknown ContentType variant '{other:?}'"
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(EnumItem::from_enum_name_and_name(
|
||||||
|
"ContentSourceType",
|
||||||
|
variant_name,
|
||||||
|
))
|
||||||
|
});
|
||||||
|
fields.add_field_method_get("Uri", |_, this| {
|
||||||
|
if let ContentType::Uri(uri) = &this.0 {
|
||||||
|
Ok(Some(uri.to_owned()))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
fields.add_field_method_get("Object", |_, this| {
|
||||||
|
if let ContentType::Object(referent) = &this.0 {
|
||||||
|
Ok(Instance::new_opt(*referent))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||||
|
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
||||||
|
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Content {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
// Regardless of the actual content of the Content, Roblox just emits
|
||||||
|
// `Content` when casting it to a string. We do not do that.
|
||||||
|
write!(f, "Content(")?;
|
||||||
|
match &self.0 {
|
||||||
|
ContentType::None => write!(f, "None")?,
|
||||||
|
ContentType::Uri(uri) => write!(f, "Uri={uri}")?,
|
||||||
|
ContentType::Object(_) => write!(f, "Object")?,
|
||||||
|
other => write!(f, "UnknownType({other:?})")?,
|
||||||
|
}
|
||||||
|
write!(f, ")")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DomContent> for Content {
|
||||||
|
fn from(value: DomContent) -> Self {
|
||||||
|
Self(value.value().clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Content> for DomContent {
|
||||||
|
fn from(value: Content) -> Self {
|
||||||
|
match value.0 {
|
||||||
|
ContentType::None => Self::none(),
|
||||||
|
ContentType::Uri(uri) => Self::from_uri(uri),
|
||||||
|
ContentType::Object(referent) => Self::from_referent(referent),
|
||||||
|
other => unimplemented!("unknown variant of ContentType: {other:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -23,7 +23,7 @@ impl Enum {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaUserData for Enum {
|
impl LuaUserData for Enum {
|
||||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||||
// Methods
|
// Methods
|
||||||
methods.add_method("GetEnumItems", |_, this, ()| {
|
methods.add_method("GetEnumItems", |_, this, ()| {
|
||||||
Ok(this
|
Ok(this
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
|
|
||||||
use mlua::prelude::*;
|
use mlua::prelude::*;
|
||||||
use rbx_dom_weak::types::Enum as DomEnum;
|
use rbx_dom_weak::types::EnumItem as DomEnumItem;
|
||||||
|
|
||||||
use super::{super::*, Enum};
|
use super::{super::*, Enum};
|
||||||
|
|
||||||
|
@ -62,26 +62,26 @@ impl EnumItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaUserData for EnumItem {
|
impl LuaUserData for EnumItem {
|
||||||
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
||||||
fields.add_field_method_get("Name", |_, this| Ok(this.name.clone()));
|
fields.add_field_method_get("Name", |_, this| Ok(this.name.clone()));
|
||||||
fields.add_field_method_get("Value", |_, this| Ok(this.value));
|
fields.add_field_method_get("Value", |_, this| Ok(this.value));
|
||||||
fields.add_field_method_get("EnumType", |_, this| Ok(this.parent.clone()));
|
fields.add_field_method_get("EnumType", |_, this| Ok(this.parent.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||||
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
||||||
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'lua> FromLua<'lua> for EnumItem {
|
impl FromLua for EnumItem {
|
||||||
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
|
fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
|
||||||
if let LuaValue::UserData(ud) = value {
|
if let LuaValue::UserData(ud) = value {
|
||||||
Ok(ud.borrow::<EnumItem>()?.to_owned())
|
Ok(ud.borrow::<EnumItem>()?.to_owned())
|
||||||
} else {
|
} else {
|
||||||
Err(LuaError::FromLuaConversionError {
|
Err(LuaError::FromLuaConversionError {
|
||||||
from: value.type_name(),
|
from: value.type_name(),
|
||||||
to: "EnumItem",
|
to: "EnumItem".to_string(),
|
||||||
message: None,
|
message: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -100,8 +100,18 @@ impl PartialEq for EnumItem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<EnumItem> for DomEnum {
|
impl From<EnumItem> for DomEnumItem {
|
||||||
fn from(v: EnumItem) -> Self {
|
fn from(v: EnumItem) -> Self {
|
||||||
DomEnum::from_u32(v.value)
|
DomEnumItem {
|
||||||
|
ty: v.parent.desc.name.to_string(),
|
||||||
|
value: v.value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<DomEnumItem> for EnumItem {
|
||||||
|
fn from(value: DomEnumItem) -> Self {
|
||||||
|
EnumItem::from_enum_name_and_value(value.ty, value.value)
|
||||||
|
.expect("cannot convert rbx_type::EnumItem with unknown type into EnumItem")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ use super::{super::*, Enum};
|
||||||
pub struct Enums;
|
pub struct Enums;
|
||||||
|
|
||||||
impl LuaUserData for Enums {
|
impl LuaUserData for Enums {
|
||||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||||
// Methods
|
// Methods
|
||||||
methods.add_method("GetEnums", |_, _, ()| {
|
methods.add_method("GetEnums", |_, _, ()| {
|
||||||
let db = rbx_reflection_database::get();
|
let db = rbx_reflection_database::get();
|
||||||
|
|
|
@ -26,11 +26,11 @@ pub struct Faces {
|
||||||
pub(crate) front: bool,
|
pub(crate) front: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaExportsTable<'_> for Faces {
|
impl LuaExportsTable for Faces {
|
||||||
const EXPORT_NAME: &'static str = "Faces";
|
const EXPORT_NAME: &'static str = "Faces";
|
||||||
|
|
||||||
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
|
fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {
|
||||||
let faces_new = |_, args: LuaMultiValue| {
|
let faces_new = |_: &Lua, args: LuaMultiValue| {
|
||||||
let mut right = false;
|
let mut right = false;
|
||||||
let mut top = false;
|
let mut top = false;
|
||||||
let mut back = false;
|
let mut back = false;
|
||||||
|
@ -87,7 +87,7 @@ impl LuaExportsTable<'_> for Faces {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaUserData for Faces {
|
impl LuaUserData for Faces {
|
||||||
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
||||||
fields.add_field_method_get("Right", |_, this| Ok(this.right));
|
fields.add_field_method_get("Right", |_, this| Ok(this.right));
|
||||||
fields.add_field_method_get("Top", |_, this| Ok(this.top));
|
fields.add_field_method_get("Top", |_, this| Ok(this.top));
|
||||||
fields.add_field_method_get("Back", |_, this| Ok(this.back));
|
fields.add_field_method_get("Back", |_, this| Ok(this.back));
|
||||||
|
@ -96,7 +96,7 @@ impl LuaUserData for Faces {
|
||||||
fields.add_field_method_get("Front", |_, this| Ok(this.front));
|
fields.add_field_method_get("Front", |_, this| Ok(this.front));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||||
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
||||||
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,11 +40,11 @@ impl Font {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaExportsTable<'_> for Font {
|
impl LuaExportsTable for Font {
|
||||||
const EXPORT_NAME: &'static str = "Font";
|
const EXPORT_NAME: &'static str = "Font";
|
||||||
|
|
||||||
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
|
fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {
|
||||||
let font_from_enum = |_, value: LuaUserDataRef<EnumItem>| {
|
let font_from_enum = |_: &Lua, value: LuaUserDataRef<EnumItem>| {
|
||||||
if value.parent.desc.name == "Font" {
|
if value.parent.desc.name == "Font" {
|
||||||
match Font::from_enum_item(&value) {
|
match Font::from_enum_item(&value) {
|
||||||
Some(props) => Ok(props),
|
Some(props) => Ok(props),
|
||||||
|
@ -62,7 +62,7 @@ impl LuaExportsTable<'_> for Font {
|
||||||
};
|
};
|
||||||
|
|
||||||
let font_from_name =
|
let font_from_name =
|
||||||
|_, (file, weight, style): (String, Option<FontWeight>, Option<FontStyle>)| {
|
|_: &Lua, (file, weight, style): (String, Option<FontWeight>, Option<FontStyle>)| {
|
||||||
Ok(Font {
|
Ok(Font {
|
||||||
family: format!("rbxasset://fonts/families/{file}.json"),
|
family: format!("rbxasset://fonts/families/{file}.json"),
|
||||||
weight: weight.unwrap_or_default(),
|
weight: weight.unwrap_or_default(),
|
||||||
|
@ -72,7 +72,7 @@ impl LuaExportsTable<'_> for Font {
|
||||||
};
|
};
|
||||||
|
|
||||||
let font_from_id =
|
let font_from_id =
|
||||||
|_, (id, weight, style): (i32, Option<FontWeight>, Option<FontStyle>)| {
|
|_: &Lua, (id, weight, style): (i32, Option<FontWeight>, Option<FontStyle>)| {
|
||||||
Ok(Font {
|
Ok(Font {
|
||||||
family: format!("rbxassetid://{id}"),
|
family: format!("rbxassetid://{id}"),
|
||||||
weight: weight.unwrap_or_default(),
|
weight: weight.unwrap_or_default(),
|
||||||
|
@ -82,7 +82,7 @@ impl LuaExportsTable<'_> for Font {
|
||||||
};
|
};
|
||||||
|
|
||||||
let font_new =
|
let font_new =
|
||||||
|_, (family, weight, style): (String, Option<FontWeight>, Option<FontStyle>)| {
|
|_: &Lua, (family, weight, style): (String, Option<FontWeight>, Option<FontStyle>)| {
|
||||||
Ok(Font {
|
Ok(Font {
|
||||||
family,
|
family,
|
||||||
weight: weight.unwrap_or_default(),
|
weight: weight.unwrap_or_default(),
|
||||||
|
@ -101,7 +101,7 @@ impl LuaExportsTable<'_> for Font {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaUserData for Font {
|
impl LuaUserData for Font {
|
||||||
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
||||||
// Getters
|
// Getters
|
||||||
fields.add_field_method_get("Family", |_, this| Ok(this.family.clone()));
|
fields.add_field_method_get("Family", |_, this| Ok(this.family.clone()));
|
||||||
fields.add_field_method_get("Weight", |_, this| Ok(this.weight));
|
fields.add_field_method_get("Weight", |_, this| Ok(this.weight));
|
||||||
|
@ -126,7 +126,7 @@ impl LuaUserData for Font {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||||
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
||||||
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
||||||
}
|
}
|
||||||
|
@ -282,8 +282,8 @@ impl std::fmt::Display for FontWeight {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'lua> FromLua<'lua> for FontWeight {
|
impl FromLua for FontWeight {
|
||||||
fn from_lua(lua_value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
|
fn from_lua(lua_value: LuaValue, _: &Lua) -> LuaResult<Self> {
|
||||||
let mut message = None;
|
let mut message = None;
|
||||||
if let LuaValue::UserData(ud) = &lua_value {
|
if let LuaValue::UserData(ud) = &lua_value {
|
||||||
let value = ud.borrow::<EnumItem>()?;
|
let value = ud.borrow::<EnumItem>()?;
|
||||||
|
@ -304,18 +304,18 @@ impl<'lua> FromLua<'lua> for FontWeight {
|
||||||
}
|
}
|
||||||
Err(LuaError::FromLuaConversionError {
|
Err(LuaError::FromLuaConversionError {
|
||||||
from: lua_value.type_name(),
|
from: lua_value.type_name(),
|
||||||
to: "Enum.FontWeight",
|
to: "Enum.FontWeight".to_string(),
|
||||||
message,
|
message,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'lua> IntoLua<'lua> for FontWeight {
|
impl IntoLua for FontWeight {
|
||||||
fn into_lua(self, lua: &'lua Lua) -> LuaResult<LuaValue<'lua>> {
|
fn into_lua(self, lua: &Lua) -> LuaResult<LuaValue> {
|
||||||
match EnumItem::from_enum_name_and_name("FontWeight", self.to_string()) {
|
match EnumItem::from_enum_name_and_name("FontWeight", self.to_string()) {
|
||||||
Some(enum_item) => Ok(LuaValue::UserData(lua.create_userdata(enum_item)?)),
|
Some(enum_item) => Ok(LuaValue::UserData(lua.create_userdata(enum_item)?)),
|
||||||
None => Err(LuaError::ToLuaConversionError {
|
None => Err(LuaError::ToLuaConversionError {
|
||||||
from: "FontWeight",
|
from: "FontWeight".to_string(),
|
||||||
to: "EnumItem",
|
to: "EnumItem",
|
||||||
message: Some(format!("Found unknown Enum.FontWeight value '{self}'")),
|
message: Some(format!("Found unknown Enum.FontWeight value '{self}'")),
|
||||||
}),
|
}),
|
||||||
|
@ -376,8 +376,8 @@ impl std::fmt::Display for FontStyle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'lua> FromLua<'lua> for FontStyle {
|
impl FromLua for FontStyle {
|
||||||
fn from_lua(lua_value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
|
fn from_lua(lua_value: LuaValue, _: &Lua) -> LuaResult<Self> {
|
||||||
let mut message = None;
|
let mut message = None;
|
||||||
if let LuaValue::UserData(ud) = &lua_value {
|
if let LuaValue::UserData(ud) = &lua_value {
|
||||||
let value = ud.borrow::<EnumItem>()?;
|
let value = ud.borrow::<EnumItem>()?;
|
||||||
|
@ -398,18 +398,18 @@ impl<'lua> FromLua<'lua> for FontStyle {
|
||||||
}
|
}
|
||||||
Err(LuaError::FromLuaConversionError {
|
Err(LuaError::FromLuaConversionError {
|
||||||
from: lua_value.type_name(),
|
from: lua_value.type_name(),
|
||||||
to: "Enum.FontStyle",
|
to: "Enum.FontStyle".to_string(),
|
||||||
message,
|
message,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'lua> IntoLua<'lua> for FontStyle {
|
impl IntoLua for FontStyle {
|
||||||
fn into_lua(self, lua: &'lua Lua) -> LuaResult<LuaValue<'lua>> {
|
fn into_lua(self, lua: &Lua) -> LuaResult<LuaValue> {
|
||||||
match EnumItem::from_enum_name_and_name("FontStyle", self.to_string()) {
|
match EnumItem::from_enum_name_and_name("FontStyle", self.to_string()) {
|
||||||
Some(enum_item) => Ok(LuaValue::UserData(lua.create_userdata(enum_item)?)),
|
Some(enum_item) => Ok(LuaValue::UserData(lua.create_userdata(enum_item)?)),
|
||||||
None => Err(LuaError::ToLuaConversionError {
|
None => Err(LuaError::ToLuaConversionError {
|
||||||
from: "FontStyle",
|
from: "FontStyle".to_string(),
|
||||||
to: "EnumItem",
|
to: "EnumItem",
|
||||||
message: Some(format!("Found unknown Enum.FontStyle value '{self}'")),
|
message: Some(format!("Found unknown Enum.FontStyle value '{self}'")),
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -4,6 +4,7 @@ mod cframe;
|
||||||
mod color3;
|
mod color3;
|
||||||
mod color_sequence;
|
mod color_sequence;
|
||||||
mod color_sequence_keypoint;
|
mod color_sequence_keypoint;
|
||||||
|
mod content;
|
||||||
mod r#enum;
|
mod r#enum;
|
||||||
mod r#enum_item;
|
mod r#enum_item;
|
||||||
mod r#enums;
|
mod r#enums;
|
||||||
|
@ -30,6 +31,7 @@ pub use cframe::CFrame;
|
||||||
pub use color3::Color3;
|
pub use color3::Color3;
|
||||||
pub use color_sequence::ColorSequence;
|
pub use color_sequence::ColorSequence;
|
||||||
pub use color_sequence_keypoint::ColorSequenceKeypoint;
|
pub use color_sequence_keypoint::ColorSequenceKeypoint;
|
||||||
|
pub use content::Content;
|
||||||
pub use faces::Faces;
|
pub use faces::Faces;
|
||||||
pub use font::Font;
|
pub use font::Font;
|
||||||
pub use number_range::NumberRange;
|
pub use number_range::NumberRange;
|
||||||
|
|
|
@ -20,11 +20,11 @@ pub struct NumberRange {
|
||||||
pub(crate) max: f32,
|
pub(crate) max: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaExportsTable<'_> for NumberRange {
|
impl LuaExportsTable for NumberRange {
|
||||||
const EXPORT_NAME: &'static str = "NumberRange";
|
const EXPORT_NAME: &'static str = "NumberRange";
|
||||||
|
|
||||||
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
|
fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {
|
||||||
let number_range_new = |_, (min, max): (f32, Option<f32>)| {
|
let number_range_new = |_: &Lua, (min, max): (f32, Option<f32>)| {
|
||||||
Ok(match max {
|
Ok(match max {
|
||||||
Some(max) => NumberRange {
|
Some(max) => NumberRange {
|
||||||
min: min.min(max),
|
min: min.min(max),
|
||||||
|
@ -41,12 +41,12 @@ impl LuaExportsTable<'_> for NumberRange {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaUserData for NumberRange {
|
impl LuaUserData for NumberRange {
|
||||||
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
||||||
fields.add_field_method_get("Min", |_, this| Ok(this.min));
|
fields.add_field_method_get("Min", |_, this| Ok(this.min));
|
||||||
fields.add_field_method_get("Max", |_, this| Ok(this.max));
|
fields.add_field_method_get("Max", |_, this| Ok(this.max));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||||
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
||||||
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,15 +21,15 @@ pub struct NumberSequence {
|
||||||
pub(crate) keypoints: Vec<NumberSequenceKeypoint>,
|
pub(crate) keypoints: Vec<NumberSequenceKeypoint>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaExportsTable<'_> for NumberSequence {
|
impl LuaExportsTable for NumberSequence {
|
||||||
const EXPORT_NAME: &'static str = "NumberSequence";
|
const EXPORT_NAME: &'static str = "NumberSequence";
|
||||||
|
|
||||||
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
|
fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {
|
||||||
type ArgsColor = f32;
|
type ArgsColor = f32;
|
||||||
type ArgsColors = (f32, f32);
|
type ArgsColors = (f32, f32);
|
||||||
type ArgsKeypoints<'lua> = Vec<LuaUserDataRef<'lua, NumberSequenceKeypoint>>;
|
type ArgsKeypoints = Vec<LuaUserDataRef<NumberSequenceKeypoint>>;
|
||||||
|
|
||||||
let number_sequence_new = |lua, args: LuaMultiValue| {
|
let number_sequence_new = |lua: &Lua, args: LuaMultiValue| {
|
||||||
if let Ok(value) = ArgsColor::from_lua_multi(args.clone(), lua) {
|
if let Ok(value) = ArgsColor::from_lua_multi(args.clone(), lua) {
|
||||||
Ok(NumberSequence {
|
Ok(NumberSequence {
|
||||||
keypoints: vec![
|
keypoints: vec![
|
||||||
|
@ -79,11 +79,11 @@ impl LuaExportsTable<'_> for NumberSequence {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaUserData for NumberSequence {
|
impl LuaUserData for NumberSequence {
|
||||||
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
||||||
fields.add_field_method_get("Keypoints", |_, this| Ok(this.keypoints.clone()));
|
fields.add_field_method_get("Keypoints", |_, this| Ok(this.keypoints.clone()));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||||
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
||||||
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,17 +21,18 @@ pub struct NumberSequenceKeypoint {
|
||||||
pub(crate) envelope: f32,
|
pub(crate) envelope: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaExportsTable<'_> for NumberSequenceKeypoint {
|
impl LuaExportsTable for NumberSequenceKeypoint {
|
||||||
const EXPORT_NAME: &'static str = "NumberSequenceKeypoint";
|
const EXPORT_NAME: &'static str = "NumberSequenceKeypoint";
|
||||||
|
|
||||||
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
|
fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {
|
||||||
let number_sequence_keypoint_new = |_, (time, value, envelope): (f32, f32, Option<f32>)| {
|
let number_sequence_keypoint_new =
|
||||||
Ok(NumberSequenceKeypoint {
|
|_: &Lua, (time, value, envelope): (f32, f32, Option<f32>)| {
|
||||||
time,
|
Ok(NumberSequenceKeypoint {
|
||||||
value,
|
time,
|
||||||
envelope: envelope.unwrap_or_default(),
|
value,
|
||||||
})
|
envelope: envelope.unwrap_or_default(),
|
||||||
};
|
})
|
||||||
|
};
|
||||||
|
|
||||||
TableBuilder::new(lua)?
|
TableBuilder::new(lua)?
|
||||||
.with_function("new", number_sequence_keypoint_new)?
|
.with_function("new", number_sequence_keypoint_new)?
|
||||||
|
@ -40,13 +41,13 @@ impl LuaExportsTable<'_> for NumberSequenceKeypoint {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaUserData for NumberSequenceKeypoint {
|
impl LuaUserData for NumberSequenceKeypoint {
|
||||||
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
||||||
fields.add_field_method_get("Time", |_, this| Ok(this.time));
|
fields.add_field_method_get("Time", |_, this| Ok(this.time));
|
||||||
fields.add_field_method_get("Value", |_, this| Ok(this.value));
|
fields.add_field_method_get("Value", |_, this| Ok(this.value));
|
||||||
fields.add_field_method_get("Envelope", |_, this| Ok(this.envelope));
|
fields.add_field_method_get("Envelope", |_, this| Ok(this.envelope));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||||
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
||||||
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,14 +38,14 @@ impl PhysicalProperties {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaExportsTable<'_> for PhysicalProperties {
|
impl LuaExportsTable for PhysicalProperties {
|
||||||
const EXPORT_NAME: &'static str = "PhysicalProperties";
|
const EXPORT_NAME: &'static str = "PhysicalProperties";
|
||||||
|
|
||||||
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
|
fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {
|
||||||
type ArgsMaterial<'lua> = LuaUserDataRef<'lua, EnumItem>;
|
type ArgsMaterial = LuaUserDataRef<EnumItem>;
|
||||||
type ArgsNumbers = (f32, f32, f32, Option<f32>, Option<f32>);
|
type ArgsNumbers = (f32, f32, f32, Option<f32>, Option<f32>);
|
||||||
|
|
||||||
let physical_properties_new = |lua, args: LuaMultiValue| {
|
let physical_properties_new = |lua: &Lua, args: LuaMultiValue| {
|
||||||
if let Ok(value) = ArgsMaterial::from_lua_multi(args.clone(), lua) {
|
if let Ok(value) = ArgsMaterial::from_lua_multi(args.clone(), lua) {
|
||||||
if value.parent.desc.name == "Material" {
|
if value.parent.desc.name == "Material" {
|
||||||
match PhysicalProperties::from_material(&value) {
|
match PhysicalProperties::from_material(&value) {
|
||||||
|
@ -86,7 +86,7 @@ impl LuaExportsTable<'_> for PhysicalProperties {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaUserData for PhysicalProperties {
|
impl LuaUserData for PhysicalProperties {
|
||||||
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
||||||
fields.add_field_method_get("Density", |_, this| Ok(this.density));
|
fields.add_field_method_get("Density", |_, this| Ok(this.density));
|
||||||
fields.add_field_method_get("Friction", |_, this| Ok(this.friction));
|
fields.add_field_method_get("Friction", |_, this| Ok(this.friction));
|
||||||
fields.add_field_method_get("FrictionWeight", |_, this| Ok(this.friction_weight));
|
fields.add_field_method_get("FrictionWeight", |_, this| Ok(this.friction_weight));
|
||||||
|
@ -94,7 +94,7 @@ impl LuaUserData for PhysicalProperties {
|
||||||
fields.add_field_method_get("ElasticityWeight", |_, this| Ok(this.elasticity_weight));
|
fields.add_field_method_get("ElasticityWeight", |_, this| Ok(this.elasticity_weight));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||||
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
||||||
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,12 +32,12 @@ impl Ray {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaExportsTable<'_> for Ray {
|
impl LuaExportsTable for Ray {
|
||||||
const EXPORT_NAME: &'static str = "Ray";
|
const EXPORT_NAME: &'static str = "Ray";
|
||||||
|
|
||||||
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
|
fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {
|
||||||
let ray_new =
|
let ray_new =
|
||||||
|_, (origin, direction): (LuaUserDataRef<Vector3>, LuaUserDataRef<Vector3>)| {
|
|_: &Lua, (origin, direction): (LuaUserDataRef<Vector3>, LuaUserDataRef<Vector3>)| {
|
||||||
Ok(Ray {
|
Ok(Ray {
|
||||||
origin: origin.0,
|
origin: origin.0,
|
||||||
direction: direction.0,
|
direction: direction.0,
|
||||||
|
@ -51,7 +51,7 @@ impl LuaExportsTable<'_> for Ray {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaUserData for Ray {
|
impl LuaUserData for Ray {
|
||||||
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
||||||
fields.add_field_method_get("Origin", |_, this| Ok(Vector3(this.origin)));
|
fields.add_field_method_get("Origin", |_, this| Ok(Vector3(this.origin)));
|
||||||
fields.add_field_method_get("Direction", |_, this| Ok(Vector3(this.direction)));
|
fields.add_field_method_get("Direction", |_, this| Ok(Vector3(this.direction)));
|
||||||
fields.add_field_method_get("Unit", |_, this| {
|
fields.add_field_method_get("Unit", |_, this| {
|
||||||
|
@ -62,7 +62,7 @@ impl LuaUserData for Ray {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||||
// Methods
|
// Methods
|
||||||
methods.add_method("ClosestPoint", |_, this, to: LuaUserDataRef<Vector3>| {
|
methods.add_method("ClosestPoint", |_, this, to: LuaUserDataRef<Vector3>| {
|
||||||
Ok(Vector3(this.closest_point(to.0)))
|
Ok(Vector3(this.closest_point(to.0)))
|
||||||
|
|
|
@ -32,17 +32,17 @@ impl Rect {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaExportsTable<'_> for Rect {
|
impl LuaExportsTable for Rect {
|
||||||
const EXPORT_NAME: &'static str = "Rect";
|
const EXPORT_NAME: &'static str = "Rect";
|
||||||
|
|
||||||
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
|
fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {
|
||||||
type ArgsVector2s<'lua> = (
|
type ArgsVector2s = (
|
||||||
Option<LuaUserDataRef<'lua, Vector2>>,
|
Option<LuaUserDataRef<Vector2>>,
|
||||||
Option<LuaUserDataRef<'lua, Vector2>>,
|
Option<LuaUserDataRef<Vector2>>,
|
||||||
);
|
);
|
||||||
type ArgsNums = (Option<f32>, Option<f32>, Option<f32>, Option<f32>);
|
type ArgsNums = (Option<f32>, Option<f32>, Option<f32>, Option<f32>);
|
||||||
|
|
||||||
let rect_new = |lua, args: LuaMultiValue| {
|
let rect_new = |lua: &Lua, args: LuaMultiValue| {
|
||||||
if let Ok((min, max)) = ArgsVector2s::from_lua_multi(args.clone(), lua) {
|
if let Ok((min, max)) = ArgsVector2s::from_lua_multi(args.clone(), lua) {
|
||||||
Ok(Rect::new(
|
Ok(Rect::new(
|
||||||
min.map(|m| *m).unwrap_or_default().0,
|
min.map(|m| *m).unwrap_or_default().0,
|
||||||
|
@ -67,14 +67,14 @@ impl LuaExportsTable<'_> for Rect {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaUserData for Rect {
|
impl LuaUserData for Rect {
|
||||||
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
||||||
fields.add_field_method_get("Min", |_, this| Ok(Vector2(this.min)));
|
fields.add_field_method_get("Min", |_, this| Ok(Vector2(this.min)));
|
||||||
fields.add_field_method_get("Max", |_, this| Ok(Vector2(this.max)));
|
fields.add_field_method_get("Max", |_, this| Ok(Vector2(this.max)));
|
||||||
fields.add_field_method_get("Width", |_, this| Ok(this.max.x - this.min.x));
|
fields.add_field_method_get("Width", |_, this| Ok(this.max.x - this.min.x));
|
||||||
fields.add_field_method_get("Height", |_, this| Ok(this.max.y - this.min.y));
|
fields.add_field_method_get("Height", |_, this| Ok(this.max.y - this.min.y));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||||
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
||||||
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
||||||
methods.add_meta_method(LuaMetaMethod::Unm, userdata_impl_unm);
|
methods.add_meta_method(LuaMetaMethod::Unm, userdata_impl_unm);
|
||||||
|
|
|
@ -22,16 +22,17 @@ pub struct Region3 {
|
||||||
pub(crate) max: Vec3,
|
pub(crate) max: Vec3,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaExportsTable<'_> for Region3 {
|
impl LuaExportsTable for Region3 {
|
||||||
const EXPORT_NAME: &'static str = "Region3";
|
const EXPORT_NAME: &'static str = "Region3";
|
||||||
|
|
||||||
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
|
fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {
|
||||||
let region3_new = |_, (min, max): (LuaUserDataRef<Vector3>, LuaUserDataRef<Vector3>)| {
|
let region3_new =
|
||||||
Ok(Region3 {
|
|_: &Lua, (min, max): (LuaUserDataRef<Vector3>, LuaUserDataRef<Vector3>)| {
|
||||||
min: min.0,
|
Ok(Region3 {
|
||||||
max: max.0,
|
min: min.0,
|
||||||
})
|
max: max.0,
|
||||||
};
|
})
|
||||||
|
};
|
||||||
|
|
||||||
TableBuilder::new(lua)?
|
TableBuilder::new(lua)?
|
||||||
.with_function("new", region3_new)?
|
.with_function("new", region3_new)?
|
||||||
|
@ -40,14 +41,14 @@ impl LuaExportsTable<'_> for Region3 {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaUserData for Region3 {
|
impl LuaUserData for Region3 {
|
||||||
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
||||||
fields.add_field_method_get("CFrame", |_, this| {
|
fields.add_field_method_get("CFrame", |_, this| {
|
||||||
Ok(CFrame(Mat4::from_translation(this.min.lerp(this.max, 0.5))))
|
Ok(CFrame(Mat4::from_translation(this.min.lerp(this.max, 0.5))))
|
||||||
});
|
});
|
||||||
fields.add_field_method_get("Size", |_, this| Ok(Vector3(this.max - this.min)));
|
fields.add_field_method_get("Size", |_, this| Ok(Vector3(this.max - this.min)));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||||
// Methods
|
// Methods
|
||||||
methods.add_method("ExpandToGrid", |_, this, resolution: f32| {
|
methods.add_method("ExpandToGrid", |_, this, resolution: f32| {
|
||||||
Ok(Region3 {
|
Ok(Region3 {
|
||||||
|
|
|
@ -22,12 +22,12 @@ pub struct Region3int16 {
|
||||||
pub(crate) max: IVec3,
|
pub(crate) max: IVec3,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaExportsTable<'_> for Region3int16 {
|
impl LuaExportsTable for Region3int16 {
|
||||||
const EXPORT_NAME: &'static str = "Region3int16";
|
const EXPORT_NAME: &'static str = "Region3int16";
|
||||||
|
|
||||||
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
|
fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {
|
||||||
let region3int16_new =
|
let region3int16_new =
|
||||||
|_, (min, max): (LuaUserDataRef<Vector3int16>, LuaUserDataRef<Vector3int16>)| {
|
|_: &Lua, (min, max): (LuaUserDataRef<Vector3int16>, LuaUserDataRef<Vector3int16>)| {
|
||||||
Ok(Region3int16 {
|
Ok(Region3int16 {
|
||||||
min: min.0,
|
min: min.0,
|
||||||
max: max.0,
|
max: max.0,
|
||||||
|
@ -41,12 +41,12 @@ impl LuaExportsTable<'_> for Region3int16 {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaUserData for Region3int16 {
|
impl LuaUserData for Region3int16 {
|
||||||
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
||||||
fields.add_field_method_get("Min", |_, this| Ok(Vector3int16(this.min)));
|
fields.add_field_method_get("Min", |_, this| Ok(Vector3int16(this.min)));
|
||||||
fields.add_field_method_get("Max", |_, this| Ok(Vector3int16(this.max)));
|
fields.add_field_method_get("Max", |_, this| Ok(Vector3int16(this.max)));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||||
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
||||||
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,11 +27,11 @@ impl UDim {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaExportsTable<'_> for UDim {
|
impl LuaExportsTable for UDim {
|
||||||
const EXPORT_NAME: &'static str = "UDim";
|
const EXPORT_NAME: &'static str = "UDim";
|
||||||
|
|
||||||
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
|
fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {
|
||||||
let udim_new = |_, (scale, offset): (Option<f32>, Option<i32>)| {
|
let udim_new = |_: &Lua, (scale, offset): (Option<f32>, Option<i32>)| {
|
||||||
Ok(UDim {
|
Ok(UDim {
|
||||||
scale: scale.unwrap_or_default(),
|
scale: scale.unwrap_or_default(),
|
||||||
offset: offset.unwrap_or_default(),
|
offset: offset.unwrap_or_default(),
|
||||||
|
@ -45,12 +45,12 @@ impl LuaExportsTable<'_> for UDim {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaUserData for UDim {
|
impl LuaUserData for UDim {
|
||||||
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
||||||
fields.add_field_method_get("Scale", |_, this| Ok(this.scale));
|
fields.add_field_method_get("Scale", |_, this| Ok(this.scale));
|
||||||
fields.add_field_method_get("Offset", |_, this| Ok(this.offset));
|
fields.add_field_method_get("Offset", |_, this| Ok(this.offset));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||||
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
||||||
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
||||||
methods.add_meta_method(LuaMetaMethod::Unm, userdata_impl_unm);
|
methods.add_meta_method(LuaMetaMethod::Unm, userdata_impl_unm);
|
||||||
|
|
|
@ -24,30 +24,27 @@ pub struct UDim2 {
|
||||||
pub(crate) y: UDim,
|
pub(crate) y: UDim,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaExportsTable<'_> for UDim2 {
|
impl LuaExportsTable for UDim2 {
|
||||||
const EXPORT_NAME: &'static str = "UDim2";
|
const EXPORT_NAME: &'static str = "UDim2";
|
||||||
|
|
||||||
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
|
fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {
|
||||||
let udim2_from_offset = |_, (x, y): (Option<i32>, Option<i32>)| {
|
let udim2_from_offset = |_: &Lua, (x, y): (Option<i32>, Option<i32>)| {
|
||||||
Ok(UDim2 {
|
Ok(UDim2 {
|
||||||
x: UDim::new(0f32, x.unwrap_or_default()),
|
x: UDim::new(0f32, x.unwrap_or_default()),
|
||||||
y: UDim::new(0f32, y.unwrap_or_default()),
|
y: UDim::new(0f32, y.unwrap_or_default()),
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
let udim2_from_scale = |_, (x, y): (Option<f32>, Option<f32>)| {
|
let udim2_from_scale = |_: &Lua, (x, y): (Option<f32>, Option<f32>)| {
|
||||||
Ok(UDim2 {
|
Ok(UDim2 {
|
||||||
x: UDim::new(x.unwrap_or_default(), 0),
|
x: UDim::new(x.unwrap_or_default(), 0),
|
||||||
y: UDim::new(y.unwrap_or_default(), 0),
|
y: UDim::new(y.unwrap_or_default(), 0),
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
type ArgsUDims<'lua> = (
|
type ArgsUDims = (Option<LuaUserDataRef<UDim>>, Option<LuaUserDataRef<UDim>>);
|
||||||
Option<LuaUserDataRef<'lua, UDim>>,
|
|
||||||
Option<LuaUserDataRef<'lua, UDim>>,
|
|
||||||
);
|
|
||||||
type ArgsNums = (Option<f32>, Option<i32>, Option<f32>, Option<i32>);
|
type ArgsNums = (Option<f32>, Option<i32>, Option<f32>, Option<i32>);
|
||||||
let udim2_new = |lua, args: LuaMultiValue| {
|
let udim2_new = |lua: &Lua, args: LuaMultiValue| {
|
||||||
if let Ok((x, y)) = ArgsUDims::from_lua_multi(args.clone(), lua) {
|
if let Ok((x, y)) = ArgsUDims::from_lua_multi(args.clone(), lua) {
|
||||||
Ok(UDim2 {
|
Ok(UDim2 {
|
||||||
x: x.map(|x| *x).unwrap_or_default(),
|
x: x.map(|x| *x).unwrap_or_default(),
|
||||||
|
@ -75,14 +72,14 @@ impl LuaExportsTable<'_> for UDim2 {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaUserData for UDim2 {
|
impl LuaUserData for UDim2 {
|
||||||
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
||||||
fields.add_field_method_get("X", |_, this| Ok(this.x));
|
fields.add_field_method_get("X", |_, this| Ok(this.x));
|
||||||
fields.add_field_method_get("Y", |_, this| Ok(this.y));
|
fields.add_field_method_get("Y", |_, this| Ok(this.y));
|
||||||
fields.add_field_method_get("Width", |_, this| Ok(this.x));
|
fields.add_field_method_get("Width", |_, this| Ok(this.x));
|
||||||
fields.add_field_method_get("Height", |_, this| Ok(this.y));
|
fields.add_field_method_get("Height", |_, this| Ok(this.y));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||||
// Methods
|
// Methods
|
||||||
methods.add_method(
|
methods.add_method(
|
||||||
"Lerp",
|
"Lerp",
|
||||||
|
|
|
@ -21,11 +21,11 @@ use super::super::*;
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
||||||
pub struct Vector2(pub Vec2);
|
pub struct Vector2(pub Vec2);
|
||||||
|
|
||||||
impl LuaExportsTable<'_> for Vector2 {
|
impl LuaExportsTable for Vector2 {
|
||||||
const EXPORT_NAME: &'static str = "Vector2";
|
const EXPORT_NAME: &'static str = "Vector2";
|
||||||
|
|
||||||
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
|
fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {
|
||||||
let vector2_new = |_, (x, y): (Option<f32>, Option<f32>)| {
|
let vector2_new = |_: &Lua, (x, y): (Option<f32>, Option<f32>)| {
|
||||||
Ok(Vector2(Vec2 {
|
Ok(Vector2(Vec2 {
|
||||||
x: x.unwrap_or_default(),
|
x: x.unwrap_or_default(),
|
||||||
y: y.unwrap_or_default(),
|
y: y.unwrap_or_default(),
|
||||||
|
@ -43,17 +43,17 @@ impl LuaExportsTable<'_> for Vector2 {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaUserData for Vector2 {
|
impl LuaUserData for Vector2 {
|
||||||
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
||||||
fields.add_field_method_get("Magnitude", |_, this| Ok(this.0.length()));
|
fields.add_field_method_get("Magnitude", |_, this| Ok(this.0.length()));
|
||||||
fields.add_field_method_get("Unit", |_, this| Ok(Vector2(this.0.normalize())));
|
fields.add_field_method_get("Unit", |_, this| Ok(Vector2(this.0.normalize())));
|
||||||
fields.add_field_method_get("X", |_, this| Ok(this.0.x));
|
fields.add_field_method_get("X", |_, this| Ok(this.0.x));
|
||||||
fields.add_field_method_get("Y", |_, this| Ok(this.0.y));
|
fields.add_field_method_get("Y", |_, this| Ok(this.0.y));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||||
// Methods
|
// Methods
|
||||||
methods.add_method("Angle", |_, this, rhs: LuaUserDataRef<Vector2>| {
|
methods.add_method("Angle", |_, this, rhs: LuaUserDataRef<Vector2>| {
|
||||||
Ok(this.0.angle_between(rhs.0))
|
Ok(this.0.angle_to(rhs.0))
|
||||||
});
|
});
|
||||||
methods.add_method("Cross", |_, this, rhs: LuaUserDataRef<Vector2>| {
|
methods.add_method("Cross", |_, this, rhs: LuaUserDataRef<Vector2>| {
|
||||||
let this_v3 = Vec3::new(this.0.x, this.0.y, 0f32);
|
let this_v3 = Vec3::new(this.0.x, this.0.y, 0f32);
|
||||||
|
|
|
@ -21,11 +21,11 @@ use super::super::*;
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub struct Vector2int16(pub IVec2);
|
pub struct Vector2int16(pub IVec2);
|
||||||
|
|
||||||
impl LuaExportsTable<'_> for Vector2int16 {
|
impl LuaExportsTable for Vector2int16 {
|
||||||
const EXPORT_NAME: &'static str = "Vector2int16";
|
const EXPORT_NAME: &'static str = "Vector2int16";
|
||||||
|
|
||||||
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
|
fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {
|
||||||
let vector2int16_new = |_, (x, y): (Option<i16>, Option<i16>)| {
|
let vector2int16_new = |_: &Lua, (x, y): (Option<i16>, Option<i16>)| {
|
||||||
Ok(Vector2int16(IVec2 {
|
Ok(Vector2int16(IVec2 {
|
||||||
x: x.unwrap_or_default() as i32,
|
x: x.unwrap_or_default() as i32,
|
||||||
y: y.unwrap_or_default() as i32,
|
y: y.unwrap_or_default() as i32,
|
||||||
|
@ -39,12 +39,12 @@ impl LuaExportsTable<'_> for Vector2int16 {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaUserData for Vector2int16 {
|
impl LuaUserData for Vector2int16 {
|
||||||
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
||||||
fields.add_field_method_get("X", |_, this| Ok(this.0.x));
|
fields.add_field_method_get("X", |_, this| Ok(this.0.x));
|
||||||
fields.add_field_method_get("Y", |_, this| Ok(this.0.y));
|
fields.add_field_method_get("Y", |_, this| Ok(this.0.y));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||||
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
||||||
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
||||||
methods.add_meta_method(LuaMetaMethod::Unm, userdata_impl_unm);
|
methods.add_meta_method(LuaMetaMethod::Unm, userdata_impl_unm);
|
||||||
|
|
|
@ -24,11 +24,11 @@ use super::{super::*, EnumItem};
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub struct Vector3(pub Vec3);
|
pub struct Vector3(pub Vec3);
|
||||||
|
|
||||||
impl LuaExportsTable<'_> for Vector3 {
|
impl LuaExportsTable for Vector3 {
|
||||||
const EXPORT_NAME: &'static str = "Vector3";
|
const EXPORT_NAME: &'static str = "Vector3";
|
||||||
|
|
||||||
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
|
fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {
|
||||||
let vector3_from_axis = |_, normal_id: LuaUserDataRef<EnumItem>| {
|
let vector3_from_axis = |_: &Lua, normal_id: LuaUserDataRef<EnumItem>| {
|
||||||
if normal_id.parent.desc.name == "Axis" {
|
if normal_id.parent.desc.name == "Axis" {
|
||||||
Ok(match normal_id.name.as_str() {
|
Ok(match normal_id.name.as_str() {
|
||||||
"X" => Vector3(Vec3::X),
|
"X" => Vector3(Vec3::X),
|
||||||
|
@ -48,7 +48,7 @@ impl LuaExportsTable<'_> for Vector3 {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let vector3_from_normal_id = |_, normal_id: LuaUserDataRef<EnumItem>| {
|
let vector3_from_normal_id = |_: &Lua, normal_id: LuaUserDataRef<EnumItem>| {
|
||||||
if normal_id.parent.desc.name == "NormalId" {
|
if normal_id.parent.desc.name == "NormalId" {
|
||||||
Ok(match normal_id.name.as_str() {
|
Ok(match normal_id.name.as_str() {
|
||||||
"Left" => Vector3(Vec3::X),
|
"Left" => Vector3(Vec3::X),
|
||||||
|
@ -71,7 +71,7 @@ impl LuaExportsTable<'_> for Vector3 {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let vector3_new = |_, (x, y, z): (Option<f32>, Option<f32>, Option<f32>)| {
|
let vector3_new = |_: &Lua, (x, y, z): (Option<f32>, Option<f32>, Option<f32>)| {
|
||||||
Ok(Vector3(Vec3 {
|
Ok(Vector3(Vec3 {
|
||||||
x: x.unwrap_or_default(),
|
x: x.unwrap_or_default(),
|
||||||
y: y.unwrap_or_default(),
|
y: y.unwrap_or_default(),
|
||||||
|
@ -93,7 +93,7 @@ impl LuaExportsTable<'_> for Vector3 {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaUserData for Vector3 {
|
impl LuaUserData for Vector3 {
|
||||||
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
||||||
fields.add_field_method_get("Magnitude", |_, this| Ok(this.0.length()));
|
fields.add_field_method_get("Magnitude", |_, this| Ok(this.0.length()));
|
||||||
fields.add_field_method_get("Unit", |_, this| Ok(Vector3(this.0.normalize())));
|
fields.add_field_method_get("Unit", |_, this| Ok(Vector3(this.0.normalize())));
|
||||||
fields.add_field_method_get("X", |_, this| Ok(this.0.x));
|
fields.add_field_method_get("X", |_, this| Ok(this.0.x));
|
||||||
|
@ -101,7 +101,7 @@ impl LuaUserData for Vector3 {
|
||||||
fields.add_field_method_get("Z", |_, this| Ok(this.0.z));
|
fields.add_field_method_get("Z", |_, this| Ok(this.0.z));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||||
// Methods
|
// Methods
|
||||||
methods.add_method("Angle", |_, this, rhs: LuaUserDataRef<Vector3>| {
|
methods.add_method("Angle", |_, this, rhs: LuaUserDataRef<Vector3>| {
|
||||||
Ok(this.0.angle_between(rhs.0))
|
Ok(this.0.angle_between(rhs.0))
|
||||||
|
|
|
@ -21,11 +21,11 @@ use super::super::*;
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub struct Vector3int16(pub IVec3);
|
pub struct Vector3int16(pub IVec3);
|
||||||
|
|
||||||
impl LuaExportsTable<'_> for Vector3int16 {
|
impl LuaExportsTable for Vector3int16 {
|
||||||
const EXPORT_NAME: &'static str = "Vector3int16";
|
const EXPORT_NAME: &'static str = "Vector3int16";
|
||||||
|
|
||||||
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
|
fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {
|
||||||
let vector3int16_new = |_, (x, y, z): (Option<i16>, Option<i16>, Option<i16>)| {
|
let vector3int16_new = |_: &Lua, (x, y, z): (Option<i16>, Option<i16>, Option<i16>)| {
|
||||||
Ok(Vector3int16(IVec3 {
|
Ok(Vector3int16(IVec3 {
|
||||||
x: x.unwrap_or_default() as i32,
|
x: x.unwrap_or_default() as i32,
|
||||||
y: y.unwrap_or_default() as i32,
|
y: y.unwrap_or_default() as i32,
|
||||||
|
@ -40,13 +40,13 @@ impl LuaExportsTable<'_> for Vector3int16 {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaUserData for Vector3int16 {
|
impl LuaUserData for Vector3int16 {
|
||||||
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
||||||
fields.add_field_method_get("X", |_, this| Ok(this.0.x));
|
fields.add_field_method_get("X", |_, this| Ok(this.0.x));
|
||||||
fields.add_field_method_get("Y", |_, this| Ok(this.0.y));
|
fields.add_field_method_get("Y", |_, this| Ok(this.0.y));
|
||||||
fields.add_field_method_get("Z", |_, this| Ok(this.0.z));
|
fields.add_field_method_get("Z", |_, this| Ok(this.0.z));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||||
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
||||||
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
||||||
methods.add_meta_method(LuaMetaMethod::Unm, userdata_impl_unm);
|
methods.add_meta_method(LuaMetaMethod::Unm, userdata_impl_unm);
|
||||||
|
|
|
@ -65,7 +65,7 @@ impl DocumentKind {
|
||||||
for child_ref in dom.root().children() {
|
for child_ref in dom.root().children() {
|
||||||
if let Some(child_inst) = dom.get_by_ref(*child_ref) {
|
if let Some(child_inst) = dom.get_by_ref(*child_ref) {
|
||||||
has_top_level_child = true;
|
has_top_level_child = true;
|
||||||
if class_is_a_service(&child_inst.class).unwrap_or(false) {
|
if class_is_a_service(child_inst.class).unwrap_or(false) {
|
||||||
has_top_level_service = true;
|
has_top_level_service = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use rbx_dom_weak::{
|
use rbx_dom_weak::{
|
||||||
types::{Ref as DomRef, VariantType as DomType},
|
types::{Ref as DomRef, VariantType as DomType},
|
||||||
Instance as DomInstance, WeakDom,
|
ustr, Instance as DomInstance, WeakDom,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::shared::instance::class_is_a;
|
use crate::shared::instance::class_is_a;
|
||||||
|
@ -18,8 +18,8 @@ pub fn postprocess_dom_for_model(dom: &mut WeakDom) {
|
||||||
remove_matching_prop(inst, DomType::UniqueId, "HistoryId");
|
remove_matching_prop(inst, DomType::UniqueId, "HistoryId");
|
||||||
// Similar story with ScriptGuid - this is used
|
// Similar story with ScriptGuid - this is used
|
||||||
// in the studio-only cloud script drafts feature
|
// in the studio-only cloud script drafts feature
|
||||||
if class_is_a(&inst.class, "LuaSourceContainer").unwrap_or(false) {
|
if class_is_a(inst.class, "LuaSourceContainer").unwrap_or(false) {
|
||||||
inst.properties.remove("ScriptGuid");
|
inst.properties.remove(&ustr("ScriptGuid"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,8 @@ where
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remove_matching_prop(inst: &mut DomInstance, ty: DomType, name: &'static str) {
|
fn remove_matching_prop(inst: &mut DomInstance, ty: DomType, name: &'static str) {
|
||||||
if inst.properties.get(name).map_or(false, |u| u.ty() == ty) {
|
let name = &ustr(name);
|
||||||
|
if inst.properties.get(name).is_some_and(|u| u.ty() == ty) {
|
||||||
inst.properties.remove(name);
|
inst.properties.remove(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,10 +18,10 @@ use mlua::prelude::*;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaExportsTable<'_> for MyType {
|
impl LuaExportsTable for MyType {
|
||||||
const EXPORT_NAME: &'static str = "MyType";
|
const EXPORT_NAME: &'static str = "MyType";
|
||||||
|
|
||||||
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
|
fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {
|
||||||
let my_type_new = |lua, n: Option<usize>| {
|
let my_type_new = |lua, n: Option<usize>| {
|
||||||
Self::new(n.unwrap_or_default())
|
Self::new(n.unwrap_or_default())
|
||||||
};
|
};
|
||||||
|
@ -37,10 +37,10 @@ use mlua::prelude::*;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
pub trait LuaExportsTable<'lua> {
|
pub trait LuaExportsTable {
|
||||||
const EXPORT_NAME: &'static str;
|
const EXPORT_NAME: &'static str;
|
||||||
|
|
||||||
fn create_exports_table(lua: &'lua Lua) -> LuaResult<LuaTable<'lua>>;
|
fn create_exports_table(lua: Lua) -> LuaResult<LuaTable>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -57,12 +57,12 @@ pub trait LuaExportsTable<'lua> {
|
||||||
let (name2, table2) = export::<Type2>(lua)?;
|
let (name2, table2) = export::<Type2>(lua)?;
|
||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
pub fn export<'lua, T>(lua: &'lua Lua) -> LuaResult<(&'static str, LuaValue<'lua>)>
|
pub fn export<T>(lua: Lua) -> LuaResult<(&'static str, LuaValue)>
|
||||||
where
|
where
|
||||||
T: LuaExportsTable<'lua>,
|
T: LuaExportsTable,
|
||||||
{
|
{
|
||||||
Ok((
|
Ok((
|
||||||
T::EXPORT_NAME,
|
T::EXPORT_NAME,
|
||||||
<T as LuaExportsTable>::create_exports_table(lua)?.into_lua(lua)?,
|
<T as LuaExportsTable>::create_exports_table(lua.clone())?.into_lua(&lua)?,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ use crate::{
|
||||||
use super::{data_model, registry::InstanceRegistry, Instance};
|
use super::{data_model, registry::InstanceRegistry, Instance};
|
||||||
|
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
pub fn add_methods<'lua, M: LuaUserDataMethods<'lua, Instance>>(m: &mut M) {
|
pub fn add_methods<M: LuaUserDataMethods<Instance>>(m: &mut M) {
|
||||||
m.add_meta_method(LuaMetaMethod::ToString, |lua, this, ()| {
|
m.add_meta_method(LuaMetaMethod::ToString, |lua, this, ()| {
|
||||||
ensure_not_destroyed(this)?;
|
ensure_not_destroyed(this)?;
|
||||||
userdata_impl_to_string(lua, this, ())
|
userdata_impl_to_string(lua, this, ())
|
||||||
|
@ -71,7 +71,7 @@ pub fn add_methods<'lua, M: LuaUserDataMethods<'lua, Instance>>(m: &mut M) {
|
||||||
"FindFirstAncestorWhichIsA",
|
"FindFirstAncestorWhichIsA",
|
||||||
|lua, this, class_name: String| {
|
|lua, this, class_name: String| {
|
||||||
ensure_not_destroyed(this)?;
|
ensure_not_destroyed(this)?;
|
||||||
this.find_ancestor(|child| class_is_a(&child.class, &class_name).unwrap_or(false))
|
this.find_ancestor(|child| class_is_a(child.class, &class_name).unwrap_or(false))
|
||||||
.into_lua(lua)
|
.into_lua(lua)
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -104,7 +104,7 @@ pub fn add_methods<'lua, M: LuaUserDataMethods<'lua, Instance>>(m: &mut M) {
|
||||||
|lua, this, (class_name, recursive): (String, Option<bool>)| {
|
|lua, this, (class_name, recursive): (String, Option<bool>)| {
|
||||||
ensure_not_destroyed(this)?;
|
ensure_not_destroyed(this)?;
|
||||||
let predicate =
|
let predicate =
|
||||||
|child: &DomInstance| class_is_a(&child.class, &class_name).unwrap_or(false);
|
|child: &DomInstance| class_is_a(child.class, &class_name).unwrap_or(false);
|
||||||
if matches!(recursive, Some(true)) {
|
if matches!(recursive, Some(true)) {
|
||||||
this.find_descendant(predicate).into_lua(lua)
|
this.find_descendant(predicate).into_lua(lua)
|
||||||
} else {
|
} else {
|
||||||
|
@ -113,8 +113,7 @@ pub fn add_methods<'lua, M: LuaUserDataMethods<'lua, Instance>>(m: &mut M) {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
m.add_method("IsA", |_, this, class_name: String| {
|
m.add_method("IsA", |_, this, class_name: String| {
|
||||||
ensure_not_destroyed(this)?;
|
Ok(class_is_a(this.class_name, class_name).unwrap_or(false))
|
||||||
Ok(class_is_a(&this.class_name, class_name).unwrap_or(false))
|
|
||||||
});
|
});
|
||||||
m.add_method(
|
m.add_method(
|
||||||
"IsAncestorOf",
|
"IsAncestorOf",
|
||||||
|
@ -212,25 +211,22 @@ fn ensure_not_destroyed(inst: &Instance) -> LuaResult<()> {
|
||||||
3. Get a current child of the instance
|
3. Get a current child of the instance
|
||||||
4. No valid property or instance found, throw error
|
4. No valid property or instance found, throw error
|
||||||
*/
|
*/
|
||||||
fn instance_property_get<'lua>(
|
fn instance_property_get(lua: &Lua, this: &Instance, prop_name: String) -> LuaResult<LuaValue> {
|
||||||
lua: &'lua Lua,
|
|
||||||
this: &Instance,
|
|
||||||
prop_name: String,
|
|
||||||
) -> LuaResult<LuaValue<'lua>> {
|
|
||||||
ensure_not_destroyed(this)?;
|
|
||||||
|
|
||||||
match prop_name.as_str() {
|
match prop_name.as_str() {
|
||||||
"ClassName" => return this.get_class_name().into_lua(lua),
|
"ClassName" => return this.get_class_name().into_lua(lua),
|
||||||
"Name" => {
|
|
||||||
return this.get_name().into_lua(lua);
|
|
||||||
}
|
|
||||||
"Parent" => {
|
"Parent" => {
|
||||||
return this.get_parent().into_lua(lua);
|
return this.get_parent().into_lua(lua);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(info) = find_property_info(&this.class_name, &prop_name) {
|
ensure_not_destroyed(this)?;
|
||||||
|
|
||||||
|
if prop_name.as_str() == "Name" {
|
||||||
|
return this.get_name().into_lua(lua);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(info) = find_property_info(this.class_name, &prop_name) {
|
||||||
if let Some(prop) = this.get_property(&prop_name) {
|
if let Some(prop) = this.get_property(&prop_name) {
|
||||||
if let DomValue::Enum(enum_value) = prop {
|
if let DomValue::Enum(enum_value) = prop {
|
||||||
let enum_name = info.enum_name.ok_or_else(|| {
|
let enum_name = info.enum_name.ok_or_else(|| {
|
||||||
|
@ -275,7 +271,7 @@ fn instance_property_get<'lua>(
|
||||||
} else if let Some(inst) = this.find_child(|inst| inst.name == prop_name) {
|
} else if let Some(inst) = this.find_child(|inst| inst.name == prop_name) {
|
||||||
Ok(LuaValue::UserData(lua.create_userdata(inst)?))
|
Ok(LuaValue::UserData(lua.create_userdata(inst)?))
|
||||||
} else if let Some(getter) = InstanceRegistry::find_property_getter(lua, this, &prop_name) {
|
} else if let Some(getter) = InstanceRegistry::find_property_getter(lua, this, &prop_name) {
|
||||||
getter.call(this.clone())
|
getter.call(*this)
|
||||||
} else if let Some(method) = InstanceRegistry::find_method(lua, this, &prop_name) {
|
} else if let Some(method) = InstanceRegistry::find_method(lua, this, &prop_name) {
|
||||||
Ok(LuaValue::Function(method))
|
Ok(LuaValue::Function(method))
|
||||||
} else {
|
} else {
|
||||||
|
@ -295,10 +291,10 @@ fn instance_property_get<'lua>(
|
||||||
2a. Set a strict enum from a given EnumItem OR
|
2a. Set a strict enum from a given EnumItem OR
|
||||||
2b. Set a normal property from a given value
|
2b. Set a normal property from a given value
|
||||||
*/
|
*/
|
||||||
fn instance_property_set<'lua>(
|
fn instance_property_set(
|
||||||
lua: &'lua Lua,
|
lua: &Lua,
|
||||||
this: &mut Instance,
|
this: &mut Instance,
|
||||||
(prop_name, prop_value): (String, LuaValue<'lua>),
|
(prop_name, prop_value): (String, LuaValue),
|
||||||
) -> LuaResult<()> {
|
) -> LuaResult<()> {
|
||||||
ensure_not_destroyed(this)?;
|
ensure_not_destroyed(this)?;
|
||||||
|
|
||||||
|
@ -319,19 +315,19 @@ fn instance_property_set<'lua>(
|
||||||
"Failed to set Parent - DataModel can not be reparented".to_string(),
|
"Failed to set Parent - DataModel can not be reparented".to_string(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
type Parent<'lua> = Option<LuaUserDataRef<'lua, Instance>>;
|
type Parent = Option<LuaUserDataRef<Instance>>;
|
||||||
let parent = Parent::from_lua(prop_value, lua)?;
|
let parent = Parent::from_lua(prop_value, lua)?;
|
||||||
this.set_parent(parent.map(|p| p.clone()));
|
this.set_parent(parent.map(|p| *p));
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(info) = find_property_info(&this.class_name, &prop_name) {
|
if let Some(info) = find_property_info(this.class_name, &prop_name) {
|
||||||
if let Some(enum_name) = info.enum_name {
|
if let Some(enum_name) = info.enum_name {
|
||||||
match LuaUserDataRef::<EnumItem>::from_lua(prop_value, lua) {
|
match LuaUserDataRef::<EnumItem>::from_lua(prop_value, lua) {
|
||||||
Ok(given_enum) if given_enum.parent.desc.name == enum_name => {
|
Ok(given_enum) if given_enum.parent.desc.name == enum_name => {
|
||||||
this.set_property(prop_name, DomValue::Enum((*given_enum).clone().into()));
|
this.set_property(prop_name, DomValue::EnumItem((*given_enum).clone().into()));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Ok(given_enum) => Err(LuaError::RuntimeError(format!(
|
Ok(given_enum) => Err(LuaError::RuntimeError(format!(
|
||||||
|
@ -354,7 +350,7 @@ fn instance_property_set<'lua>(
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
} else if let Some(setter) = InstanceRegistry::find_property_setter(lua, this, &prop_name) {
|
} else if let Some(setter) = InstanceRegistry::find_property_setter(lua, this, &prop_name) {
|
||||||
setter.call((this.clone(), prop_value))
|
setter.call((*this, prop_value))
|
||||||
} else {
|
} else {
|
||||||
Err(LuaError::RuntimeError(format!(
|
Err(LuaError::RuntimeError(format!(
|
||||||
"{prop_name} is not a valid member of {this}",
|
"{prop_name} is not a valid member of {this}",
|
||||||
|
|
|
@ -12,11 +12,11 @@ use super::Instance;
|
||||||
|
|
||||||
pub const CLASS_NAME: &str = "DataModel";
|
pub const CLASS_NAME: &str = "DataModel";
|
||||||
|
|
||||||
pub fn add_fields<'lua, F: LuaUserDataFields<'lua, Instance>>(f: &mut F) {
|
pub fn add_fields<F: LuaUserDataFields<Instance>>(f: &mut F) {
|
||||||
add_class_restricted_getter(f, CLASS_NAME, "Workspace", data_model_get_workspace);
|
add_class_restricted_getter(f, CLASS_NAME, "Workspace", data_model_get_workspace);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_methods<'lua, M: LuaUserDataMethods<'lua, Instance>>(m: &mut M) {
|
pub fn add_methods<M: LuaUserDataMethods<Instance>>(m: &mut M) {
|
||||||
add_class_restricted_method(m, CLASS_NAME, "GetService", data_model_get_service);
|
add_class_restricted_method(m, CLASS_NAME, "GetService", data_model_get_service);
|
||||||
add_class_restricted_method(m, CLASS_NAME, "FindService", data_model_find_service);
|
add_class_restricted_method(m, CLASS_NAME, "FindService", data_model_find_service);
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ fn data_model_get_service(_: &Lua, this: &Instance, service_name: String) -> Lua
|
||||||
Ok(service)
|
Ok(service)
|
||||||
} else {
|
} else {
|
||||||
let service = Instance::new_orphaned(service_name);
|
let service = Instance::new_orphaned(service_name);
|
||||||
service.set_parent(Some(this.clone()));
|
service.set_parent(Some(*this));
|
||||||
Ok(service)
|
Ok(service)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,14 +4,13 @@ use std::{
|
||||||
collections::{BTreeMap, VecDeque},
|
collections::{BTreeMap, VecDeque},
|
||||||
fmt,
|
fmt,
|
||||||
hash::{Hash, Hasher},
|
hash::{Hash, Hasher},
|
||||||
sync::Mutex,
|
sync::{LazyLock, Mutex},
|
||||||
};
|
};
|
||||||
|
|
||||||
use mlua::prelude::*;
|
use mlua::prelude::*;
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
use rbx_dom_weak::{
|
use rbx_dom_weak::{
|
||||||
types::{Attributes as DomAttributes, Ref as DomRef, Variant as DomValue},
|
types::{Attributes as DomAttributes, Ref as DomRef, Variant as DomValue},
|
||||||
Instance as DomInstance, InstanceBuilder as DomInstanceBuilder, WeakDom,
|
ustr, Instance as DomInstance, InstanceBuilder as DomInstanceBuilder, Ustr, WeakDom,
|
||||||
};
|
};
|
||||||
|
|
||||||
use lune_utils::TableBuilder;
|
use lune_utils::TableBuilder;
|
||||||
|
@ -31,13 +30,13 @@ pub mod registry;
|
||||||
const PROPERTY_NAME_ATTRIBUTES: &str = "Attributes";
|
const PROPERTY_NAME_ATTRIBUTES: &str = "Attributes";
|
||||||
const PROPERTY_NAME_TAGS: &str = "Tags";
|
const PROPERTY_NAME_TAGS: &str = "Tags";
|
||||||
|
|
||||||
static INTERNAL_DOM: Lazy<Mutex<WeakDom>> =
|
static INTERNAL_DOM: LazyLock<Mutex<WeakDom>> =
|
||||||
Lazy::new(|| Mutex::new(WeakDom::new(DomInstanceBuilder::new("ROOT"))));
|
LazyLock::new(|| Mutex::new(WeakDom::new(DomInstanceBuilder::new("ROOT"))));
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct Instance {
|
pub struct Instance {
|
||||||
pub(crate) dom_ref: DomRef,
|
pub(crate) dom_ref: DomRef,
|
||||||
pub(crate) class_name: String,
|
pub(crate) class_name: Ustr,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Instance {
|
impl Instance {
|
||||||
|
@ -75,7 +74,7 @@ impl Instance {
|
||||||
|
|
||||||
Some(Self {
|
Some(Self {
|
||||||
dom_ref,
|
dom_ref,
|
||||||
class_name: instance.class.clone(),
|
class_name: instance.class,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -96,14 +95,14 @@ impl Instance {
|
||||||
|
|
||||||
let class_name = class_name.as_ref();
|
let class_name = class_name.as_ref();
|
||||||
|
|
||||||
let instance = DomInstanceBuilder::new(class_name.to_string());
|
let instance = DomInstanceBuilder::new(class_name);
|
||||||
|
|
||||||
let dom_root = dom.root_ref();
|
let dom_root = dom.root_ref();
|
||||||
let dom_ref = dom.insert(dom_root, instance);
|
let dom_ref = dom.insert(dom_root, instance);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
dom_ref,
|
dom_ref,
|
||||||
class_name: class_name.to_string(),
|
class_name: ustr(class_name),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -244,7 +243,7 @@ impl Instance {
|
||||||
on the Roblox Developer Hub
|
on the Roblox Developer Hub
|
||||||
*/
|
*/
|
||||||
pub fn is_a(&self, class_name: impl AsRef<str>) -> bool {
|
pub fn is_a(&self, class_name: impl AsRef<str>) -> bool {
|
||||||
class_is_a(&self.class_name, class_name).unwrap_or(false)
|
class_is_a(self.class_name, class_name).unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -302,10 +301,7 @@ impl Instance {
|
||||||
pub fn get_parent(&self) -> Option<Instance> {
|
pub fn get_parent(&self) -> Option<Instance> {
|
||||||
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||||
|
|
||||||
let parent_ref = dom
|
let parent_ref = dom.get_by_ref(self.dom_ref)?.parent();
|
||||||
.get_by_ref(self.dom_ref)
|
|
||||||
.expect("Failed to find instance in document")
|
|
||||||
.parent();
|
|
||||||
|
|
||||||
if parent_ref == dom.root_ref() {
|
if parent_ref == dom.root_ref() {
|
||||||
None
|
None
|
||||||
|
@ -344,7 +340,7 @@ impl Instance {
|
||||||
.get_by_ref(self.dom_ref)
|
.get_by_ref(self.dom_ref)
|
||||||
.expect("Failed to find instance in document")
|
.expect("Failed to find instance in document")
|
||||||
.properties
|
.properties
|
||||||
.get(name.as_ref())
|
.get(&ustr(name.as_ref()))
|
||||||
.cloned()
|
.cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -361,7 +357,7 @@ impl Instance {
|
||||||
.get_by_ref_mut(self.dom_ref)
|
.get_by_ref_mut(self.dom_ref)
|
||||||
.expect("Failed to find instance in document")
|
.expect("Failed to find instance in document")
|
||||||
.properties
|
.properties
|
||||||
.insert(name.as_ref().to_string(), value);
|
.insert(ustr(name.as_ref()), value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -377,7 +373,7 @@ impl Instance {
|
||||||
.get_by_ref(self.dom_ref)
|
.get_by_ref(self.dom_ref)
|
||||||
.expect("Failed to find instance in document");
|
.expect("Failed to find instance in document");
|
||||||
if let Some(DomValue::Attributes(attributes)) =
|
if let Some(DomValue::Attributes(attributes)) =
|
||||||
inst.properties.get(PROPERTY_NAME_ATTRIBUTES)
|
inst.properties.get(&ustr(PROPERTY_NAME_ATTRIBUTES))
|
||||||
{
|
{
|
||||||
attributes.get(name.as_ref()).cloned()
|
attributes.get(name.as_ref()).cloned()
|
||||||
} else {
|
} else {
|
||||||
|
@ -398,7 +394,7 @@ impl Instance {
|
||||||
.get_by_ref(self.dom_ref)
|
.get_by_ref(self.dom_ref)
|
||||||
.expect("Failed to find instance in document");
|
.expect("Failed to find instance in document");
|
||||||
if let Some(DomValue::Attributes(attributes)) =
|
if let Some(DomValue::Attributes(attributes)) =
|
||||||
inst.properties.get(PROPERTY_NAME_ATTRIBUTES)
|
inst.properties.get(&ustr(PROPERTY_NAME_ATTRIBUTES))
|
||||||
{
|
{
|
||||||
attributes.clone().into_iter().collect()
|
attributes.clone().into_iter().collect()
|
||||||
} else {
|
} else {
|
||||||
|
@ -425,14 +421,14 @@ impl Instance {
|
||||||
value => value,
|
value => value,
|
||||||
};
|
};
|
||||||
if let Some(DomValue::Attributes(attributes)) =
|
if let Some(DomValue::Attributes(attributes)) =
|
||||||
inst.properties.get_mut(PROPERTY_NAME_ATTRIBUTES)
|
inst.properties.get_mut(&ustr(PROPERTY_NAME_ATTRIBUTES))
|
||||||
{
|
{
|
||||||
attributes.insert(name.as_ref().to_string(), value);
|
attributes.insert(name.as_ref().to_string(), value);
|
||||||
} else {
|
} else {
|
||||||
let mut attributes = DomAttributes::new();
|
let mut attributes = DomAttributes::new();
|
||||||
attributes.insert(name.as_ref().to_string(), value);
|
attributes.insert(name.as_ref().to_string(), value);
|
||||||
inst.properties.insert(
|
inst.properties.insert(
|
||||||
PROPERTY_NAME_ATTRIBUTES.to_string(),
|
ustr(PROPERTY_NAME_ATTRIBUTES),
|
||||||
DomValue::Attributes(attributes),
|
DomValue::Attributes(attributes),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -452,11 +448,11 @@ impl Instance {
|
||||||
.get_by_ref_mut(self.dom_ref)
|
.get_by_ref_mut(self.dom_ref)
|
||||||
.expect("Failed to find instance in document");
|
.expect("Failed to find instance in document");
|
||||||
if let Some(DomValue::Attributes(attributes)) =
|
if let Some(DomValue::Attributes(attributes)) =
|
||||||
inst.properties.get_mut(PROPERTY_NAME_ATTRIBUTES)
|
inst.properties.get_mut(&ustr(PROPERTY_NAME_ATTRIBUTES))
|
||||||
{
|
{
|
||||||
attributes.remove(name.as_ref());
|
attributes.remove(name.as_ref());
|
||||||
if attributes.is_empty() {
|
if attributes.is_empty() {
|
||||||
inst.properties.remove(PROPERTY_NAME_ATTRIBUTES);
|
inst.properties.remove(&ustr(PROPERTY_NAME_ATTRIBUTES));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -473,11 +469,11 @@ impl Instance {
|
||||||
let inst = dom
|
let inst = dom
|
||||||
.get_by_ref_mut(self.dom_ref)
|
.get_by_ref_mut(self.dom_ref)
|
||||||
.expect("Failed to find instance in document");
|
.expect("Failed to find instance in document");
|
||||||
if let Some(DomValue::Tags(tags)) = inst.properties.get_mut(PROPERTY_NAME_TAGS) {
|
if let Some(DomValue::Tags(tags)) = inst.properties.get_mut(&ustr(PROPERTY_NAME_TAGS)) {
|
||||||
tags.push(name.as_ref());
|
tags.push(name.as_ref());
|
||||||
} else {
|
} else {
|
||||||
inst.properties.insert(
|
inst.properties.insert(
|
||||||
PROPERTY_NAME_TAGS.to_string(),
|
ustr(PROPERTY_NAME_TAGS),
|
||||||
DomValue::Tags(vec![name.as_ref().to_string()].into()),
|
DomValue::Tags(vec![name.as_ref().to_string()].into()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -495,7 +491,7 @@ impl Instance {
|
||||||
let inst = dom
|
let inst = dom
|
||||||
.get_by_ref(self.dom_ref)
|
.get_by_ref(self.dom_ref)
|
||||||
.expect("Failed to find instance in document");
|
.expect("Failed to find instance in document");
|
||||||
if let Some(DomValue::Tags(tags)) = inst.properties.get(PROPERTY_NAME_TAGS) {
|
if let Some(DomValue::Tags(tags)) = inst.properties.get(&ustr(PROPERTY_NAME_TAGS)) {
|
||||||
tags.iter().map(ToString::to_string).collect()
|
tags.iter().map(ToString::to_string).collect()
|
||||||
} else {
|
} else {
|
||||||
Vec::new()
|
Vec::new()
|
||||||
|
@ -514,7 +510,7 @@ impl Instance {
|
||||||
let inst = dom
|
let inst = dom
|
||||||
.get_by_ref(self.dom_ref)
|
.get_by_ref(self.dom_ref)
|
||||||
.expect("Failed to find instance in document");
|
.expect("Failed to find instance in document");
|
||||||
if let Some(DomValue::Tags(tags)) = inst.properties.get(PROPERTY_NAME_TAGS) {
|
if let Some(DomValue::Tags(tags)) = inst.properties.get(&ustr(PROPERTY_NAME_TAGS)) {
|
||||||
let name = name.as_ref();
|
let name = name.as_ref();
|
||||||
tags.iter().any(|tag| tag == name)
|
tags.iter().any(|tag| tag == name)
|
||||||
} else {
|
} else {
|
||||||
|
@ -534,14 +530,12 @@ impl Instance {
|
||||||
let inst = dom
|
let inst = dom
|
||||||
.get_by_ref_mut(self.dom_ref)
|
.get_by_ref_mut(self.dom_ref)
|
||||||
.expect("Failed to find instance in document");
|
.expect("Failed to find instance in document");
|
||||||
if let Some(DomValue::Tags(tags)) = inst.properties.get_mut(PROPERTY_NAME_TAGS) {
|
if let Some(DomValue::Tags(tags)) = inst.properties.get_mut(&ustr(PROPERTY_NAME_TAGS)) {
|
||||||
let name = name.as_ref();
|
let name = name.as_ref();
|
||||||
let mut new_tags = tags.iter().map(ToString::to_string).collect::<Vec<_>>();
|
let mut new_tags = tags.iter().map(ToString::to_string).collect::<Vec<_>>();
|
||||||
new_tags.retain(|tag| tag != name);
|
new_tags.retain(|tag| tag != name);
|
||||||
inst.properties.insert(
|
inst.properties
|
||||||
PROPERTY_NAME_TAGS.to_string(),
|
.insert(ustr(PROPERTY_NAME_TAGS), DomValue::Tags(new_tags.into()));
|
||||||
DomValue::Tags(new_tags.into()),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -699,8 +693,7 @@ impl Instance {
|
||||||
predicate callback and a breadth-first search.
|
predicate callback and a breadth-first search.
|
||||||
|
|
||||||
### See Also
|
### See Also
|
||||||
* [`FindFirstDescendant`](https://create.roblox.com/docs/reference/engine/classes/Instance#FindFirstDescendant)
|
* [`FindFirstDescendant`](https://create.roblox.com/docs/reference/engine/classes/Instance#FindFirstDescendant) on the Roblox Developer Hub
|
||||||
on the Roblox Developer Hub
|
|
||||||
*/
|
*/
|
||||||
pub fn find_descendant<F>(&self, predicate: F) -> Option<Instance>
|
pub fn find_descendant<F>(&self, predicate: F) -> Option<Instance>
|
||||||
where
|
where
|
||||||
|
@ -730,11 +723,11 @@ impl Instance {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaExportsTable<'_> for Instance {
|
impl LuaExportsTable for Instance {
|
||||||
const EXPORT_NAME: &'static str = "Instance";
|
const EXPORT_NAME: &'static str = "Instance";
|
||||||
|
|
||||||
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
|
fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {
|
||||||
let instance_new = |lua, class_name: String| {
|
let instance_new = |lua: &Lua, class_name: String| {
|
||||||
if class_exists(&class_name) {
|
if class_exists(&class_name) {
|
||||||
Instance::new_orphaned(class_name).into_lua(lua)
|
Instance::new_orphaned(class_name).into_lua(lua)
|
||||||
} else {
|
} else {
|
||||||
|
@ -763,12 +756,12 @@ impl LuaExportsTable<'_> for Instance {
|
||||||
instance registry, and register properties + methods from the lua side
|
instance registry, and register properties + methods from the lua side
|
||||||
*/
|
*/
|
||||||
impl LuaUserData for Instance {
|
impl LuaUserData for Instance {
|
||||||
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
||||||
data_model::add_fields(fields);
|
data_model::add_fields(fields);
|
||||||
workspace::add_fields(fields);
|
workspace::add_fields(fields);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||||
base::add_methods(methods);
|
base::add_methods(methods);
|
||||||
data_model::add_methods(methods);
|
data_model::add_methods(methods);
|
||||||
terrain::add_methods(methods);
|
terrain::add_methods(methods);
|
||||||
|
|
|
@ -58,11 +58,11 @@ impl InstanceRegistry {
|
||||||
|
|
||||||
- If the method already exists in the registry.
|
- If the method already exists in the registry.
|
||||||
*/
|
*/
|
||||||
pub fn insert_method<'lua>(
|
pub fn insert_method(
|
||||||
lua: &'lua Lua,
|
lua: &Lua,
|
||||||
class_name: &str,
|
class_name: &str,
|
||||||
method_name: &str,
|
method_name: &str,
|
||||||
method: LuaFunction<'lua>,
|
method: LuaFunction,
|
||||||
) -> Result<(), InstanceRegistryError> {
|
) -> Result<(), InstanceRegistryError> {
|
||||||
let registry = Self::get_or_create(lua);
|
let registry = Self::get_or_create(lua);
|
||||||
|
|
||||||
|
@ -94,11 +94,11 @@ impl InstanceRegistry {
|
||||||
|
|
||||||
- If the property already exists in the registry.
|
- If the property already exists in the registry.
|
||||||
*/
|
*/
|
||||||
pub fn insert_property_getter<'lua>(
|
pub fn insert_property_getter(
|
||||||
lua: &'lua Lua,
|
lua: &Lua,
|
||||||
class_name: &str,
|
class_name: &str,
|
||||||
property_name: &str,
|
property_name: &str,
|
||||||
property_getter: LuaFunction<'lua>,
|
property_getter: LuaFunction,
|
||||||
) -> Result<(), InstanceRegistryError> {
|
) -> Result<(), InstanceRegistryError> {
|
||||||
let registry = Self::get_or_create(lua);
|
let registry = Self::get_or_create(lua);
|
||||||
|
|
||||||
|
@ -130,11 +130,11 @@ impl InstanceRegistry {
|
||||||
|
|
||||||
- If the property already exists in the registry.
|
- If the property already exists in the registry.
|
||||||
*/
|
*/
|
||||||
pub fn insert_property_setter<'lua>(
|
pub fn insert_property_setter(
|
||||||
lua: &'lua Lua,
|
lua: &Lua,
|
||||||
class_name: &str,
|
class_name: &str,
|
||||||
property_name: &str,
|
property_name: &str,
|
||||||
property_setter: LuaFunction<'lua>,
|
property_setter: LuaFunction,
|
||||||
) -> Result<(), InstanceRegistryError> {
|
) -> Result<(), InstanceRegistryError> {
|
||||||
let registry = Self::get_or_create(lua);
|
let registry = Self::get_or_create(lua);
|
||||||
|
|
||||||
|
@ -165,11 +165,7 @@ impl InstanceRegistry {
|
||||||
Returns `None` if the method is not found.
|
Returns `None` if the method is not found.
|
||||||
*/
|
*/
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn find_method<'lua>(
|
pub fn find_method(lua: &Lua, instance: &Instance, method_name: &str) -> Option<LuaFunction> {
|
||||||
lua: &'lua Lua,
|
|
||||||
instance: &Instance,
|
|
||||||
method_name: &str,
|
|
||||||
) -> Option<LuaFunction<'lua>> {
|
|
||||||
let registry = Self::get_or_create(lua);
|
let registry = Self::get_or_create(lua);
|
||||||
let methods = registry
|
let methods = registry
|
||||||
.methods
|
.methods
|
||||||
|
@ -192,11 +188,11 @@ impl InstanceRegistry {
|
||||||
Returns `None` if the property getter is not found.
|
Returns `None` if the property getter is not found.
|
||||||
*/
|
*/
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn find_property_getter<'lua>(
|
pub fn find_property_getter(
|
||||||
lua: &'lua Lua,
|
lua: &Lua,
|
||||||
instance: &Instance,
|
instance: &Instance,
|
||||||
property_name: &str,
|
property_name: &str,
|
||||||
) -> Option<LuaFunction<'lua>> {
|
) -> Option<LuaFunction> {
|
||||||
let registry = Self::get_or_create(lua);
|
let registry = Self::get_or_create(lua);
|
||||||
let getters = registry
|
let getters = registry
|
||||||
.getters
|
.getters
|
||||||
|
@ -219,11 +215,11 @@ impl InstanceRegistry {
|
||||||
Returns `None` if the property setter is not found.
|
Returns `None` if the property setter is not found.
|
||||||
*/
|
*/
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn find_property_setter<'lua>(
|
pub fn find_property_setter(
|
||||||
lua: &'lua Lua,
|
lua: &Lua,
|
||||||
instance: &Instance,
|
instance: &Instance,
|
||||||
property_name: &str,
|
property_name: &str,
|
||||||
) -> Option<LuaFunction<'lua>> {
|
) -> Option<LuaFunction> {
|
||||||
let registry = Self::get_or_create(lua);
|
let registry = Self::get_or_create(lua);
|
||||||
let setters = registry
|
let setters = registry
|
||||||
.setters
|
.setters
|
||||||
|
|
|
@ -10,7 +10,7 @@ use super::Instance;
|
||||||
|
|
||||||
pub const CLASS_NAME: &str = "Terrain";
|
pub const CLASS_NAME: &str = "Terrain";
|
||||||
|
|
||||||
pub fn add_methods<'lua, M: LuaUserDataMethods<'lua, Instance>>(methods: &mut M) {
|
pub fn add_methods<M: LuaUserDataMethods<Instance>>(methods: &mut M) {
|
||||||
add_class_restricted_method(
|
add_class_restricted_method(
|
||||||
methods,
|
methods,
|
||||||
CLASS_NAME,
|
CLASS_NAME,
|
||||||
|
@ -27,9 +27,8 @@ pub fn add_methods<'lua, M: LuaUserDataMethods<'lua, Instance>>(methods: &mut M)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_or_create_material_colors(instance: &Instance) -> MaterialColors {
|
fn get_or_create_material_colors(instance: &Instance) -> MaterialColors {
|
||||||
if let Some(Variant::MaterialColors(material_colors)) = instance.get_property("MaterialColors")
|
if let Some(Variant::MaterialColors(inner)) = instance.get_property("MaterialColors") {
|
||||||
{
|
inner
|
||||||
material_colors
|
|
||||||
} else {
|
} else {
|
||||||
MaterialColors::default()
|
MaterialColors::default()
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ use super::Instance;
|
||||||
|
|
||||||
pub const CLASS_NAME: &str = "Workspace";
|
pub const CLASS_NAME: &str = "Workspace";
|
||||||
|
|
||||||
pub fn add_fields<'lua, F: LuaUserDataFields<'lua, Instance>>(f: &mut F) {
|
pub fn add_fields<F: LuaUserDataFields<Instance>>(f: &mut F) {
|
||||||
add_class_restricted_getter(f, CLASS_NAME, "Terrain", workspace_get_terrain);
|
add_class_restricted_getter(f, CLASS_NAME, "Terrain", workspace_get_terrain);
|
||||||
add_class_restricted_getter(f, CLASS_NAME, "CurrentCamera", workspace_get_camera);
|
add_class_restricted_getter(f, CLASS_NAME, "CurrentCamera", workspace_get_camera);
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,37 +14,38 @@ pub(crate) mod shared;
|
||||||
|
|
||||||
use exports::export;
|
use exports::export;
|
||||||
|
|
||||||
fn create_all_exports(lua: &Lua) -> LuaResult<Vec<(&'static str, LuaValue)>> {
|
fn create_all_exports(lua: Lua) -> LuaResult<Vec<(&'static str, LuaValue)>> {
|
||||||
use datatypes::types::*;
|
use datatypes::types::*;
|
||||||
use instance::Instance;
|
use instance::Instance;
|
||||||
Ok(vec![
|
Ok(vec![
|
||||||
// Datatypes
|
// Datatypes
|
||||||
export::<Axes>(lua)?,
|
export::<Axes>(lua.clone())?,
|
||||||
export::<BrickColor>(lua)?,
|
export::<BrickColor>(lua.clone())?,
|
||||||
export::<CFrame>(lua)?,
|
export::<CFrame>(lua.clone())?,
|
||||||
export::<Color3>(lua)?,
|
export::<Color3>(lua.clone())?,
|
||||||
export::<ColorSequence>(lua)?,
|
export::<ColorSequence>(lua.clone())?,
|
||||||
export::<ColorSequenceKeypoint>(lua)?,
|
export::<ColorSequenceKeypoint>(lua.clone())?,
|
||||||
export::<Faces>(lua)?,
|
export::<Content>(lua.clone())?,
|
||||||
export::<Font>(lua)?,
|
export::<Faces>(lua.clone())?,
|
||||||
export::<NumberRange>(lua)?,
|
export::<Font>(lua.clone())?,
|
||||||
export::<NumberSequence>(lua)?,
|
export::<NumberRange>(lua.clone())?,
|
||||||
export::<NumberSequenceKeypoint>(lua)?,
|
export::<NumberSequence>(lua.clone())?,
|
||||||
export::<PhysicalProperties>(lua)?,
|
export::<NumberSequenceKeypoint>(lua.clone())?,
|
||||||
export::<Ray>(lua)?,
|
export::<PhysicalProperties>(lua.clone())?,
|
||||||
export::<Rect>(lua)?,
|
export::<Ray>(lua.clone())?,
|
||||||
export::<UDim>(lua)?,
|
export::<Rect>(lua.clone())?,
|
||||||
export::<UDim2>(lua)?,
|
export::<UDim>(lua.clone())?,
|
||||||
export::<Region3>(lua)?,
|
export::<UDim2>(lua.clone())?,
|
||||||
export::<Region3int16>(lua)?,
|
export::<Region3>(lua.clone())?,
|
||||||
export::<Vector2>(lua)?,
|
export::<Region3int16>(lua.clone())?,
|
||||||
export::<Vector2int16>(lua)?,
|
export::<Vector2>(lua.clone())?,
|
||||||
export::<Vector3>(lua)?,
|
export::<Vector2int16>(lua.clone())?,
|
||||||
export::<Vector3int16>(lua)?,
|
export::<Vector3>(lua.clone())?,
|
||||||
|
export::<Vector3int16>(lua.clone())?,
|
||||||
// Classes
|
// Classes
|
||||||
export::<Instance>(lua)?,
|
export::<Instance>(lua.clone())?,
|
||||||
// Singletons
|
// Singletons
|
||||||
("Enum", Enums.into_lua(lua)?),
|
("Enum", Enums.into_lua(&lua)?),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,13 +59,13 @@ fn create_all_exports(lua: &Lua) -> LuaResult<Vec<(&'static str, LuaValue)>> {
|
||||||
|
|
||||||
Errors when out of memory or when a value cannot be created.
|
Errors when out of memory or when a value cannot be created.
|
||||||
*/
|
*/
|
||||||
pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
|
pub fn module(lua: Lua) -> LuaResult<LuaTable> {
|
||||||
// FUTURE: We can probably create these lazily as users
|
// FUTURE: We can probably create these lazily as users
|
||||||
// index the main exports (this return value) table and
|
// index the main exports (this return value) table and
|
||||||
// save some memory and startup time. The full exports
|
// save some memory and startup time. The full exports
|
||||||
// table is quite big and probably won't get any smaller
|
// table is quite big and probably won't get any smaller
|
||||||
// since we impl all roblox constructors for each datatype.
|
// since we impl all roblox constructors for each datatype.
|
||||||
let exports = create_all_exports(lua)?;
|
let exports = create_all_exports(lua.clone())?;
|
||||||
TableBuilder::new(lua)?
|
TableBuilder::new(lua)?
|
||||||
.with_values(exports)?
|
.with_values(exports)?
|
||||||
.build_readonly()
|
.build_readonly()
|
||||||
|
|
|
@ -85,7 +85,7 @@ impl DatabaseClass {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaUserData for DatabaseClass {
|
impl LuaUserData for DatabaseClass {
|
||||||
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
||||||
fields.add_field_method_get("Name", |_, this| Ok(this.get_name()));
|
fields.add_field_method_get("Name", |_, this| Ok(this.get_name()));
|
||||||
fields.add_field_method_get("Superclass", |_, this| Ok(this.get_superclass()));
|
fields.add_field_method_get("Superclass", |_, this| Ok(this.get_superclass()));
|
||||||
fields.add_field_method_get("Properties", |_, this| Ok(this.get_properties()));
|
fields.add_field_method_get("Properties", |_, this| Ok(this.get_properties()));
|
||||||
|
@ -108,7 +108,7 @@ impl LuaUserData for DatabaseClass {
|
||||||
fields.add_field_method_get("Tags", |_, this| Ok(this.get_tags_str()));
|
fields.add_field_method_get("Tags", |_, this| Ok(this.get_tags_str()));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||||
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
||||||
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,12 +45,12 @@ impl DatabaseEnum {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaUserData for DatabaseEnum {
|
impl LuaUserData for DatabaseEnum {
|
||||||
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
||||||
fields.add_field_method_get("Name", |_, this| Ok(this.get_name()));
|
fields.add_field_method_get("Name", |_, this| Ok(this.get_name()));
|
||||||
fields.add_field_method_get("Items", |_, this| Ok(this.get_items()));
|
fields.add_field_method_get("Items", |_, this| Ok(this.get_items()));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||||
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
||||||
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,11 +111,11 @@ impl Database {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaUserData for Database {
|
impl LuaUserData for Database {
|
||||||
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
fn add_fields<F: LuaUserDataFields<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) {
|
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||||
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
||||||
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
||||||
methods.add_method("GetEnumNames", |_, this, (): ()| Ok(this.get_enum_names()));
|
methods.add_method("GetEnumNames", |_, this, (): ()| Ok(this.get_enum_names()));
|
||||||
|
|
|
@ -69,14 +69,14 @@ impl DatabaseProperty {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaUserData for DatabaseProperty {
|
impl LuaUserData for DatabaseProperty {
|
||||||
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
||||||
fields.add_field_method_get("Name", |_, this| Ok(this.get_name()));
|
fields.add_field_method_get("Name", |_, this| Ok(this.get_name()));
|
||||||
fields.add_field_method_get("Datatype", |_, this| Ok(this.get_datatype_name()));
|
fields.add_field_method_get("Datatype", |_, this| Ok(this.get_datatype_name()));
|
||||||
fields.add_field_method_get("Scriptability", |_, this| Ok(this.get_scriptability_str()));
|
fields.add_field_method_get("Scriptability", |_, this| Ok(this.get_scriptability_str()));
|
||||||
fields.add_field_method_get("Tags", |_, this| Ok(this.get_tags_str()));
|
fields.add_field_method_get("Tags", |_, this| Ok(this.get_tags_str()));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||||
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
||||||
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,14 +6,14 @@ use crate::instance::Instance;
|
||||||
|
|
||||||
use super::instance::class_is_a;
|
use super::instance::class_is_a;
|
||||||
|
|
||||||
pub(crate) fn add_class_restricted_getter<'lua, F: LuaUserDataFields<'lua, Instance>, R, G>(
|
pub(crate) fn add_class_restricted_getter<F: LuaUserDataFields<Instance>, R, G>(
|
||||||
fields: &mut F,
|
fields: &mut F,
|
||||||
class_name: &'static str,
|
class_name: &'static str,
|
||||||
field_name: &'static str,
|
field_name: &'static str,
|
||||||
field_getter: G,
|
field_getter: G,
|
||||||
) where
|
) where
|
||||||
R: IntoLua<'lua>,
|
R: IntoLua,
|
||||||
G: 'static + Fn(&'lua Lua, &Instance) -> LuaResult<R>,
|
G: 'static + Fn(&Lua, &Instance) -> LuaResult<R>,
|
||||||
{
|
{
|
||||||
fields.add_field_method_get(field_name, move |lua, this| {
|
fields.add_field_method_get(field_name, move |lua, this| {
|
||||||
if class_is_a(this.get_class_name(), class_name).unwrap_or(false) {
|
if class_is_a(this.get_class_name(), class_name).unwrap_or(false) {
|
||||||
|
@ -27,14 +27,14 @@ pub(crate) fn add_class_restricted_getter<'lua, F: LuaUserDataFields<'lua, Insta
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub(crate) fn add_class_restricted_setter<'lua, F: LuaUserDataFields<'lua, Instance>, A, G>(
|
pub(crate) fn add_class_restricted_setter<F: LuaUserDataFields<Instance>, A, G>(
|
||||||
fields: &mut F,
|
fields: &mut F,
|
||||||
class_name: &'static str,
|
class_name: &'static str,
|
||||||
field_name: &'static str,
|
field_name: &'static str,
|
||||||
field_getter: G,
|
field_getter: G,
|
||||||
) where
|
) where
|
||||||
A: FromLua<'lua>,
|
A: FromLua,
|
||||||
G: 'static + Fn(&'lua Lua, &Instance, A) -> LuaResult<()>,
|
G: 'static + Fn(&Lua, &Instance, A) -> LuaResult<()>,
|
||||||
{
|
{
|
||||||
fields.add_field_method_set(field_name, move |lua, this, value| {
|
fields.add_field_method_set(field_name, move |lua, this, value| {
|
||||||
if class_is_a(this.get_class_name(), class_name).unwrap_or(false) {
|
if class_is_a(this.get_class_name(), class_name).unwrap_or(false) {
|
||||||
|
@ -47,15 +47,15 @@ pub(crate) fn add_class_restricted_setter<'lua, F: LuaUserDataFields<'lua, Insta
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn add_class_restricted_method<'lua, M: LuaUserDataMethods<'lua, Instance>, A, R, F>(
|
pub(crate) fn add_class_restricted_method<M: LuaUserDataMethods<Instance>, A, R, F>(
|
||||||
methods: &mut M,
|
methods: &mut M,
|
||||||
class_name: &'static str,
|
class_name: &'static str,
|
||||||
method_name: &'static str,
|
method_name: &'static str,
|
||||||
method: F,
|
method: F,
|
||||||
) where
|
) where
|
||||||
A: FromLuaMulti<'lua>,
|
A: FromLuaMulti,
|
||||||
R: IntoLuaMulti<'lua>,
|
R: IntoLuaMulti,
|
||||||
F: 'static + Fn(&'lua Lua, &Instance, A) -> LuaResult<R>,
|
F: 'static + Fn(&Lua, &Instance, A) -> LuaResult<R>,
|
||||||
{
|
{
|
||||||
methods.add_method(method_name, move |lua, this, args| {
|
methods.add_method(method_name, move |lua, this, args| {
|
||||||
if class_is_a(this.get_class_name(), class_name).unwrap_or(false) {
|
if class_is_a(this.get_class_name(), class_name).unwrap_or(false) {
|
||||||
|
@ -68,21 +68,15 @@ pub(crate) fn add_class_restricted_method<'lua, M: LuaUserDataMethods<'lua, Inst
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn add_class_restricted_method_mut<
|
pub(crate) fn add_class_restricted_method_mut<M: LuaUserDataMethods<Instance>, A, R, F>(
|
||||||
'lua,
|
|
||||||
M: LuaUserDataMethods<'lua, Instance>,
|
|
||||||
A,
|
|
||||||
R,
|
|
||||||
F,
|
|
||||||
>(
|
|
||||||
methods: &mut M,
|
methods: &mut M,
|
||||||
class_name: &'static str,
|
class_name: &'static str,
|
||||||
method_name: &'static str,
|
method_name: &'static str,
|
||||||
method: F,
|
method: F,
|
||||||
) where
|
) where
|
||||||
A: FromLuaMulti<'lua>,
|
A: FromLuaMulti,
|
||||||
R: IntoLuaMulti<'lua>,
|
R: IntoLuaMulti,
|
||||||
F: 'static + Fn(&'lua Lua, &mut Instance, A) -> LuaResult<R>,
|
F: 'static + Fn(&Lua, &mut Instance, A) -> LuaResult<R>,
|
||||||
{
|
{
|
||||||
methods.add_method_mut(method_name, move |lua, this, args| {
|
methods.add_method_mut(method_name, move |lua, this, args| {
|
||||||
if class_is_a(this.get_class_name(), class_name).unwrap_or(false) {
|
if class_is_a(this.get_class_name(), class_name).unwrap_or(false) {
|
||||||
|
@ -122,7 +116,7 @@ pub(crate) fn get_or_create_property_ref_instance(
|
||||||
Ok(inst)
|
Ok(inst)
|
||||||
} else {
|
} else {
|
||||||
let inst = Instance::new_orphaned(class_name);
|
let inst = Instance::new_orphaned(class_name);
|
||||||
inst.set_parent(Some(this.clone()));
|
inst.set_parent(Some(*this));
|
||||||
this.set_property(prop_name, DomValue::Ref(inst.dom_ref));
|
this.set_property(prop_name, DomValue::Ref(inst.dom_ref));
|
||||||
Ok(inst)
|
Ok(inst)
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ pub fn make_list_writer() -> Box<ListWriter> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
Userdata metamethod implementations
|
Userdata metamethod implementations
|
||||||
|
|
||||||
Note that many of these return [`LuaResult`] even though they don't
|
Note that many of these return [`LuaResult`] even though they don't
|
||||||
|
@ -87,10 +87,10 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
}
|
||||||
Err(LuaError::FromLuaConversionError {
|
Err(LuaError::FromLuaConversionError {
|
||||||
from: rhs.type_name(),
|
from: rhs.type_name(),
|
||||||
to: type_name::<D>(),
|
to: type_name::<D>().to_string(),
|
||||||
message: Some(format!(
|
message: Some(format!(
|
||||||
"Expected {} or number, got {}",
|
"Expected {} or number, got {}",
|
||||||
type_name::<D>(),
|
type_name::<D>(),
|
||||||
|
@ -112,10 +112,10 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
}
|
||||||
Err(LuaError::FromLuaConversionError {
|
Err(LuaError::FromLuaConversionError {
|
||||||
from: rhs.type_name(),
|
from: rhs.type_name(),
|
||||||
to: type_name::<D>(),
|
to: type_name::<D>().to_string(),
|
||||||
message: Some(format!(
|
message: Some(format!(
|
||||||
"Expected {} or number, got {}",
|
"Expected {} or number, got {}",
|
||||||
type_name::<D>(),
|
type_name::<D>(),
|
||||||
|
@ -137,10 +137,10 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
}
|
||||||
Err(LuaError::FromLuaConversionError {
|
Err(LuaError::FromLuaConversionError {
|
||||||
from: rhs.type_name(),
|
from: rhs.type_name(),
|
||||||
to: type_name::<D>(),
|
to: type_name::<D>().to_string(),
|
||||||
message: Some(format!(
|
message: Some(format!(
|
||||||
"Expected {} or number, got {}",
|
"Expected {} or number, got {}",
|
||||||
type_name::<D>(),
|
type_name::<D>(),
|
||||||
|
@ -168,10 +168,10 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
}
|
||||||
Err(LuaError::FromLuaConversionError {
|
Err(LuaError::FromLuaConversionError {
|
||||||
from: rhs.type_name(),
|
from: rhs.type_name(),
|
||||||
to: type_name::<D>(),
|
to: type_name::<D>().to_string(),
|
||||||
message: Some(format!(
|
message: Some(format!(
|
||||||
"Expected {} or number, got {}",
|
"Expected {} or number, got {}",
|
||||||
type_name::<D>(),
|
type_name::<D>(),
|
||||||
|
@ -193,10 +193,10 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
};
|
}
|
||||||
Err(LuaError::FromLuaConversionError {
|
Err(LuaError::FromLuaConversionError {
|
||||||
from: rhs.type_name(),
|
from: rhs.type_name(),
|
||||||
to: type_name::<D>(),
|
to: type_name::<D>().to_string(),
|
||||||
message: Some(format!(
|
message: Some(format!(
|
||||||
"Expected {} or number, got {}",
|
"Expected {} or number, got {}",
|
||||||
type_name::<D>(),
|
type_name::<D>(),
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "lune-std-datetime"
|
name = "lune-std-datetime"
|
||||||
version = "0.1.3"
|
version = "0.2.2"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
repository = "https://github.com/lune-org/lune"
|
repository = "https://github.com/lune-org/lune"
|
||||||
|
@ -13,10 +13,10 @@ path = "src/lib.rs"
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
mlua = { version = "0.9.9", features = ["luau"] }
|
mlua = { version = "0.10.3", features = ["luau"] }
|
||||||
|
|
||||||
thiserror = "1.0"
|
thiserror = "2.0"
|
||||||
chrono = "0.4.38"
|
chrono = "0.4.38"
|
||||||
chrono_lc = "0.1.6"
|
chrono_lc = "0.1.6"
|
||||||
|
|
||||||
lune-utils = { version = "0.1.3", path = "../lune-utils" }
|
lune-utils = { version = "0.2.2", path = "../lune-utils" }
|
||||||
|
|
|
@ -159,7 +159,7 @@ impl DateTime {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Parses a time string in the ISO 8601 format, such as
|
Parses a time string in the RFC 3339 format, such as
|
||||||
`1996-12-19T16:39:57-08:00`, into a new `DateTime` struct.
|
`1996-12-19T16:39:57-08:00`, into a new `DateTime` struct.
|
||||||
|
|
||||||
See [`chrono::DateTime::parse_from_rfc3339`] for additional details.
|
See [`chrono::DateTime::parse_from_rfc3339`] for additional details.
|
||||||
|
@ -168,8 +168,23 @@ impl DateTime {
|
||||||
|
|
||||||
Returns an error if the input string is not a valid RFC 3339 date-time.
|
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> {
|
pub fn from_rfc_3339(date: impl AsRef<str>) -> DateTimeResult<Self> {
|
||||||
let inner = ChronoDateTime::parse_from_rfc3339(iso_date.as_ref())?.with_timezone(&Utc);
|
let inner = ChronoDateTime::parse_from_rfc3339(date.as_ref())?.with_timezone(&Utc);
|
||||||
|
Ok(Self { inner })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Parses a time string in the RFC 2822 format, such as
|
||||||
|
`Tue, 1 Jul 2003 10:52:37 +0200`, into a new `DateTime` struct.
|
||||||
|
|
||||||
|
See [`chrono::DateTime::parse_from_rfc2822`] for additional details.
|
||||||
|
|
||||||
|
# Errors
|
||||||
|
|
||||||
|
Returns an error if the input string is not a valid RFC 2822 date-time.
|
||||||
|
*/
|
||||||
|
pub fn from_rfc_2822(date: impl AsRef<str>) -> DateTimeResult<Self> {
|
||||||
|
let inner = ChronoDateTime::parse_from_rfc2822(date.as_ref())?.with_timezone(&Utc);
|
||||||
Ok(Self { inner })
|
Ok(Self { inner })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,25 +207,35 @@ impl DateTime {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Formats a time string in the ISO 8601 format, such as `1996-12-19T16:39:57-08:00`.
|
Formats a time string in the RFC 3339 format, such as `1996-12-19T16:39:57-08:00`.
|
||||||
|
|
||||||
See [`chrono::DateTime::to_rfc3339`] for additional details.
|
See [`chrono::DateTime::to_rfc3339`] for additional details.
|
||||||
*/
|
*/
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn to_iso_date(self) -> String {
|
pub fn to_rfc_3339(self) -> String {
|
||||||
self.inner.to_rfc3339()
|
self.inner.to_rfc3339()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Formats a time string in the RFC 2822 format, such as `Tue, 1 Jul 2003 10:52:37 +0200`.
|
||||||
|
|
||||||
|
See [`chrono::DateTime::to_rfc2822`] for additional details.
|
||||||
|
*/
|
||||||
|
#[must_use]
|
||||||
|
pub fn to_rfc_2822(self) -> String {
|
||||||
|
self.inner.to_rfc2822()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LuaUserData for DateTime {
|
impl LuaUserData for DateTime {
|
||||||
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
||||||
fields.add_field_method_get("unixTimestamp", |_, this| Ok(this.inner.timestamp()));
|
fields.add_field_method_get("unixTimestamp", |_, this| Ok(this.inner.timestamp()));
|
||||||
fields.add_field_method_get("unixTimestampMillis", |_, this| {
|
fields.add_field_method_get("unixTimestampMillis", |_, this| {
|
||||||
Ok(this.inner.timestamp_millis())
|
Ok(this.inner.timestamp_millis())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||||
// Metamethods to compare DateTime as instants in time
|
// Metamethods to compare DateTime as instants in time
|
||||||
methods.add_meta_method(
|
methods.add_meta_method(
|
||||||
LuaMetaMethod::Eq,
|
LuaMetaMethod::Eq,
|
||||||
|
@ -229,7 +254,9 @@ impl LuaUserData for DateTime {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
// Normal methods
|
// Normal methods
|
||||||
methods.add_method("toIsoDate", |_, this, ()| Ok(this.to_iso_date()));
|
methods.add_method("toIsoDate", |_, this, ()| Ok(this.to_rfc_3339())); // FUTURE: Remove this rfc3339 alias method
|
||||||
|
methods.add_method("toRfc3339", |_, this, ()| Ok(this.to_rfc_3339()));
|
||||||
|
methods.add_method("toRfc2822", |_, this, ()| Ok(this.to_rfc_2822()));
|
||||||
methods.add_method(
|
methods.add_method(
|
||||||
"formatUniversalTime",
|
"formatUniversalTime",
|
||||||
|_, this, (format, locale): (Option<String>, Option<String>)| {
|
|_, this, (format, locale): (Option<String>, Option<String>)| {
|
||||||
|
|
|
@ -10,6 +10,16 @@ mod values;
|
||||||
|
|
||||||
pub use self::date_time::DateTime;
|
pub use self::date_time::DateTime;
|
||||||
|
|
||||||
|
const TYPEDEFS: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/types.d.luau"));
|
||||||
|
|
||||||
|
/**
|
||||||
|
Returns a string containing type definitions for the `datetime` standard library.
|
||||||
|
*/
|
||||||
|
#[must_use]
|
||||||
|
pub fn typedefs() -> String {
|
||||||
|
TYPEDEFS.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Creates the `datetime` standard library module.
|
Creates the `datetime` standard library module.
|
||||||
|
|
||||||
|
@ -17,10 +27,16 @@ pub use self::date_time::DateTime;
|
||||||
|
|
||||||
Errors when out of memory.
|
Errors when out of memory.
|
||||||
*/
|
*/
|
||||||
pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
|
pub fn module(lua: Lua) -> LuaResult<LuaTable> {
|
||||||
TableBuilder::new(lua)?
|
TableBuilder::new(lua)?
|
||||||
.with_function("fromIsoDate", |_, iso_date: String| {
|
.with_function("fromIsoDate", |_, date: String| {
|
||||||
Ok(DateTime::from_iso_date(iso_date)?)
|
Ok(DateTime::from_rfc_3339(date)?) // FUTURE: Remove this rfc3339 alias method
|
||||||
|
})?
|
||||||
|
.with_function("fromRfc3339", |_, date: String| {
|
||||||
|
Ok(DateTime::from_rfc_3339(date)?)
|
||||||
|
})?
|
||||||
|
.with_function("fromRfc2822", |_, date: String| {
|
||||||
|
Ok(DateTime::from_rfc_2822(date)?)
|
||||||
})?
|
})?
|
||||||
.with_function("fromLocalTime", |_, values| {
|
.with_function("fromLocalTime", |_, values| {
|
||||||
Ok(DateTime::from_local_time(&values)?)
|
Ok(DateTime::from_local_time(&values)?)
|
||||||
|
|
|
@ -60,7 +60,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
Conversion methods between `DateTimeValues` and plain lua tables
|
Conversion methods between `DateTimeValues` and plain lua tables
|
||||||
|
|
||||||
Note that the `IntoLua` implementation here uses a read-only table,
|
Note that the `IntoLua` implementation here uses a read-only table,
|
||||||
|
@ -68,15 +68,15 @@ where
|
||||||
a fixed point in time, and we guarantee that it doesn't change
|
a fixed point in time, and we guarantee that it doesn't change
|
||||||
*/
|
*/
|
||||||
|
|
||||||
impl FromLua<'_> for DateTimeValues {
|
impl FromLua for DateTimeValues {
|
||||||
fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
|
fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
|
||||||
if !value.is_table() {
|
if !value.is_table() {
|
||||||
return Err(LuaError::FromLuaConversionError {
|
return Err(LuaError::FromLuaConversionError {
|
||||||
from: value.type_name(),
|
from: value.type_name(),
|
||||||
to: "DateTimeValues",
|
to: "DateTimeValues".to_string(),
|
||||||
message: Some("value must be a table".to_string()),
|
message: Some("value must be a table".to_string()),
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
let value = value.as_table().unwrap();
|
let value = value.as_table().unwrap();
|
||||||
let values = Self {
|
let values = Self {
|
||||||
|
@ -93,16 +93,16 @@ impl FromLua<'_> for DateTimeValues {
|
||||||
Ok(dt) => Ok(dt),
|
Ok(dt) => Ok(dt),
|
||||||
Err(e) => Err(LuaError::FromLuaConversionError {
|
Err(e) => Err(LuaError::FromLuaConversionError {
|
||||||
from: "table",
|
from: "table",
|
||||||
to: "DateTimeValues",
|
to: "DateTimeValues".to_string(),
|
||||||
message: Some(e.to_string()),
|
message: Some(e.to_string()),
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoLua<'_> for DateTimeValues {
|
impl IntoLua for DateTimeValues {
|
||||||
fn into_lua(self, lua: &Lua) -> LuaResult<LuaValue> {
|
fn into_lua(self, lua: &Lua) -> LuaResult<LuaValue> {
|
||||||
let tab = TableBuilder::new(lua)?
|
let tab = TableBuilder::new(lua.clone())?
|
||||||
.with_value("year", self.year)?
|
.with_value("year", self.year)?
|
||||||
.with_values(vec![
|
.with_values(vec![
|
||||||
("month", self.month),
|
("month", self.month),
|
||||||
|
@ -117,7 +117,7 @@ impl IntoLua<'_> for DateTimeValues {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*
|
||||||
Conversion methods between chrono's timezone-aware `DateTime` to
|
Conversion methods between chrono's timezone-aware `DateTime` to
|
||||||
and from our non-timezone-aware `DateTimeValues` values struct
|
and from our non-timezone-aware `DateTimeValues` values struct
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -87,10 +87,19 @@ export type DateTimeValueArguments = DateTimeValues & OptionalMillisecond
|
||||||
]=]
|
]=]
|
||||||
export type DateTimeValueReturns = DateTimeValues & Millisecond
|
export type DateTimeValueReturns = DateTimeValues & Millisecond
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
@prop unixTimestamp number
|
||||||
|
@within DateTime
|
||||||
|
Number of seconds passed since the UNIX epoch.
|
||||||
|
]=]
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
@prop unixTimestampMillis number
|
||||||
|
@within DateTime
|
||||||
|
Number of milliseconds passed since the UNIX epoch.
|
||||||
|
]=]
|
||||||
local DateTime = {
|
local DateTime = {
|
||||||
--- Number of seconds passed since the UNIX epoch.
|
|
||||||
unixTimestamp = (nil :: any) :: number,
|
unixTimestamp = (nil :: any) :: number,
|
||||||
--- Number of milliseconds passed since the UNIX epoch.
|
|
||||||
unixTimestampMillis = (nil :: any) :: number,
|
unixTimestampMillis = (nil :: any) :: number,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,11 +163,7 @@ end
|
||||||
@param locale -- The locale the time should be formatted in
|
@param locale -- The locale the time should be formatted in
|
||||||
@return string -- The formatting string
|
@return string -- The formatting string
|
||||||
]=]
|
]=]
|
||||||
function DateTime.formatUniversalTime(
|
function DateTime.formatUniversalTime(self: DateTime, formatString: string?, locale: Locale?): string
|
||||||
self: DateTime,
|
|
||||||
formatString: string?,
|
|
||||||
locale: Locale?
|
|
||||||
): string
|
|
||||||
return nil :: any
|
return nil :: any
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -166,6 +171,8 @@ end
|
||||||
@within DateTime
|
@within DateTime
|
||||||
@tag Method
|
@tag Method
|
||||||
|
|
||||||
|
**DEPRECATED**: Use `DateTime.toRfc3339` instead.
|
||||||
|
|
||||||
Formats this `DateTime` as an ISO 8601 date-time string.
|
Formats this `DateTime` as an ISO 8601 date-time string.
|
||||||
|
|
||||||
Some examples of ISO 8601 date-time strings are:
|
Some examples of ISO 8601 date-time strings are:
|
||||||
|
@ -180,6 +187,42 @@ function DateTime.toIsoDate(self: DateTime): string
|
||||||
return nil :: any
|
return nil :: any
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
@within DateTime
|
||||||
|
@tag Method
|
||||||
|
|
||||||
|
Formats this `DateTime` as an RFC 2822 date-time string.
|
||||||
|
|
||||||
|
Some examples of RFC 2822 date-time strings are:
|
||||||
|
|
||||||
|
- `Fri, 21 Nov 1997 09:55:06 -0600`
|
||||||
|
- `Tue, 1 Jul 2003 10:52:37 +0200`
|
||||||
|
- `Mon, 23 Dec 2024 01:58:48 GMT`
|
||||||
|
|
||||||
|
@return string -- The RFC 2822 formatted string
|
||||||
|
]=]
|
||||||
|
function DateTime.toRfc2822(self: DateTime): string
|
||||||
|
return nil :: any
|
||||||
|
end
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
@within DateTime
|
||||||
|
@tag Method
|
||||||
|
|
||||||
|
Formats this `DateTime` as an RFC 3339 date-time string.
|
||||||
|
|
||||||
|
Some examples of RFC 3339 date-time strings are:
|
||||||
|
|
||||||
|
- `2020-02-22T18:12:08Z`
|
||||||
|
- `2000-01-31T12:34:56+05:00`
|
||||||
|
- `1970-01-01T00:00:00.055Z`
|
||||||
|
|
||||||
|
@return string -- The RFC 3339 formatted string
|
||||||
|
]=]
|
||||||
|
function DateTime.toRfc3339(self: DateTime): string
|
||||||
|
return nil :: any
|
||||||
|
end
|
||||||
|
|
||||||
--[=[
|
--[=[
|
||||||
@within DateTime
|
@within DateTime
|
||||||
@tag Method
|
@tag Method
|
||||||
|
@ -243,8 +286,11 @@ export type DateTime = typeof(DateTime)
|
||||||
-- Creates a DateTime for the current exact moment in time
|
-- Creates a DateTime for the current exact moment in time
|
||||||
local now = DateTime.now()
|
local now = DateTime.now()
|
||||||
|
|
||||||
-- Formats the current moment in time as an ISO 8601 string
|
-- Formats the current moment in time as an RFC 3339 string
|
||||||
print(now:toIsoDate())
|
print(now:toRfc3339())
|
||||||
|
|
||||||
|
-- Formats the current moment in time as an RFC 2822 string
|
||||||
|
print(now:toRfc2822())
|
||||||
|
|
||||||
-- Formats the current moment in time, using the local
|
-- Formats the current moment in time, using the local
|
||||||
-- time, the French locale, and the specified time string
|
-- time, the French locale, and the specified time string
|
||||||
|
@ -385,6 +431,8 @@ end
|
||||||
@within DateTime
|
@within DateTime
|
||||||
@tag Constructor
|
@tag Constructor
|
||||||
|
|
||||||
|
**DEPRECATED**: Use `DateTime.fromRfc3339` instead.
|
||||||
|
|
||||||
Creates a new `DateTime` from an ISO 8601 date-time string.
|
Creates a new `DateTime` from an ISO 8601 date-time string.
|
||||||
|
|
||||||
### Errors
|
### Errors
|
||||||
|
@ -405,4 +453,52 @@ function dateTime.fromIsoDate(isoDate: string): DateTime
|
||||||
return nil :: any
|
return nil :: any
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
@within DateTime
|
||||||
|
@tag Constructor
|
||||||
|
|
||||||
|
Creates a new `DateTime` from an RFC 3339 date-time string.
|
||||||
|
|
||||||
|
### Errors
|
||||||
|
|
||||||
|
This constructor is fallible and may throw an error if the given
|
||||||
|
string does not strictly follow the RFC 3339 date-time string format.
|
||||||
|
|
||||||
|
Some examples of valid RFC 3339 date-time strings are:
|
||||||
|
|
||||||
|
- `2020-02-22T18:12:08Z`
|
||||||
|
- `2000-01-31T12:34:56+05:00`
|
||||||
|
- `1970-01-01T00:00:00.055Z`
|
||||||
|
|
||||||
|
@param rfc3339Date -- An RFC 3339 formatted string
|
||||||
|
@return DateTime -- The new DateTime object
|
||||||
|
]=]
|
||||||
|
function dateTime.fromRfc3339(rfc3339Date: string): DateTime
|
||||||
|
return nil :: any
|
||||||
|
end
|
||||||
|
|
||||||
|
--[=[
|
||||||
|
@within DateTime
|
||||||
|
@tag Constructor
|
||||||
|
|
||||||
|
Creates a new `DateTime` from an RFC 2822 date-time string.
|
||||||
|
|
||||||
|
### Errors
|
||||||
|
|
||||||
|
This constructor is fallible and may throw an error if the given
|
||||||
|
string does not strictly follow the RFC 2822 date-time string format.
|
||||||
|
|
||||||
|
Some examples of valid RFC 2822 date-time strings are:
|
||||||
|
|
||||||
|
- `Fri, 21 Nov 1997 09:55:06 -0600`
|
||||||
|
- `Tue, 1 Jul 2003 10:52:37 +0200`
|
||||||
|
- `Mon, 23 Dec 2024 01:58:48 GMT`
|
||||||
|
|
||||||
|
@param rfc2822Date -- An RFC 2822 formatted string
|
||||||
|
@return DateTime -- The new DateTime object
|
||||||
|
]=]
|
||||||
|
function dateTime.fromRfc2822(rfc2822Date: string): DateTime
|
||||||
|
return nil :: any
|
||||||
|
end
|
||||||
|
|
||||||
return dateTime
|
return dateTime
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "lune-std-fs"
|
name = "lune-std-fs"
|
||||||
version = "0.1.2"
|
version = "0.2.2"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
repository = "https://github.com/lune-org/lune"
|
repository = "https://github.com/lune-org/lune"
|
||||||
|
@ -13,11 +13,11 @@ path = "src/lib.rs"
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
mlua = { version = "0.9.9", features = ["luau"] }
|
mlua = { version = "0.10.3", features = ["luau"] }
|
||||||
|
|
||||||
|
async-fs = "2.1"
|
||||||
bstr = "1.9"
|
bstr = "1.9"
|
||||||
|
futures-lite = "2.6"
|
||||||
|
|
||||||
tokio = { version = "1", default-features = false, features = ["fs"] }
|
lune-utils = { version = "0.2.2", path = "../lune-utils" }
|
||||||
|
lune-std-datetime = { version = "0.2.2", path = "../lune-std-datetime" }
|
||||||
lune-utils = { version = "0.1.3", path = "../lune-utils" }
|
|
||||||
lune-std-datetime = { version = "0.1.2", path = "../lune-std-datetime" }
|
|
||||||
|
|
|
@ -2,8 +2,9 @@ use std::collections::VecDeque;
|
||||||
use std::io::ErrorKind;
|
use std::io::ErrorKind;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use async_fs as fs;
|
||||||
|
use futures_lite::prelude::*;
|
||||||
use mlua::prelude::*;
|
use mlua::prelude::*;
|
||||||
use tokio::fs;
|
|
||||||
|
|
||||||
use super::options::FsWriteOptions;
|
use super::options::FsWriteOptions;
|
||||||
|
|
||||||
|
@ -24,8 +25,8 @@ async fn get_contents_at(root: PathBuf, _: FsWriteOptions) -> LuaResult<CopyCont
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
// Push initial children of the root path into the queue
|
// Push initial children of the root path into the queue
|
||||||
let mut entries = fs::read_dir(&normalized_root).await?;
|
let mut reader = fs::read_dir(&normalized_root).await?;
|
||||||
while let Some(entry) = entries.next_entry().await? {
|
while let Some(entry) = reader.try_next().await? {
|
||||||
queue.push_back((1, entry.path()));
|
queue.push_back((1, entry.path()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,7 +43,7 @@ async fn get_contents_at(root: PathBuf, _: FsWriteOptions) -> LuaResult<CopyCont
|
||||||
} else if meta.is_dir() {
|
} else if meta.is_dir() {
|
||||||
// FUTURE: Add an option in FsWriteOptions for max depth and limit it here
|
// FUTURE: Add an option in FsWriteOptions for max depth and limit it here
|
||||||
let mut entries = fs::read_dir(¤t_path).await?;
|
let mut entries = fs::read_dir(¤t_path).await?;
|
||||||
while let Some(entry) = entries.next_entry().await? {
|
while let Some(entry) = entries.try_next().await? {
|
||||||
queue.push_back((current_depth + 1, entry.path()));
|
queue.push_back((current_depth + 1, entry.path()));
|
||||||
}
|
}
|
||||||
dirs.push((current_depth, current_path));
|
dirs.push((current_depth, current_path));
|
||||||
|
|
|
@ -3,9 +3,10 @@
|
||||||
use std::io::ErrorKind as IoErrorKind;
|
use std::io::ErrorKind as IoErrorKind;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use async_fs as fs;
|
||||||
use bstr::{BString, ByteSlice};
|
use bstr::{BString, ByteSlice};
|
||||||
|
use futures_lite::prelude::*;
|
||||||
use mlua::prelude::*;
|
use mlua::prelude::*;
|
||||||
use tokio::fs;
|
|
||||||
|
|
||||||
use lune_utils::TableBuilder;
|
use lune_utils::TableBuilder;
|
||||||
|
|
||||||
|
@ -17,6 +18,16 @@ use self::copy::copy;
|
||||||
use self::metadata::FsMetadata;
|
use self::metadata::FsMetadata;
|
||||||
use self::options::FsWriteOptions;
|
use self::options::FsWriteOptions;
|
||||||
|
|
||||||
|
const TYPEDEFS: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/types.d.luau"));
|
||||||
|
|
||||||
|
/**
|
||||||
|
Returns a string containing type definitions for the `fs` standard library.
|
||||||
|
*/
|
||||||
|
#[must_use]
|
||||||
|
pub fn typedefs() -> String {
|
||||||
|
TYPEDEFS.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Creates the `fs` standard library module.
|
Creates the `fs` standard library module.
|
||||||
|
|
||||||
|
@ -24,7 +35,7 @@ use self::options::FsWriteOptions;
|
||||||
|
|
||||||
Errors when out of memory.
|
Errors when out of memory.
|
||||||
*/
|
*/
|
||||||
pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
|
pub fn module(lua: Lua) -> LuaResult<LuaTable> {
|
||||||
TableBuilder::new(lua)?
|
TableBuilder::new(lua)?
|
||||||
.with_async_function("readFile", fs_read_file)?
|
.with_async_function("readFile", fs_read_file)?
|
||||||
.with_async_function("readDir", fs_read_dir)?
|
.with_async_function("readDir", fs_read_dir)?
|
||||||
|
@ -40,16 +51,16 @@ pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
|
||||||
.build_readonly()
|
.build_readonly()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fs_read_file(lua: &Lua, path: String) -> LuaResult<LuaString> {
|
async fn fs_read_file(lua: Lua, path: String) -> LuaResult<LuaString> {
|
||||||
let bytes = fs::read(&path).await.into_lua_err()?;
|
let bytes = fs::read(&path).await.into_lua_err()?;
|
||||||
|
|
||||||
lua.create_string(bytes)
|
lua.create_string(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fs_read_dir(_: &Lua, path: String) -> LuaResult<Vec<String>> {
|
async fn fs_read_dir(_: Lua, path: String) -> LuaResult<Vec<String>> {
|
||||||
let mut dir_strings = Vec::new();
|
let mut dir_strings = Vec::new();
|
||||||
let mut dir = fs::read_dir(&path).await.into_lua_err()?;
|
let mut dir = fs::read_dir(&path).await.into_lua_err()?;
|
||||||
while let Some(dir_entry) = dir.next_entry().await.into_lua_err()? {
|
while let Some(dir_entry) = dir.try_next().await.into_lua_err()? {
|
||||||
if let Some(dir_name_str) = dir_entry.file_name().to_str() {
|
if let Some(dir_name_str) = dir_entry.file_name().to_str() {
|
||||||
dir_strings.push(dir_name_str.to_owned());
|
dir_strings.push(dir_name_str.to_owned());
|
||||||
} else {
|
} else {
|
||||||
|
@ -62,23 +73,23 @@ async fn fs_read_dir(_: &Lua, path: String) -> LuaResult<Vec<String>> {
|
||||||
Ok(dir_strings)
|
Ok(dir_strings)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fs_write_file(_: &Lua, (path, contents): (String, BString)) -> LuaResult<()> {
|
async fn fs_write_file(_: Lua, (path, contents): (String, BString)) -> LuaResult<()> {
|
||||||
fs::write(&path, contents.as_bytes()).await.into_lua_err()
|
fs::write(&path, contents.as_bytes()).await.into_lua_err()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fs_write_dir(_: &Lua, path: String) -> LuaResult<()> {
|
async fn fs_write_dir(_: Lua, path: String) -> LuaResult<()> {
|
||||||
fs::create_dir_all(&path).await.into_lua_err()
|
fs::create_dir_all(&path).await.into_lua_err()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fs_remove_file(_: &Lua, path: String) -> LuaResult<()> {
|
async fn fs_remove_file(_: Lua, path: String) -> LuaResult<()> {
|
||||||
fs::remove_file(&path).await.into_lua_err()
|
fs::remove_file(&path).await.into_lua_err()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fs_remove_dir(_: &Lua, path: String) -> LuaResult<()> {
|
async fn fs_remove_dir(_: Lua, path: String) -> LuaResult<()> {
|
||||||
fs::remove_dir_all(&path).await.into_lua_err()
|
fs::remove_dir_all(&path).await.into_lua_err()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fs_metadata(_: &Lua, path: String) -> LuaResult<FsMetadata> {
|
async fn fs_metadata(_: Lua, path: String) -> LuaResult<FsMetadata> {
|
||||||
match fs::metadata(path).await {
|
match fs::metadata(path).await {
|
||||||
Err(e) if e.kind() == IoErrorKind::NotFound => Ok(FsMetadata::not_found()),
|
Err(e) if e.kind() == IoErrorKind::NotFound => Ok(FsMetadata::not_found()),
|
||||||
Ok(meta) => Ok(FsMetadata::from(meta)),
|
Ok(meta) => Ok(FsMetadata::from(meta)),
|
||||||
|
@ -86,7 +97,7 @@ async fn fs_metadata(_: &Lua, path: String) -> LuaResult<FsMetadata> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fs_is_file(_: &Lua, path: String) -> LuaResult<bool> {
|
async fn fs_is_file(_: Lua, path: String) -> LuaResult<bool> {
|
||||||
match fs::metadata(path).await {
|
match fs::metadata(path).await {
|
||||||
Err(e) if e.kind() == IoErrorKind::NotFound => Ok(false),
|
Err(e) if e.kind() == IoErrorKind::NotFound => Ok(false),
|
||||||
Ok(meta) => Ok(meta.is_file()),
|
Ok(meta) => Ok(meta.is_file()),
|
||||||
|
@ -94,7 +105,7 @@ async fn fs_is_file(_: &Lua, path: String) -> LuaResult<bool> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fs_is_dir(_: &Lua, path: String) -> LuaResult<bool> {
|
async fn fs_is_dir(_: Lua, path: String) -> LuaResult<bool> {
|
||||||
match fs::metadata(path).await {
|
match fs::metadata(path).await {
|
||||||
Err(e) if e.kind() == IoErrorKind::NotFound => Ok(false),
|
Err(e) if e.kind() == IoErrorKind::NotFound => Ok(false),
|
||||||
Ok(meta) => Ok(meta.is_dir()),
|
Ok(meta) => Ok(meta.is_dir()),
|
||||||
|
@ -102,7 +113,7 @@ async fn fs_is_dir(_: &Lua, path: String) -> LuaResult<bool> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fs_move(_: &Lua, (from, to, options): (String, String, FsWriteOptions)) -> LuaResult<()> {
|
async fn fs_move(_: Lua, (from, to, options): (String, String, FsWriteOptions)) -> LuaResult<()> {
|
||||||
let path_from = PathBuf::from(from);
|
let path_from = PathBuf::from(from);
|
||||||
if !path_from.exists() {
|
if !path_from.exists() {
|
||||||
return Err(LuaError::RuntimeError(format!(
|
return Err(LuaError::RuntimeError(format!(
|
||||||
|
@ -121,6 +132,6 @@ async fn fs_move(_: &Lua, (from, to, options): (String, String, FsWriteOptions))
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fs_copy(_: &Lua, (from, to, options): (String, String, FsWriteOptions)) -> LuaResult<()> {
|
async fn fs_copy(_: Lua, (from, to, options): (String, String, FsWriteOptions)) -> LuaResult<()> {
|
||||||
copy(from, to, options).await
|
copy(from, to, options).await
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,8 +61,8 @@ impl From<StdFileType> for FsMetadataKind {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'lua> IntoLua<'lua> for FsMetadataKind {
|
impl IntoLua for FsMetadataKind {
|
||||||
fn into_lua(self, lua: &'lua Lua) -> LuaResult<LuaValue<'lua>> {
|
fn into_lua(self, lua: &Lua) -> LuaResult<LuaValue> {
|
||||||
if self == Self::None {
|
if self == Self::None {
|
||||||
Ok(LuaValue::Nil)
|
Ok(LuaValue::Nil)
|
||||||
} else {
|
} else {
|
||||||
|
@ -84,8 +84,8 @@ impl From<StdPermissions> for FsPermissions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'lua> IntoLua<'lua> for FsPermissions {
|
impl IntoLua for FsPermissions {
|
||||||
fn into_lua(self, lua: &'lua Lua) -> LuaResult<LuaValue<'lua>> {
|
fn into_lua(self, lua: &Lua) -> LuaResult<LuaValue> {
|
||||||
let tab = lua.create_table_with_capacity(0, 1)?;
|
let tab = lua.create_table_with_capacity(0, 1)?;
|
||||||
tab.set("readOnly", self.read_only)?;
|
tab.set("readOnly", self.read_only)?;
|
||||||
tab.set_readonly(true);
|
tab.set_readonly(true);
|
||||||
|
@ -116,8 +116,8 @@ impl FsMetadata {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'lua> IntoLua<'lua> for FsMetadata {
|
impl IntoLua for FsMetadata {
|
||||||
fn into_lua(self, lua: &'lua Lua) -> LuaResult<LuaValue<'lua>> {
|
fn into_lua(self, lua: &Lua) -> LuaResult<LuaValue> {
|
||||||
let tab = lua.create_table_with_capacity(0, 6)?;
|
let tab = lua.create_table_with_capacity(0, 6)?;
|
||||||
tab.set("kind", self.kind)?;
|
tab.set("kind", self.kind)?;
|
||||||
tab.set("exists", self.exists)?;
|
tab.set("exists", self.exists)?;
|
||||||
|
|
|
@ -5,8 +5,8 @@ pub struct FsWriteOptions {
|
||||||
pub(crate) overwrite: bool,
|
pub(crate) overwrite: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'lua> FromLua<'lua> for FsWriteOptions {
|
impl FromLua for FsWriteOptions {
|
||||||
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
|
fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
|
||||||
Ok(match value {
|
Ok(match value {
|
||||||
LuaValue::Nil => Self { overwrite: false },
|
LuaValue::Nil => Self { overwrite: false },
|
||||||
LuaValue::Boolean(b) => Self { overwrite: b },
|
LuaValue::Boolean(b) => Self { overwrite: b },
|
||||||
|
@ -19,7 +19,7 @@ impl<'lua> FromLua<'lua> for FsWriteOptions {
|
||||||
_ => {
|
_ => {
|
||||||
return Err(LuaError::FromLuaConversionError {
|
return Err(LuaError::FromLuaConversionError {
|
||||||
from: value.type_name(),
|
from: value.type_name(),
|
||||||
to: "FsWriteOptions",
|
to: "FsWriteOptions".to_string(),
|
||||||
message: Some(format!(
|
message: Some(format!(
|
||||||
"Invalid write options - expected boolean or table, got {}",
|
"Invalid write options - expected boolean or table, got {}",
|
||||||
value.type_name()
|
value.type_name()
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
local DateTime = require("./datetime")
|
--!nocheck
|
||||||
|
|
||||||
|
local DateTime = require("@lune/datetime")
|
||||||
type DateTime = DateTime.DateTime
|
type DateTime = DateTime.DateTime
|
||||||
|
|
||||||
export type MetadataKind = "file" | "dir" | "symlink"
|
export type MetadataKind = "file" | "dir" | "symlink"
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "lune-std-luau"
|
name = "lune-std-luau"
|
||||||
version = "0.1.2"
|
version = "0.2.2"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
repository = "https://github.com/lune-org/lune"
|
repository = "https://github.com/lune-org/lune"
|
||||||
|
@ -13,6 +13,6 @@ path = "src/lib.rs"
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
mlua = { version = "0.9.9", features = ["luau", "luau-jit"] }
|
mlua = { version = "0.10.3", features = ["luau", "luau-jit"] }
|
||||||
|
|
||||||
lune-utils = { version = "0.1.3", path = "../lune-utils" }
|
lune-utils = { version = "0.2.2", path = "../lune-utils" }
|
||||||
|
|
|
@ -2,13 +2,21 @@
|
||||||
|
|
||||||
use mlua::prelude::*;
|
use mlua::prelude::*;
|
||||||
|
|
||||||
use lune_utils::TableBuilder;
|
use lune_utils::{jit::JitEnablement, TableBuilder};
|
||||||
|
|
||||||
mod options;
|
mod options;
|
||||||
|
|
||||||
use self::options::{LuauCompileOptions, LuauLoadOptions};
|
use self::options::{LuauCompileOptions, LuauLoadOptions};
|
||||||
|
|
||||||
const BYTECODE_ERROR_BYTE: u8 = 0;
|
const TYPEDEFS: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/types.d.luau"));
|
||||||
|
|
||||||
|
/**
|
||||||
|
Returns a string containing type definitions for the `luau` standard library.
|
||||||
|
*/
|
||||||
|
#[must_use]
|
||||||
|
pub fn typedefs() -> String {
|
||||||
|
TYPEDEFS.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Creates the `luau` standard library module.
|
Creates the `luau` standard library module.
|
||||||
|
@ -17,33 +25,30 @@ const BYTECODE_ERROR_BYTE: u8 = 0;
|
||||||
|
|
||||||
Errors when out of memory.
|
Errors when out of memory.
|
||||||
*/
|
*/
|
||||||
pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
|
pub fn module(lua: Lua) -> LuaResult<LuaTable> {
|
||||||
TableBuilder::new(lua)?
|
TableBuilder::new(lua)?
|
||||||
.with_function("compile", compile_source)?
|
.with_function("compile", compile_source)?
|
||||||
.with_function("load", load_source)?
|
.with_function("load", load_source)?
|
||||||
.build_readonly()
|
.build_readonly()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compile_source<'lua>(
|
fn compile_source(
|
||||||
lua: &'lua Lua,
|
lua: &Lua,
|
||||||
(source, options): (LuaString<'lua>, LuauCompileOptions),
|
(source, options): (LuaString, LuauCompileOptions),
|
||||||
) -> LuaResult<LuaString<'lua>> {
|
) -> LuaResult<LuaString> {
|
||||||
let bytecode = options.into_compiler().compile(source);
|
options
|
||||||
|
.into_compiler()
|
||||||
match bytecode.first() {
|
.compile(source.as_bytes())
|
||||||
Some(&BYTECODE_ERROR_BYTE) => Err(LuaError::RuntimeError(
|
.and_then(|s| lua.create_string(s))
|
||||||
String::from_utf8_lossy(&bytecode).into_owned(),
|
|
||||||
)),
|
|
||||||
Some(_) => lua.create_string(bytecode),
|
|
||||||
None => panic!("Compiling resulted in empty bytecode"),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_source<'lua>(
|
fn load_source(
|
||||||
lua: &'lua Lua,
|
lua: &Lua,
|
||||||
(source, options): (LuaString<'lua>, LuauLoadOptions),
|
(source, options): (LuaString, LuauLoadOptions),
|
||||||
) -> LuaResult<LuaFunction<'lua>> {
|
) -> LuaResult<LuaFunction> {
|
||||||
let mut chunk = lua.load(source.as_bytes()).set_name(options.debug_name);
|
let mut chunk = lua
|
||||||
|
.load(source.as_bytes().to_vec())
|
||||||
|
.set_name(options.debug_name);
|
||||||
let env_changed = options.environment.is_some();
|
let env_changed = options.environment.is_some();
|
||||||
|
|
||||||
if let Some(custom_environment) = options.environment {
|
if let Some(custom_environment) = options.environment {
|
||||||
|
@ -56,10 +61,10 @@ fn load_source<'lua>(
|
||||||
environment.set(key, value)?;
|
environment.set(key, value)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(global_metatable) = lua.globals().get_metatable() {
|
if let Some(global_metatable) = lua.globals().metatable() {
|
||||||
environment.set_metatable(Some(global_metatable));
|
environment.set_metatable(Some(global_metatable));
|
||||||
}
|
}
|
||||||
} else if let Some(custom_metatable) = custom_environment.get_metatable() {
|
} else if let Some(custom_metatable) = custom_environment.metatable() {
|
||||||
// Since we don't need to set the global metatable,
|
// Since we don't need to set the global metatable,
|
||||||
// we can just set a custom metatable if it exists
|
// we can just set a custom metatable if it exists
|
||||||
environment.set_metatable(Some(custom_metatable));
|
environment.set_metatable(Some(custom_metatable));
|
||||||
|
@ -78,7 +83,13 @@ fn load_source<'lua>(
|
||||||
// changed, otherwise disable JIT since it'll fall back anyways
|
// changed, otherwise disable JIT since it'll fall back anyways
|
||||||
lua.enable_jit(options.codegen_enabled && !env_changed);
|
lua.enable_jit(options.codegen_enabled && !env_changed);
|
||||||
let function = chunk.into_function()?;
|
let function = chunk.into_function()?;
|
||||||
lua.enable_jit(true);
|
lua.enable_jit(
|
||||||
|
lua.app_data_ref::<JitEnablement>()
|
||||||
|
.ok_or(LuaError::runtime(
|
||||||
|
"Failed to get current JitStatus ref from AppData",
|
||||||
|
))?
|
||||||
|
.enabled(),
|
||||||
|
);
|
||||||
|
|
||||||
Ok(function)
|
Ok(function)
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,8 +36,8 @@ impl Default for LuauCompileOptions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'lua> FromLua<'lua> for LuauCompileOptions {
|
impl FromLua for LuauCompileOptions {
|
||||||
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
|
fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
|
||||||
Ok(match value {
|
Ok(match value {
|
||||||
LuaValue::Nil => Self::default(),
|
LuaValue::Nil => Self::default(),
|
||||||
LuaValue::Table(t) => {
|
LuaValue::Table(t) => {
|
||||||
|
@ -68,7 +68,7 @@ impl<'lua> FromLua<'lua> for LuauCompileOptions {
|
||||||
_ => {
|
_ => {
|
||||||
return Err(LuaError::FromLuaConversionError {
|
return Err(LuaError::FromLuaConversionError {
|
||||||
from: value.type_name(),
|
from: value.type_name(),
|
||||||
to: "CompileOptions",
|
to: "CompileOptions".to_string(),
|
||||||
message: Some(format!(
|
message: Some(format!(
|
||||||
"Invalid compile options - expected table, got {}",
|
"Invalid compile options - expected table, got {}",
|
||||||
value.type_name()
|
value.type_name()
|
||||||
|
@ -79,14 +79,14 @@ impl<'lua> FromLua<'lua> for LuauCompileOptions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct LuauLoadOptions<'lua> {
|
pub struct LuauLoadOptions {
|
||||||
pub(crate) debug_name: String,
|
pub(crate) debug_name: String,
|
||||||
pub(crate) environment: Option<LuaTable<'lua>>,
|
pub(crate) environment: Option<LuaTable>,
|
||||||
pub(crate) inject_globals: bool,
|
pub(crate) inject_globals: bool,
|
||||||
pub(crate) codegen_enabled: bool,
|
pub(crate) codegen_enabled: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for LuauLoadOptions<'_> {
|
impl Default for LuauLoadOptions {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
debug_name: DEFAULT_DEBUG_NAME.to_string(),
|
debug_name: DEFAULT_DEBUG_NAME.to_string(),
|
||||||
|
@ -97,8 +97,8 @@ impl Default for LuauLoadOptions<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'lua> FromLua<'lua> for LuauLoadOptions<'lua> {
|
impl FromLua for LuauLoadOptions {
|
||||||
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
|
fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
|
||||||
Ok(match value {
|
Ok(match value {
|
||||||
LuaValue::Nil => Self::default(),
|
LuaValue::Nil => Self::default(),
|
||||||
LuaValue::Table(t) => {
|
LuaValue::Table(t) => {
|
||||||
|
@ -131,7 +131,7 @@ impl<'lua> FromLua<'lua> for LuauLoadOptions<'lua> {
|
||||||
_ => {
|
_ => {
|
||||||
return Err(LuaError::FromLuaConversionError {
|
return Err(LuaError::FromLuaConversionError {
|
||||||
from: value.type_name(),
|
from: value.type_name(),
|
||||||
to: "LoadOptions",
|
to: "LoadOptions".to_string(),
|
||||||
message: Some(format!(
|
message: Some(format!(
|
||||||
"Invalid load options - expected string or table, got {}",
|
"Invalid load options - expected string or table, got {}",
|
||||||
value.type_name()
|
value.type_name()
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "lune-std-net"
|
name = "lune-std-net"
|
||||||
version = "0.1.2"
|
version = "0.2.2"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
repository = "https://github.com/lune-org/lune"
|
repository = "https://github.com/lune-org/lune"
|
||||||
|
@ -13,27 +13,30 @@ path = "src/lib.rs"
|
||||||
workspace = true
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
mlua = { version = "0.9.9", features = ["luau"] }
|
mlua = { version = "0.10.3", features = ["luau"] }
|
||||||
mlua-luau-scheduler = { version = "0.0.2", path = "../mlua-luau-scheduler" }
|
mlua-luau-scheduler = { version = "0.1.2", path = "../mlua-luau-scheduler" }
|
||||||
|
|
||||||
|
async-channel = "2.3"
|
||||||
|
async-executor = "1.13"
|
||||||
|
async-io = "2.4"
|
||||||
|
async-lock = "3.4"
|
||||||
|
async-net = "2.0"
|
||||||
|
async-tungstenite = "0.29"
|
||||||
|
blocking = "1.6"
|
||||||
bstr = "1.9"
|
bstr = "1.9"
|
||||||
futures-util = "0.3"
|
form_urlencoded = "1.2"
|
||||||
hyper = { version = "1.1", features = ["full"] }
|
futures = { version = "0.3", default-features = false, features = ["std"] }
|
||||||
hyper-util = { version = "0.1", features = ["full"] }
|
futures-lite = "2.6"
|
||||||
http = "1.0"
|
futures-rustls = "0.26"
|
||||||
http-body-util = { version = "0.1" }
|
http-body-util = "0.1"
|
||||||
hyper-tungstenite = { version = "0.13" }
|
hyper = { version = "1.6", default-features = false, features = ["http1", "client", "server"] }
|
||||||
reqwest = { version = "0.11", default-features = false, features = [
|
pin-project-lite = "0.2"
|
||||||
"rustls-tls",
|
rustls = { version = "0.23", default-features = false, features = ["std", "tls12", "ring"] }
|
||||||
] }
|
rustls-pki-types = "1.11"
|
||||||
tokio-tungstenite = { version = "0.21", features = ["rustls-tls-webpki-roots"] }
|
url = "2.5"
|
||||||
urlencoding = "2.1"
|
urlencoding = "2.1"
|
||||||
|
webpki = "0.22"
|
||||||
|
webpki-roots = "0.26"
|
||||||
|
|
||||||
tokio = { version = "1", default-features = false, features = [
|
lune-utils = { version = "0.2.2", path = "../lune-utils" }
|
||||||
"sync",
|
lune-std-serde = { version = "0.2.2", path = "../lune-std-serde" }
|
||||||
"net",
|
|
||||||
"macros",
|
|
||||||
] }
|
|
||||||
|
|
||||||
lune-utils = { version = "0.1.3", path = "../lune-utils" }
|
|
||||||
lune-std-serde = { version = "0.1.2", path = "../lune-std-serde" }
|
|
||||||
|
|
59
crates/lune-std-net/src/body/cursor.rs
Normal file
59
crates/lune-std-net/src/body/cursor.rs
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
use hyper::body::{Buf, Bytes};
|
||||||
|
|
||||||
|
use super::inner::ReadableBodyInner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
The cursor keeping track of inner data and its position for a readable body.
|
||||||
|
*/
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ReadableBodyCursor {
|
||||||
|
inner: ReadableBodyInner,
|
||||||
|
start: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReadableBodyCursor {
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.inner.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_slice(&self) -> &[u8] {
|
||||||
|
&self.inner.as_slice()[self.start..]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn advance(&mut self, cnt: usize) {
|
||||||
|
self.start += cnt;
|
||||||
|
if self.start > self.inner.len() {
|
||||||
|
self.start = self.inner.len();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_bytes(self) -> Bytes {
|
||||||
|
self.inner.into_bytes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Buf for ReadableBodyCursor {
|
||||||
|
fn remaining(&self) -> usize {
|
||||||
|
self.len().saturating_sub(self.start)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn chunk(&self) -> &[u8] {
|
||||||
|
self.as_slice()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn advance(&mut self, cnt: usize) {
|
||||||
|
self.advance(cnt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> From<T> for ReadableBodyCursor
|
||||||
|
where
|
||||||
|
T: Into<ReadableBodyInner>,
|
||||||
|
{
|
||||||
|
fn from(value: T) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: value.into(),
|
||||||
|
start: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
crates/lune-std-net/src/body/incoming.rs
Normal file
35
crates/lune-std-net/src/body/incoming.rs
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
use http_body_util::BodyExt;
|
||||||
|
use hyper::{
|
||||||
|
body::{Bytes, Incoming},
|
||||||
|
header::CONTENT_ENCODING,
|
||||||
|
HeaderMap,
|
||||||
|
};
|
||||||
|
|
||||||
|
use mlua::prelude::*;
|
||||||
|
|
||||||
|
use lune_std_serde::{decompress, CompressDecompressFormat};
|
||||||
|
|
||||||
|
pub async fn handle_incoming_body(
|
||||||
|
headers: &HeaderMap,
|
||||||
|
body: Incoming,
|
||||||
|
should_decompress: bool,
|
||||||
|
) -> LuaResult<(Bytes, bool)> {
|
||||||
|
let mut body = body.collect().await.into_lua_err()?.to_bytes();
|
||||||
|
|
||||||
|
let was_decompressed = if should_decompress {
|
||||||
|
let decompress_format = headers
|
||||||
|
.get(CONTENT_ENCODING)
|
||||||
|
.and_then(|value| value.to_str().ok())
|
||||||
|
.and_then(CompressDecompressFormat::detect_from_header_str);
|
||||||
|
if let Some(format) = decompress_format {
|
||||||
|
body = Bytes::from(decompress(body, format).await?);
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((body, was_decompressed))
|
||||||
|
}
|
110
crates/lune-std-net/src/body/inner.rs
Normal file
110
crates/lune-std-net/src/body/inner.rs
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
use hyper::body::{Buf as _, Bytes};
|
||||||
|
use mlua::{prelude::*, Buffer as LuaBuffer};
|
||||||
|
|
||||||
|
/**
|
||||||
|
The inner data for a readable body.
|
||||||
|
*/
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum ReadableBodyInner {
|
||||||
|
Bytes(Bytes),
|
||||||
|
String(String),
|
||||||
|
LuaString(LuaString),
|
||||||
|
LuaBuffer(LuaBuffer),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReadableBodyInner {
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
match self {
|
||||||
|
Self::Bytes(b) => b.len(),
|
||||||
|
Self::String(s) => s.len(),
|
||||||
|
Self::LuaString(s) => s.as_bytes().len(),
|
||||||
|
Self::LuaBuffer(b) => b.len(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_slice(&self) -> &[u8] {
|
||||||
|
/*
|
||||||
|
SAFETY: Reading lua strings and lua buffers as raw slices is safe while we can
|
||||||
|
guarantee that the inner Lua value + main lua struct has not yet been dropped
|
||||||
|
|
||||||
|
1. Buffers are fixed-size and guaranteed to never resize
|
||||||
|
2. We do not expose any method for writing to the body, only reading
|
||||||
|
3. We guarantee that net.request and net.serve futures are only driven forward
|
||||||
|
while we also know that the Lua + scheduler pair have not yet been dropped
|
||||||
|
4. Any writes from within lua to a buffer, are considered user error,
|
||||||
|
and are not unsafe, since the only possible outcome with the above
|
||||||
|
guarantees is invalid / mangled contents in request / response bodies
|
||||||
|
*/
|
||||||
|
match self {
|
||||||
|
Self::Bytes(b) => b.chunk(),
|
||||||
|
Self::String(s) => s.as_bytes(),
|
||||||
|
Self::LuaString(s) => unsafe {
|
||||||
|
// BorrowedBytes would not let us return a plain slice here,
|
||||||
|
// which is what the Buf implementation below needs - we need to
|
||||||
|
// do a little hack here to re-create the slice without a lifetime
|
||||||
|
let b = s.as_bytes();
|
||||||
|
|
||||||
|
let ptr = b.as_ptr();
|
||||||
|
let len = b.len();
|
||||||
|
|
||||||
|
std::slice::from_raw_parts(ptr, len)
|
||||||
|
},
|
||||||
|
Self::LuaBuffer(b) => unsafe {
|
||||||
|
// Similar to above, we need to get the raw slice for the buffer,
|
||||||
|
// which is a bit trickier here because Buffer has a read + write
|
||||||
|
// interface instead of using slices for some unknown reason
|
||||||
|
let v = LuaValue::Buffer(b.clone());
|
||||||
|
|
||||||
|
let ptr = v.to_pointer().cast::<u8>();
|
||||||
|
let len = b.len();
|
||||||
|
|
||||||
|
std::slice::from_raw_parts(ptr, len)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_bytes(self) -> Bytes {
|
||||||
|
match self {
|
||||||
|
Self::Bytes(b) => b,
|
||||||
|
Self::String(s) => Bytes::from(s),
|
||||||
|
Self::LuaString(s) => Bytes::from(s.as_bytes().to_vec()),
|
||||||
|
Self::LuaBuffer(b) => Bytes::from(b.to_vec()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&'static str> for ReadableBodyInner {
|
||||||
|
fn from(value: &'static str) -> Self {
|
||||||
|
Self::Bytes(Bytes::from(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Vec<u8>> for ReadableBodyInner {
|
||||||
|
fn from(value: Vec<u8>) -> Self {
|
||||||
|
Self::Bytes(Bytes::from(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Bytes> for ReadableBodyInner {
|
||||||
|
fn from(value: Bytes) -> Self {
|
||||||
|
Self::Bytes(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for ReadableBodyInner {
|
||||||
|
fn from(value: String) -> Self {
|
||||||
|
Self::String(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<LuaString> for ReadableBodyInner {
|
||||||
|
fn from(value: LuaString) -> Self {
|
||||||
|
Self::LuaString(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<LuaBuffer> for ReadableBodyInner {
|
||||||
|
fn from(value: LuaBuffer) -> Self {
|
||||||
|
Self::LuaBuffer(value)
|
||||||
|
}
|
||||||
|
}
|
11
crates/lune-std-net/src/body/mod.rs
Normal file
11
crates/lune-std-net/src/body/mod.rs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
#![allow(unused_imports)]
|
||||||
|
|
||||||
|
mod cursor;
|
||||||
|
mod incoming;
|
||||||
|
mod inner;
|
||||||
|
mod readable;
|
||||||
|
|
||||||
|
pub use self::cursor::ReadableBodyCursor;
|
||||||
|
pub use self::incoming::handle_incoming_body;
|
||||||
|
pub use self::inner::ReadableBodyInner;
|
||||||
|
pub use self::readable::ReadableBody;
|
105
crates/lune-std-net/src/body/readable.rs
Normal file
105
crates/lune-std-net/src/body/readable.rs
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
use std::convert::Infallible;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
|
use hyper::body::{Body, Bytes, Frame, SizeHint};
|
||||||
|
use mlua::prelude::*;
|
||||||
|
|
||||||
|
use super::cursor::ReadableBodyCursor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Zero-copy wrapper for a readable body.
|
||||||
|
|
||||||
|
Provides methods to read bytes that can be safely used if, and only
|
||||||
|
if, the respective Lua struct for the body has not yet been dropped.
|
||||||
|
|
||||||
|
If the body was created from a `Vec<u8>`, `Bytes`, or a `String`, reading
|
||||||
|
bytes is always safe and does not go through any additional indirections.
|
||||||
|
*/
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ReadableBody {
|
||||||
|
cursor: Option<ReadableBodyCursor>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReadableBody {
|
||||||
|
pub const fn empty() -> Self {
|
||||||
|
Self { cursor: None }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_slice(&self) -> &[u8] {
|
||||||
|
match self.cursor.as_ref() {
|
||||||
|
Some(cursor) => cursor.as_slice(),
|
||||||
|
None => &[],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_bytes(self) -> Bytes {
|
||||||
|
match self.cursor {
|
||||||
|
Some(cursor) => cursor.into_bytes(),
|
||||||
|
None => Bytes::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Body for ReadableBody {
|
||||||
|
type Data = ReadableBodyCursor;
|
||||||
|
type Error = Infallible;
|
||||||
|
|
||||||
|
fn poll_frame(
|
||||||
|
mut self: Pin<&mut Self>,
|
||||||
|
_cx: &mut Context<'_>,
|
||||||
|
) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
|
||||||
|
Poll::Ready(self.cursor.take().map(|d| Ok(Frame::data(d))))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_end_stream(&self) -> bool {
|
||||||
|
self.cursor.is_none()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size_hint(&self) -> SizeHint {
|
||||||
|
self.cursor.as_ref().map_or_else(
|
||||||
|
|| SizeHint::with_exact(0),
|
||||||
|
|c| SizeHint::with_exact(c.len() as u64),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> From<T> for ReadableBody
|
||||||
|
where
|
||||||
|
T: Into<ReadableBodyCursor>,
|
||||||
|
{
|
||||||
|
fn from(value: T) -> Self {
|
||||||
|
Self {
|
||||||
|
cursor: Some(value.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> From<Option<T>> for ReadableBody
|
||||||
|
where
|
||||||
|
T: Into<ReadableBodyCursor>,
|
||||||
|
{
|
||||||
|
fn from(value: Option<T>) -> Self {
|
||||||
|
Self {
|
||||||
|
cursor: value.map(Into::into),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromLua for ReadableBody {
|
||||||
|
fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
|
||||||
|
match value {
|
||||||
|
LuaValue::Nil => Ok(Self::empty()),
|
||||||
|
LuaValue::String(str) => Ok(Self::from(str)),
|
||||||
|
LuaValue::Buffer(buf) => Ok(Self::from(buf)),
|
||||||
|
v => Err(LuaError::FromLuaConversionError {
|
||||||
|
from: v.type_name(),
|
||||||
|
to: "Body".to_string(),
|
||||||
|
message: Some(format!(
|
||||||
|
"Invalid body - expected string or buffer, got {}",
|
||||||
|
v.type_name()
|
||||||
|
)),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,163 +0,0 @@
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use mlua::prelude::*;
|
|
||||||
|
|
||||||
use reqwest::header::{HeaderMap, HeaderName, HeaderValue, CONTENT_ENCODING};
|
|
||||||
|
|
||||||
use lune_std_serde::{decompress, CompressDecompressFormat};
|
|
||||||
use lune_utils::TableBuilder;
|
|
||||||
|
|
||||||
use super::{config::RequestConfig, util::header_map_to_table};
|
|
||||||
|
|
||||||
const REGISTRY_KEY: &str = "NetClient";
|
|
||||||
|
|
||||||
pub struct NetClientBuilder {
|
|
||||||
builder: reqwest::ClientBuilder,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NetClientBuilder {
|
|
||||||
pub fn new() -> NetClientBuilder {
|
|
||||||
Self {
|
|
||||||
builder: reqwest::ClientBuilder::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn headers<K, V>(mut self, headers: &[(K, V)]) -> LuaResult<Self>
|
|
||||||
where
|
|
||||||
K: AsRef<str>,
|
|
||||||
V: AsRef<[u8]>,
|
|
||||||
{
|
|
||||||
let mut map = HeaderMap::new();
|
|
||||||
for (key, val) in headers {
|
|
||||||
let hkey = HeaderName::from_str(key.as_ref()).into_lua_err()?;
|
|
||||||
let hval = HeaderValue::from_bytes(val.as_ref()).into_lua_err()?;
|
|
||||||
map.insert(hkey, hval);
|
|
||||||
}
|
|
||||||
self.builder = self.builder.default_headers(map);
|
|
||||||
Ok(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build(self) -> LuaResult<NetClient> {
|
|
||||||
let client = self.builder.build().into_lua_err()?;
|
|
||||||
Ok(NetClient { inner: client })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct NetClient {
|
|
||||||
inner: reqwest::Client,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NetClient {
|
|
||||||
pub fn from_registry(lua: &Lua) -> Self {
|
|
||||||
lua.named_registry_value(REGISTRY_KEY)
|
|
||||||
.expect("Failed to get NetClient from lua registry")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn into_registry(self, lua: &Lua) {
|
|
||||||
lua.set_named_registry_value(REGISTRY_KEY, self)
|
|
||||||
.expect("Failed to store NetClient in lua registry");
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn request(&self, config: RequestConfig) -> LuaResult<NetClientResponse> {
|
|
||||||
// Create and send the request
|
|
||||||
let mut request = self.inner.request(config.method, config.url);
|
|
||||||
for (query, values) in config.query {
|
|
||||||
request = request.query(
|
|
||||||
&values
|
|
||||||
.iter()
|
|
||||||
.map(|v| (query.as_str(), v))
|
|
||||||
.collect::<Vec<_>>(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
for (header, values) in config.headers {
|
|
||||||
for value in values {
|
|
||||||
request = request.header(header.as_str(), value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let res = request
|
|
||||||
.body(config.body.unwrap_or_default())
|
|
||||||
.send()
|
|
||||||
.await
|
|
||||||
.into_lua_err()?;
|
|
||||||
|
|
||||||
// Extract status, headers
|
|
||||||
let res_status = res.status().as_u16();
|
|
||||||
let res_status_text = res.status().canonical_reason();
|
|
||||||
let res_headers = res.headers().clone();
|
|
||||||
|
|
||||||
// Read response bytes
|
|
||||||
let mut res_bytes = res.bytes().await.into_lua_err()?.to_vec();
|
|
||||||
let mut res_decompressed = false;
|
|
||||||
|
|
||||||
// Check for extra options, decompression
|
|
||||||
if config.options.decompress {
|
|
||||||
let decompress_format = res_headers
|
|
||||||
.iter()
|
|
||||||
.find(|(name, _)| {
|
|
||||||
name.as_str()
|
|
||||||
.eq_ignore_ascii_case(CONTENT_ENCODING.as_str())
|
|
||||||
})
|
|
||||||
.and_then(|(_, value)| value.to_str().ok())
|
|
||||||
.and_then(CompressDecompressFormat::detect_from_header_str);
|
|
||||||
if let Some(format) = decompress_format {
|
|
||||||
res_bytes = decompress(res_bytes, format).await?;
|
|
||||||
res_decompressed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(NetClientResponse {
|
|
||||||
ok: (200..300).contains(&res_status),
|
|
||||||
status_code: res_status,
|
|
||||||
status_message: res_status_text.unwrap_or_default().to_string(),
|
|
||||||
headers: res_headers,
|
|
||||||
body: res_bytes,
|
|
||||||
body_decompressed: res_decompressed,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LuaUserData for NetClient {}
|
|
||||||
|
|
||||||
impl FromLua<'_> for NetClient {
|
|
||||||
fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
|
|
||||||
if let LuaValue::UserData(ud) = value {
|
|
||||||
if let Ok(ctx) = ud.borrow::<NetClient>() {
|
|
||||||
return Ok(ctx.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
unreachable!("NetClient should only be used from registry")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&Lua> for NetClient {
|
|
||||||
fn from(value: &Lua) -> Self {
|
|
||||||
value
|
|
||||||
.named_registry_value(REGISTRY_KEY)
|
|
||||||
.expect("Missing require context in lua registry")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct NetClientResponse {
|
|
||||||
ok: bool,
|
|
||||||
status_code: u16,
|
|
||||||
status_message: String,
|
|
||||||
headers: HeaderMap,
|
|
||||||
body: Vec<u8>,
|
|
||||||
body_decompressed: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NetClientResponse {
|
|
||||||
pub fn into_lua_table(self, lua: &Lua) -> LuaResult<LuaTable> {
|
|
||||||
TableBuilder::new(lua)?
|
|
||||||
.with_value("ok", self.ok)?
|
|
||||||
.with_value("statusCode", self.status_code)?
|
|
||||||
.with_value("statusMessage", self.status_message)?
|
|
||||||
.with_value(
|
|
||||||
"headers",
|
|
||||||
header_map_to_table(lua, self.headers, self.body_decompressed)?,
|
|
||||||
)?
|
|
||||||
.with_value("body", lua.create_string(&self.body)?)?
|
|
||||||
.build_readonly()
|
|
||||||
}
|
|
||||||
}
|
|
95
crates/lune-std-net/src/client/http_stream.rs
Normal file
95
crates/lune-std-net/src/client/http_stream.rs
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
use std::{
|
||||||
|
io,
|
||||||
|
pin::Pin,
|
||||||
|
sync::Arc,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
|
||||||
|
use async_net::TcpStream;
|
||||||
|
use futures_lite::prelude::*;
|
||||||
|
use futures_rustls::{TlsConnector, TlsStream};
|
||||||
|
use rustls_pki_types::ServerName;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
use crate::client::rustls::CLIENT_CONFIG;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum HttpStream {
|
||||||
|
Plain(TcpStream),
|
||||||
|
Tls(TlsStream<TcpStream>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HttpStream {
|
||||||
|
pub async fn connect(url: Url) -> Result<Self, io::Error> {
|
||||||
|
let Some(host) = url.host() else {
|
||||||
|
return Err(make_err("unknown or missing host"));
|
||||||
|
};
|
||||||
|
let Some(port) = url.port_or_known_default() else {
|
||||||
|
return Err(make_err("unknown or missing port"));
|
||||||
|
};
|
||||||
|
|
||||||
|
let use_tls = match url.scheme() {
|
||||||
|
"http" => false,
|
||||||
|
"https" => true,
|
||||||
|
s => return Err(make_err(format!("unsupported scheme: {s}"))),
|
||||||
|
};
|
||||||
|
|
||||||
|
let host = host.to_string();
|
||||||
|
let stream = TcpStream::connect((host.clone(), port)).await?;
|
||||||
|
|
||||||
|
let stream = if use_tls {
|
||||||
|
let servname = ServerName::try_from(host).map_err(make_err)?.to_owned();
|
||||||
|
let connector = TlsConnector::from(Arc::clone(&CLIENT_CONFIG));
|
||||||
|
let stream = connector.connect(servname, stream).await?;
|
||||||
|
Self::Tls(TlsStream::Client(stream))
|
||||||
|
} else {
|
||||||
|
Self::Plain(stream)
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(stream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsyncRead for HttpStream {
|
||||||
|
fn poll_read(
|
||||||
|
mut self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
buf: &mut [u8],
|
||||||
|
) -> Poll<io::Result<usize>> {
|
||||||
|
match &mut *self {
|
||||||
|
HttpStream::Plain(stream) => Pin::new(stream).poll_read(cx, buf),
|
||||||
|
HttpStream::Tls(stream) => Pin::new(stream).poll_read(cx, buf),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsyncWrite for HttpStream {
|
||||||
|
fn poll_write(
|
||||||
|
mut self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
buf: &[u8],
|
||||||
|
) -> Poll<io::Result<usize>> {
|
||||||
|
match &mut *self {
|
||||||
|
HttpStream::Plain(stream) => Pin::new(stream).poll_write(cx, buf),
|
||||||
|
HttpStream::Tls(stream) => Pin::new(stream).poll_write(cx, buf),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||||
|
match &mut *self {
|
||||||
|
HttpStream::Plain(stream) => Pin::new(stream).poll_close(cx),
|
||||||
|
HttpStream::Tls(stream) => Pin::new(stream).poll_close(cx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||||
|
match &mut *self {
|
||||||
|
HttpStream::Plain(stream) => Pin::new(stream).poll_flush(cx),
|
||||||
|
HttpStream::Tls(stream) => Pin::new(stream).poll_flush(cx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_err(e: impl ToString) -> io::Error {
|
||||||
|
io::Error::new(io::ErrorKind::Other, e.to_string())
|
||||||
|
}
|
125
crates/lune-std-net/src/client/mod.rs
Normal file
125
crates/lune-std-net/src/client/mod.rs
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
use http_body_util::Full;
|
||||||
|
use hyper::{
|
||||||
|
body::Incoming,
|
||||||
|
client::conn::http1::handshake,
|
||||||
|
header::{HeaderValue, ACCEPT, CONTENT_LENGTH, HOST, LOCATION, USER_AGENT},
|
||||||
|
Method, Request as HyperRequest, Response as HyperResponse, Uri,
|
||||||
|
};
|
||||||
|
|
||||||
|
use mlua::prelude::*;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
body::ReadableBody,
|
||||||
|
client::{http_stream::HttpStream, ws_stream::WsStream},
|
||||||
|
shared::{
|
||||||
|
headers::create_user_agent_header,
|
||||||
|
hyper::{HyperExecutor, HyperIo},
|
||||||
|
request::Request,
|
||||||
|
response::Response,
|
||||||
|
websocket::Websocket,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub mod http_stream;
|
||||||
|
pub mod rustls;
|
||||||
|
pub mod ws_stream;
|
||||||
|
|
||||||
|
const MAX_REDIRECTS: usize = 10;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Connects to a websocket at the given URL.
|
||||||
|
*/
|
||||||
|
pub async fn connect_websocket(url: Url) -> LuaResult<Websocket<WsStream>> {
|
||||||
|
let stream = WsStream::connect(url).await?;
|
||||||
|
Ok(Websocket::from(stream))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Sends the request and returns the final response.
|
||||||
|
|
||||||
|
This will follow any redirects returned by the server,
|
||||||
|
modifying the request method and body as necessary.
|
||||||
|
*/
|
||||||
|
pub async fn send_request(mut request: Request, lua: Lua) -> LuaResult<Response> {
|
||||||
|
let url = request
|
||||||
|
.inner
|
||||||
|
.uri()
|
||||||
|
.to_string()
|
||||||
|
.parse::<Url>()
|
||||||
|
.into_lua_err()?;
|
||||||
|
|
||||||
|
// Some headers are required by most if not
|
||||||
|
// all servers, make sure those are present...
|
||||||
|
if !request.headers().contains_key(HOST.as_str()) {
|
||||||
|
if let Some(host) = url.host_str() {
|
||||||
|
let host = HeaderValue::from_str(host).into_lua_err()?;
|
||||||
|
request.inner.headers_mut().insert(HOST, host);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !request.headers().contains_key(USER_AGENT.as_str()) {
|
||||||
|
let ua = create_user_agent_header(&lua)?;
|
||||||
|
let ua = HeaderValue::from_str(&ua).into_lua_err()?;
|
||||||
|
request.inner.headers_mut().insert(USER_AGENT, ua);
|
||||||
|
}
|
||||||
|
if !request.headers().contains_key(CONTENT_LENGTH.as_str()) && request.method() != Method::GET {
|
||||||
|
let len = request.body().len().to_string();
|
||||||
|
let len = HeaderValue::from_str(&len).into_lua_err()?;
|
||||||
|
request.inner.headers_mut().insert(CONTENT_LENGTH, len);
|
||||||
|
}
|
||||||
|
if !request.headers().contains_key(ACCEPT.as_str()) {
|
||||||
|
let accept = HeaderValue::from_static("*/*");
|
||||||
|
request.inner.headers_mut().insert(ACCEPT, accept);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... we can now safely continue and send the request
|
||||||
|
loop {
|
||||||
|
let stream = HttpStream::connect(url.clone()).await?;
|
||||||
|
|
||||||
|
let (mut sender, conn) = handshake(HyperIo::from(stream)).await.into_lua_err()?;
|
||||||
|
|
||||||
|
HyperExecutor::execute(lua.clone(), conn);
|
||||||
|
|
||||||
|
let (parts, body) = request.clone_inner().into_parts();
|
||||||
|
let data = HyperRequest::from_parts(parts, Full::new(body.into_bytes()));
|
||||||
|
let incoming = sender.send_request(data).await.into_lua_err()?;
|
||||||
|
|
||||||
|
if let Some((new_method, new_uri)) =
|
||||||
|
check_redirect(request.inner.method().clone(), &incoming)
|
||||||
|
{
|
||||||
|
if request.redirects.is_some_and(|r| r >= MAX_REDIRECTS) {
|
||||||
|
return Err(LuaError::external("Too many redirects"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if new_method == Method::GET {
|
||||||
|
*request.inner.body_mut() = ReadableBody::empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
*request.inner.method_mut() = new_method;
|
||||||
|
*request.inner.uri_mut() = new_uri;
|
||||||
|
|
||||||
|
*request.redirects.get_or_insert_default() += 1;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
break Response::from_incoming(incoming, request.decompress).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check_redirect(method: Method, response: &HyperResponse<Incoming>) -> Option<(Method, Uri)> {
|
||||||
|
if !response.status().is_redirection() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let location = response.headers().get(LOCATION)?;
|
||||||
|
let location = location.to_str().ok()?;
|
||||||
|
let location = location.parse().ok()?;
|
||||||
|
|
||||||
|
let method = match response.status().as_u16() {
|
||||||
|
301..=303 => Method::GET,
|
||||||
|
_ => method,
|
||||||
|
};
|
||||||
|
|
||||||
|
Some((method, location))
|
||||||
|
}
|
26
crates/lune-std-net/src/client/rustls.rs
Normal file
26
crates/lune-std-net/src/client/rustls.rs
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
use std::sync::{
|
||||||
|
atomic::{AtomicBool, Ordering},
|
||||||
|
Arc, LazyLock,
|
||||||
|
};
|
||||||
|
|
||||||
|
use rustls::{crypto::ring, ClientConfig};
|
||||||
|
|
||||||
|
static PROVIDER_INITIALIZED: AtomicBool = AtomicBool::new(false);
|
||||||
|
|
||||||
|
pub fn initialize_provider() {
|
||||||
|
if !PROVIDER_INITIALIZED.load(Ordering::Relaxed) {
|
||||||
|
PROVIDER_INITIALIZED.store(true, Ordering::Relaxed);
|
||||||
|
// Only errors if already installed, which is fine
|
||||||
|
ring::default_provider().install_default().ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub static CLIENT_CONFIG: LazyLock<Arc<ClientConfig>> = LazyLock::new(|| {
|
||||||
|
initialize_provider();
|
||||||
|
rustls::ClientConfig::builder()
|
||||||
|
.with_root_certificates(rustls::RootCertStore {
|
||||||
|
roots: webpki_roots::TLS_SERVER_ROOTS.to_vec(),
|
||||||
|
})
|
||||||
|
.with_no_client_auth()
|
||||||
|
.into()
|
||||||
|
});
|
114
crates/lune-std-net/src/client/ws_stream.rs
Normal file
114
crates/lune-std-net/src/client/ws_stream.rs
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
use std::{
|
||||||
|
io,
|
||||||
|
pin::Pin,
|
||||||
|
sync::Arc,
|
||||||
|
task::{Context, Poll},
|
||||||
|
};
|
||||||
|
|
||||||
|
use async_net::TcpStream;
|
||||||
|
use async_tungstenite::{
|
||||||
|
tungstenite::{Error as TungsteniteError, Message, Result as TungsteniteResult},
|
||||||
|
WebSocketStream as TungsteniteStream,
|
||||||
|
};
|
||||||
|
use futures::Sink;
|
||||||
|
use futures_lite::prelude::*;
|
||||||
|
use futures_rustls::{TlsConnector, TlsStream};
|
||||||
|
use rustls_pki_types::ServerName;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
use crate::client::rustls::CLIENT_CONFIG;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum WsStream {
|
||||||
|
Plain(TungsteniteStream<TcpStream>),
|
||||||
|
Tls(TungsteniteStream<TlsStream<TcpStream>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WsStream {
|
||||||
|
pub async fn connect(url: Url) -> Result<Self, io::Error> {
|
||||||
|
let Some(host) = url.host() else {
|
||||||
|
return Err(make_err("unknown or missing host"));
|
||||||
|
};
|
||||||
|
let Some(port) = url.port_or_known_default() else {
|
||||||
|
return Err(make_err("unknown or missing port"));
|
||||||
|
};
|
||||||
|
|
||||||
|
let use_tls = match url.scheme() {
|
||||||
|
"ws" => false,
|
||||||
|
"wss" => true,
|
||||||
|
s => return Err(make_err(format!("unsupported scheme: {s}"))),
|
||||||
|
};
|
||||||
|
|
||||||
|
let host = host.to_string();
|
||||||
|
let stream = TcpStream::connect((host.clone(), port)).await?;
|
||||||
|
|
||||||
|
let stream = if use_tls {
|
||||||
|
let servname = ServerName::try_from(host).map_err(make_err)?.to_owned();
|
||||||
|
let connector = TlsConnector::from(Arc::clone(&CLIENT_CONFIG));
|
||||||
|
|
||||||
|
let stream = connector.connect(servname, stream).await?;
|
||||||
|
let stream = TlsStream::Client(stream);
|
||||||
|
|
||||||
|
let stream = async_tungstenite::client_async(url.to_string(), stream)
|
||||||
|
.await
|
||||||
|
.map_err(make_err)?
|
||||||
|
.0;
|
||||||
|
Self::Tls(stream)
|
||||||
|
} else {
|
||||||
|
let stream = async_tungstenite::client_async(url.to_string(), stream)
|
||||||
|
.await
|
||||||
|
.map_err(make_err)?
|
||||||
|
.0;
|
||||||
|
Self::Plain(stream)
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(stream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sink<Message> for WsStream {
|
||||||
|
type Error = TungsteniteError;
|
||||||
|
|
||||||
|
fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
|
match &mut *self {
|
||||||
|
WsStream::Plain(s) => Pin::new(s).poll_ready(cx),
|
||||||
|
WsStream::Tls(s) => Pin::new(s).poll_ready(cx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_send(mut self: Pin<&mut Self>, item: Message) -> Result<(), Self::Error> {
|
||||||
|
match &mut *self {
|
||||||
|
WsStream::Plain(s) => Pin::new(s).start_send(item),
|
||||||
|
WsStream::Tls(s) => Pin::new(s).start_send(item),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
|
match &mut *self {
|
||||||
|
WsStream::Plain(s) => Pin::new(s).poll_flush(cx),
|
||||||
|
WsStream::Tls(s) => Pin::new(s).poll_flush(cx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
|
match &mut *self {
|
||||||
|
WsStream::Plain(s) => Pin::new(s).poll_close(cx),
|
||||||
|
WsStream::Tls(s) => Pin::new(s).poll_close(cx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Stream for WsStream {
|
||||||
|
type Item = TungsteniteResult<Message>;
|
||||||
|
|
||||||
|
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||||
|
match &mut *self {
|
||||||
|
WsStream::Plain(s) => Pin::new(s).poll_next(cx),
|
||||||
|
WsStream::Tls(s) => Pin::new(s).poll_next(cx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_err(e: impl ToString) -> io::Error {
|
||||||
|
io::Error::new(io::ErrorKind::Other, e.to_string())
|
||||||
|
}
|
|
@ -1,231 +0,0 @@
|
||||||
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: IpAddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
|
|
||||||
|
|
||||||
const WEB_SOCKET_UPDGRADE_REQUEST_HANDLER: &str = r#"
|
|
||||||
return {
|
|
||||||
status = 426,
|
|
||||||
body = "Upgrade Required",
|
|
||||||
headers = {
|
|
||||||
Upgrade = "websocket",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
"#;
|
|
||||||
|
|
||||||
// Net request config
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct RequestConfigOptions {
|
|
||||||
pub decompress: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for RequestConfigOptions {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self { decompress: true }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'lua> FromLua<'lua> for RequestConfigOptions {
|
|
||||||
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
|
|
||||||
if let LuaValue::Nil = value {
|
|
||||||
// Nil means default options
|
|
||||||
Ok(Self::default())
|
|
||||||
} else if let LuaValue::Table(tab) = value {
|
|
||||||
// Table means custom options
|
|
||||||
let decompress = match tab.get::<_, Option<bool>>("decompress") {
|
|
||||||
Ok(decomp) => Ok(decomp.unwrap_or(true)),
|
|
||||||
Err(_) => Err(LuaError::RuntimeError(
|
|
||||||
"Invalid option value for 'decompress' in request config options".to_string(),
|
|
||||||
)),
|
|
||||||
}?;
|
|
||||||
Ok(Self { decompress })
|
|
||||||
} else {
|
|
||||||
// Anything else is invalid
|
|
||||||
Err(LuaError::FromLuaConversionError {
|
|
||||||
from: value.type_name(),
|
|
||||||
to: "RequestConfigOptions",
|
|
||||||
message: Some(format!(
|
|
||||||
"Invalid request config options - expected table or nil, got {}",
|
|
||||||
value.type_name()
|
|
||||||
)),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct RequestConfig {
|
|
||||||
pub url: String,
|
|
||||||
pub method: Method,
|
|
||||||
pub query: HashMap<String, Vec<String>>,
|
|
||||||
pub headers: HashMap<String, Vec<String>>,
|
|
||||||
pub body: Option<Vec<u8>>,
|
|
||||||
pub options: RequestConfigOptions,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromLua<'_> for RequestConfig {
|
|
||||||
fn from_lua(value: LuaValue, lua: &Lua) -> LuaResult<Self> {
|
|
||||||
// If we just got a string we assume its a GET request to a given url
|
|
||||||
if let LuaValue::String(s) = value {
|
|
||||||
Ok(Self {
|
|
||||||
url: s.to_string_lossy().to_string(),
|
|
||||||
method: Method::GET,
|
|
||||||
query: HashMap::new(),
|
|
||||||
headers: HashMap::new(),
|
|
||||||
body: None,
|
|
||||||
options: RequestConfigOptions::default(),
|
|
||||||
})
|
|
||||||
} else if let LuaValue::Table(tab) = value {
|
|
||||||
// If we got a table we are able to configure the entire request
|
|
||||||
// Extract url
|
|
||||||
let url = match tab.get::<_, LuaString>("url") {
|
|
||||||
Ok(config_url) => Ok(config_url.to_string_lossy().to_string()),
|
|
||||||
Err(_) => Err(LuaError::runtime("Missing 'url' in request config")),
|
|
||||||
}?;
|
|
||||||
// Extract method
|
|
||||||
let method = match tab.get::<_, LuaString>("method") {
|
|
||||||
Ok(config_method) => config_method.to_string_lossy().trim().to_ascii_uppercase(),
|
|
||||||
Err(_) => "GET".to_string(),
|
|
||||||
};
|
|
||||||
// Extract query
|
|
||||||
let query = match tab.get::<_, LuaTable>("query") {
|
|
||||||
Ok(tab) => table_to_hash_map(tab, "query")?,
|
|
||||||
Err(_) => HashMap::new(),
|
|
||||||
};
|
|
||||||
// Extract headers
|
|
||||||
let headers = match tab.get::<_, LuaTable>("headers") {
|
|
||||||
Ok(tab) => table_to_hash_map(tab, "headers")?,
|
|
||||||
Err(_) => HashMap::new(),
|
|
||||||
};
|
|
||||||
// Extract 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() {
|
|
||||||
"GET" => Ok(Method::GET),
|
|
||||||
"POST" => Ok(Method::POST),
|
|
||||||
"PUT" => Ok(Method::PUT),
|
|
||||||
"DELETE" => Ok(Method::DELETE),
|
|
||||||
"HEAD" => Ok(Method::HEAD),
|
|
||||||
"OPTIONS" => Ok(Method::OPTIONS),
|
|
||||||
"PATCH" => Ok(Method::PATCH),
|
|
||||||
_ => Err(LuaError::RuntimeError(format!(
|
|
||||||
"Invalid request config method '{}'",
|
|
||||||
&method
|
|
||||||
))),
|
|
||||||
}?;
|
|
||||||
// Parse any extra options given
|
|
||||||
let options = match tab.get::<_, LuaValue>("options") {
|
|
||||||
Ok(opts) => RequestConfigOptions::from_lua(opts, lua)?,
|
|
||||||
Err(_) => RequestConfigOptions::default(),
|
|
||||||
};
|
|
||||||
// All good, validated and we got what we need
|
|
||||||
Ok(Self {
|
|
||||||
url,
|
|
||||||
method,
|
|
||||||
query,
|
|
||||||
headers,
|
|
||||||
body,
|
|
||||||
options,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// Anything else is invalid
|
|
||||||
Err(LuaError::FromLuaConversionError {
|
|
||||||
from: value.type_name(),
|
|
||||||
to: "RequestConfig",
|
|
||||||
message: Some(format!(
|
|
||||||
"Invalid request config - expected string or table, got {}",
|
|
||||||
value.type_name()
|
|
||||||
)),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Net serve config
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ServeConfig<'a> {
|
|
||||||
pub address: IpAddr,
|
|
||||||
pub handle_request: LuaFunction<'a>,
|
|
||||||
pub handle_web_socket: Option<LuaFunction<'a>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'lua> FromLua<'lua> for ServeConfig<'lua> {
|
|
||||||
fn from_lua(value: LuaValue<'lua>, lua: &'lua Lua) -> LuaResult<Self> {
|
|
||||||
if let LuaValue::Function(f) = &value {
|
|
||||||
// Single function = request handler, rest is default
|
|
||||||
Ok(ServeConfig {
|
|
||||||
handle_request: f.clone(),
|
|
||||||
handle_web_socket: None,
|
|
||||||
address: DEFAULT_IP_ADDRESS,
|
|
||||||
})
|
|
||||||
} else if let LuaValue::Table(t) = &value {
|
|
||||||
// Table means custom options
|
|
||||||
let address: Option<LuaString> = t.get("address")?;
|
|
||||||
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: IpAddr = match &address {
|
|
||||||
Some(addr) => {
|
|
||||||
let addr_str = addr.to_str()?;
|
|
||||||
|
|
||||||
addr_str
|
|
||||||
.trim_start_matches("http://")
|
|
||||||
.trim_start_matches("https://")
|
|
||||||
.parse()
|
|
||||||
.map_err(|_e| LuaError::FromLuaConversionError {
|
|
||||||
from: value.type_name(),
|
|
||||||
to: "ServeConfig",
|
|
||||||
message: Some(format!(
|
|
||||||
"IP address format is incorrect - \
|
|
||||||
expected an IP in the form 'http://0.0.0.0' or '0.0.0.0', \
|
|
||||||
got '{addr_str}'"
|
|
||||||
)),
|
|
||||||
})?
|
|
||||||
}
|
|
||||||
None => DEFAULT_IP_ADDRESS,
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
address,
|
|
||||||
handle_request: handle_request.unwrap_or_else(|| {
|
|
||||||
lua.load(WEB_SOCKET_UPDGRADE_REQUEST_HANDLER)
|
|
||||||
.into_function()
|
|
||||||
.expect("Failed to create default http responder function")
|
|
||||||
}),
|
|
||||||
handle_web_socket,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Err(LuaError::FromLuaConversionError {
|
|
||||||
from: value.type_name(),
|
|
||||||
to: "ServeConfig",
|
|
||||||
message: Some(String::from(
|
|
||||||
"Invalid serve config - expected table with 'handleRequest' or 'handleWebSocket' function",
|
|
||||||
)),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Anything else is invalid
|
|
||||||
Err(LuaError::FromLuaConversionError {
|
|
||||||
from: value.type_name(),
|
|
||||||
to: "ServeConfig",
|
|
||||||
message: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,26 +1,29 @@
|
||||||
#![allow(clippy::cargo_common_metadata)]
|
#![allow(clippy::cargo_common_metadata)]
|
||||||
|
|
||||||
use bstr::BString;
|
|
||||||
use mlua::prelude::*;
|
|
||||||
use mlua_luau_scheduler::LuaSpawnExt;
|
|
||||||
|
|
||||||
mod client;
|
|
||||||
mod config;
|
|
||||||
mod server;
|
|
||||||
mod util;
|
|
||||||
mod websocket;
|
|
||||||
|
|
||||||
use lune_utils::TableBuilder;
|
use lune_utils::TableBuilder;
|
||||||
|
use mlua::prelude::*;
|
||||||
|
|
||||||
|
pub(crate) mod body;
|
||||||
|
pub(crate) mod client;
|
||||||
|
pub(crate) mod server;
|
||||||
|
pub(crate) mod shared;
|
||||||
|
pub(crate) mod url;
|
||||||
|
|
||||||
use self::{
|
use self::{
|
||||||
client::{NetClient, NetClientBuilder},
|
client::ws_stream::WsStream,
|
||||||
config::{RequestConfig, ServeConfig},
|
server::config::ServeConfig,
|
||||||
server::serve,
|
shared::{request::Request, response::Response, websocket::Websocket},
|
||||||
util::create_user_agent_header,
|
|
||||||
websocket::NetWebSocket,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use lune_std_serde::{decode, encode, EncodeDecodeConfig, EncodeDecodeFormat};
|
const TYPEDEFS: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/types.d.luau"));
|
||||||
|
|
||||||
|
/**
|
||||||
|
Returns a string containing type definitions for the `net` standard library.
|
||||||
|
*/
|
||||||
|
#[must_use]
|
||||||
|
pub fn typedefs() -> String {
|
||||||
|
TYPEDEFS.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Creates the `net` standard library module.
|
Creates the `net` standard library module.
|
||||||
|
@ -29,14 +32,10 @@ use lune_std_serde::{decode, encode, EncodeDecodeConfig, EncodeDecodeFormat};
|
||||||
|
|
||||||
Errors when out of memory.
|
Errors when out of memory.
|
||||||
*/
|
*/
|
||||||
pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
|
pub fn module(lua: Lua) -> LuaResult<LuaTable> {
|
||||||
NetClientBuilder::new()
|
// No initial rustls setup is necessary, the respective
|
||||||
.headers(&[("User-Agent", create_user_agent_header(lua)?)])?
|
// functions lazily initialize anything there as needed
|
||||||
.build()?
|
|
||||||
.into_registry(lua);
|
|
||||||
TableBuilder::new(lua)?
|
TableBuilder::new(lua)?
|
||||||
.with_function("jsonEncode", net_json_encode)?
|
|
||||||
.with_function("jsonDecode", net_json_decode)?
|
|
||||||
.with_async_function("request", net_request)?
|
.with_async_function("request", net_request)?
|
||||||
.with_async_function("socket", net_socket)?
|
.with_async_function("socket", net_socket)?
|
||||||
.with_async_function("serve", net_serve)?
|
.with_async_function("serve", net_serve)?
|
||||||
|
@ -45,58 +44,35 @@ pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
|
||||||
.build_readonly()
|
.build_readonly()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn net_json_encode<'lua>(
|
async fn net_request(lua: Lua, req: Request) -> LuaResult<Response> {
|
||||||
lua: &'lua Lua,
|
self::client::send_request(req, lua).await
|
||||||
(val, pretty): (LuaValue<'lua>, Option<bool>),
|
|
||||||
) -> LuaResult<LuaString<'lua>> {
|
|
||||||
let config = EncodeDecodeConfig::from((EncodeDecodeFormat::Json, pretty.unwrap_or_default()));
|
|
||||||
encode(val, lua, config)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn net_json_decode(lua: &Lua, json: BString) -> LuaResult<LuaValue> {
|
async fn net_socket(_: Lua, url: String) -> LuaResult<Websocket<WsStream>> {
|
||||||
let config = EncodeDecodeConfig::from(EncodeDecodeFormat::Json);
|
let url = url.parse().into_lua_err()?;
|
||||||
decode(json, lua, config)
|
self::client::connect_websocket(url).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn net_request(lua: &Lua, config: RequestConfig) -> LuaResult<LuaTable> {
|
async fn net_serve(lua: Lua, (port, config): (u16, ServeConfig)) -> LuaResult<LuaTable> {
|
||||||
let client = NetClient::from_registry(lua);
|
self::server::serve(lua.clone(), port, config)
|
||||||
// NOTE: We spawn the request as a background task to free up resources in lua
|
.await?
|
||||||
let res = lua.spawn(async move { client.request(config).await });
|
.into_lua_table(lua)
|
||||||
res.await?.into_lua_table(lua)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn net_socket(lua: &Lua, url: String) -> LuaResult<LuaTable> {
|
fn net_url_encode(
|
||||||
let (ws, _) = tokio_tungstenite::connect_async(url).await.into_lua_err()?;
|
lua: &Lua,
|
||||||
NetWebSocket::new(ws).into_lua_table(lua)
|
(lua_string, as_binary): (LuaString, Option<bool>),
|
||||||
|
) -> LuaResult<LuaString> {
|
||||||
|
let as_binary = as_binary.unwrap_or_default();
|
||||||
|
let bytes = self::url::encode(lua_string, as_binary)?;
|
||||||
|
lua.create_string(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn net_serve<'lua>(
|
fn net_url_decode(
|
||||||
lua: &'lua Lua,
|
lua: &Lua,
|
||||||
(port, config): (u16, ServeConfig<'lua>),
|
(lua_string, as_binary): (LuaString, Option<bool>),
|
||||||
) -> LuaResult<LuaTable<'lua>> {
|
) -> LuaResult<LuaString> {
|
||||||
serve(lua, port, config).await
|
let as_binary = as_binary.unwrap_or_default();
|
||||||
}
|
let bytes = self::url::decode(lua_string, as_binary)?;
|
||||||
|
lua.create_string(bytes)
|
||||||
fn net_url_encode<'lua>(
|
|
||||||
lua: &'lua Lua,
|
|
||||||
(lua_string, as_binary): (LuaString<'lua>, Option<bool>),
|
|
||||||
) -> LuaResult<LuaValue<'lua>> {
|
|
||||||
if matches!(as_binary, Some(true)) {
|
|
||||||
urlencoding::encode_binary(lua_string.as_bytes()).into_lua(lua)
|
|
||||||
} else {
|
|
||||||
urlencoding::encode(lua_string.to_str()?).into_lua(lua)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn net_url_decode<'lua>(
|
|
||||||
lua: &'lua Lua,
|
|
||||||
(lua_string, as_binary): (LuaString<'lua>, Option<bool>),
|
|
||||||
) -> LuaResult<LuaValue<'lua>> {
|
|
||||||
if matches!(as_binary, Some(true)) {
|
|
||||||
urlencoding::decode_binary(lua_string.as_bytes()).into_lua(lua)
|
|
||||||
} else {
|
|
||||||
urlencoding::decode(lua_string.to_str()?)
|
|
||||||
.map_err(|e| LuaError::RuntimeError(format!("Encountered invalid encoding - {e}")))?
|
|
||||||
.into_lua(lua)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
87
crates/lune-std-net/src/server/config.rs
Normal file
87
crates/lune-std-net/src/server/config.rs
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
use std::net::{IpAddr, Ipv4Addr};
|
||||||
|
|
||||||
|
use mlua::prelude::*;
|
||||||
|
|
||||||
|
const DEFAULT_IP_ADDRESS: IpAddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
|
||||||
|
|
||||||
|
const WEB_SOCKET_UPDGRADE_REQUEST_HANDLER: &str = r#"
|
||||||
|
return {
|
||||||
|
status = 426,
|
||||||
|
body = "Upgrade Required",
|
||||||
|
headers = {
|
||||||
|
Upgrade = "websocket",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ServeConfig {
|
||||||
|
pub address: IpAddr,
|
||||||
|
pub handle_request: LuaFunction,
|
||||||
|
pub handle_web_socket: Option<LuaFunction>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromLua for ServeConfig {
|
||||||
|
fn from_lua(value: LuaValue, lua: &Lua) -> LuaResult<Self> {
|
||||||
|
if let LuaValue::Function(f) = &value {
|
||||||
|
// Single function = request handler, rest is default
|
||||||
|
Ok(ServeConfig {
|
||||||
|
handle_request: f.clone(),
|
||||||
|
handle_web_socket: None,
|
||||||
|
address: DEFAULT_IP_ADDRESS,
|
||||||
|
})
|
||||||
|
} else if let LuaValue::Table(t) = &value {
|
||||||
|
// Table means custom options
|
||||||
|
let address: Option<LuaString> = t.get("address")?;
|
||||||
|
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: IpAddr = match &address {
|
||||||
|
Some(addr) => {
|
||||||
|
let addr_str = addr.to_str()?;
|
||||||
|
|
||||||
|
addr_str
|
||||||
|
.trim_start_matches("http://")
|
||||||
|
.trim_start_matches("https://")
|
||||||
|
.parse()
|
||||||
|
.map_err(|_e| LuaError::FromLuaConversionError {
|
||||||
|
from: value.type_name(),
|
||||||
|
to: "ServeConfig".to_string(),
|
||||||
|
message: Some(format!(
|
||||||
|
"IP address format is incorrect - \
|
||||||
|
expected an IP in the form 'http://0.0.0.0' or '0.0.0.0', \
|
||||||
|
got '{addr_str}'"
|
||||||
|
)),
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
None => DEFAULT_IP_ADDRESS,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
address,
|
||||||
|
handle_request: handle_request.unwrap_or_else(|| {
|
||||||
|
lua.load(WEB_SOCKET_UPDGRADE_REQUEST_HANDLER)
|
||||||
|
.into_function()
|
||||||
|
.expect("Failed to create default http responder function")
|
||||||
|
}),
|
||||||
|
handle_web_socket,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err(LuaError::FromLuaConversionError {
|
||||||
|
from: value.type_name(),
|
||||||
|
to: "ServeConfig".to_string(),
|
||||||
|
message: Some(String::from(
|
||||||
|
"Invalid serve config - expected table with 'handleRequest' or 'handleWebSocket' function",
|
||||||
|
)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Anything else is invalid
|
||||||
|
Err(LuaError::FromLuaConversionError {
|
||||||
|
from: value.type_name(),
|
||||||
|
to: "ServeConfig".to_string(),
|
||||||
|
message: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
72
crates/lune-std-net/src/server/handle.rs
Normal file
72
crates/lune-std-net/src/server/handle.rs
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
use std::{
|
||||||
|
net::SocketAddr,
|
||||||
|
sync::{
|
||||||
|
atomic::{AtomicBool, Ordering},
|
||||||
|
Arc,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
use async_channel::{unbounded, Receiver, Sender};
|
||||||
|
|
||||||
|
use lune_utils::TableBuilder;
|
||||||
|
use mlua::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ServeHandle {
|
||||||
|
addr: SocketAddr,
|
||||||
|
shutdown: Arc<AtomicBool>,
|
||||||
|
sender: Sender<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServeHandle {
|
||||||
|
pub fn new(addr: SocketAddr) -> (Self, Receiver<()>) {
|
||||||
|
let (sender, receiver) = unbounded();
|
||||||
|
let this = Self {
|
||||||
|
addr,
|
||||||
|
shutdown: Arc::new(AtomicBool::new(false)),
|
||||||
|
sender,
|
||||||
|
};
|
||||||
|
(this, receiver)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Remove this in the next major release to use colon/self
|
||||||
|
// based call syntax and userdata implementation below instead
|
||||||
|
pub fn into_lua_table(self, lua: Lua) -> LuaResult<LuaTable> {
|
||||||
|
let shutdown = self.shutdown.clone();
|
||||||
|
let sender = self.sender.clone();
|
||||||
|
TableBuilder::new(lua)?
|
||||||
|
.with_value("ip", self.addr.ip().to_string())?
|
||||||
|
.with_value("port", self.addr.port())?
|
||||||
|
.with_function("stop", move |_, ()| {
|
||||||
|
if shutdown.load(Ordering::SeqCst) {
|
||||||
|
Err(LuaError::runtime("Server already stopped"))
|
||||||
|
} else {
|
||||||
|
shutdown.store(true, Ordering::SeqCst);
|
||||||
|
sender.try_send(()).ok();
|
||||||
|
sender.close();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
})?
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LuaUserData for ServeHandle {
|
||||||
|
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
||||||
|
fields.add_field_method_get("ip", |_, this| Ok(this.addr.ip().to_string()));
|
||||||
|
fields.add_field_method_get("port", |_, this| Ok(this.addr.port()));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
|
||||||
|
methods.add_method("stop", |_, this, ()| {
|
||||||
|
if this.shutdown.load(Ordering::SeqCst) {
|
||||||
|
Err(LuaError::runtime("Server already stopped"))
|
||||||
|
} else {
|
||||||
|
this.shutdown.store(true, Ordering::SeqCst);
|
||||||
|
this.sender.try_send(()).ok();
|
||||||
|
this.sender.close();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,61 +0,0 @@
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
||||||
|
|
||||||
use mlua::prelude::*;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub(super) struct SvcKeys {
|
|
||||||
key_request: &'static str,
|
|
||||||
key_websocket: Option<&'static str>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SvcKeys {
|
|
||||||
pub(super) fn new<'lua>(
|
|
||||||
lua: &'lua Lua,
|
|
||||||
handle_request: LuaFunction<'lua>,
|
|
||||||
handle_websocket: Option<LuaFunction<'lua>>,
|
|
||||||
) -> LuaResult<Self> {
|
|
||||||
static SERVE_COUNTER: AtomicUsize = AtomicUsize::new(0);
|
|
||||||
let count = SERVE_COUNTER.fetch_add(1, Ordering::Relaxed);
|
|
||||||
|
|
||||||
// NOTE: We leak strings here, but this is an acceptable tradeoff since programs
|
|
||||||
// generally only start one or a couple of servers and they are usually never dropped.
|
|
||||||
// Leaking here lets us keep this struct Copy and access the request handler callbacks
|
|
||||||
// very performantly, significantly reducing the per-request overhead of the server.
|
|
||||||
let key_request: &'static str =
|
|
||||||
Box::leak(format!("__net_serve_request_{count}").into_boxed_str());
|
|
||||||
let key_websocket: Option<&'static str> = if handle_websocket.is_some() {
|
|
||||||
Some(Box::leak(
|
|
||||||
format!("__net_serve_websocket_{count}").into_boxed_str(),
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
lua.set_named_registry_value(key_request, handle_request)?;
|
|
||||||
if let Some(key) = key_websocket {
|
|
||||||
lua.set_named_registry_value(key, handle_websocket.unwrap())?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
key_request,
|
|
||||||
key_websocket,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn has_websocket_handler(&self) -> bool {
|
|
||||||
self.key_websocket.is_some()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn request_handler<'lua>(&self, lua: &'lua Lua) -> LuaResult<LuaFunction<'lua>> {
|
|
||||||
lua.named_registry_value(self.key_request)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn websocket_handler<'lua>(
|
|
||||||
&self,
|
|
||||||
lua: &'lua Lua,
|
|
||||||
) -> LuaResult<Option<LuaFunction<'lua>>> {
|
|
||||||
self.key_websocket
|
|
||||||
.map(|key| lua.named_registry_value(key))
|
|
||||||
.transpose()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,105 +1,121 @@
|
||||||
use std::{
|
use std::{cell::Cell, net::SocketAddr, rc::Rc};
|
||||||
net::SocketAddr,
|
|
||||||
rc::{Rc, Weak},
|
|
||||||
};
|
|
||||||
|
|
||||||
use hyper::server::conn::http1;
|
use async_net::TcpListener;
|
||||||
use hyper_util::rt::TokioIo;
|
use futures_lite::pin;
|
||||||
use tokio::{net::TcpListener, pin};
|
use hyper::server::conn::http1::Builder as Http1Builder;
|
||||||
|
|
||||||
use mlua::prelude::*;
|
use mlua::prelude::*;
|
||||||
use mlua_luau_scheduler::LuaSpawnExt;
|
use mlua_luau_scheduler::LuaSpawnExt;
|
||||||
|
|
||||||
use lune_utils::TableBuilder;
|
use crate::{
|
||||||
|
server::{config::ServeConfig, handle::ServeHandle, service::Service},
|
||||||
|
shared::{
|
||||||
|
futures::{either, Either},
|
||||||
|
hyper::{HyperIo, HyperTimer},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
use super::config::ServeConfig;
|
pub mod config;
|
||||||
|
pub mod handle;
|
||||||
|
pub mod service;
|
||||||
|
pub mod upgrade;
|
||||||
|
|
||||||
mod keys;
|
/**
|
||||||
mod request;
|
Starts an HTTP server using the given port and configuration.
|
||||||
mod response;
|
|
||||||
mod service;
|
|
||||||
|
|
||||||
use keys::SvcKeys;
|
Returns a `ServeHandle` that can be used to gracefully stop the server.
|
||||||
use service::Svc;
|
*/
|
||||||
|
pub async fn serve(lua: Lua, port: u16, config: ServeConfig) -> LuaResult<ServeHandle> {
|
||||||
pub async fn serve<'lua>(
|
let address = SocketAddr::from((config.address, port));
|
||||||
lua: &'lua Lua,
|
let service = Service {
|
||||||
port: u16,
|
lua: lua.clone(),
|
||||||
config: ServeConfig<'lua>,
|
address,
|
||||||
) -> LuaResult<LuaTable<'lua>> {
|
config,
|
||||||
let addr: SocketAddr = (config.address, port).into();
|
|
||||||
let listener = TcpListener::bind(addr).await?;
|
|
||||||
|
|
||||||
let (lua_svc, lua_inner) = {
|
|
||||||
let rc = lua
|
|
||||||
.app_data_ref::<Weak<Lua>>()
|
|
||||||
.expect("Missing weak lua ref")
|
|
||||||
.upgrade()
|
|
||||||
.expect("Lua was dropped unexpectedly");
|
|
||||||
(Rc::clone(&rc), rc)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let keys = SvcKeys::new(lua, config.handle_request, config.handle_web_socket)?;
|
let listener = TcpListener::bind(address).await?;
|
||||||
let svc = Svc {
|
let (handle, shutdown_rx) = ServeHandle::new(address);
|
||||||
lua: lua_svc,
|
|
||||||
addr,
|
|
||||||
keys,
|
|
||||||
};
|
|
||||||
|
|
||||||
let (shutdown_tx, shutdown_rx) = tokio::sync::watch::channel(false);
|
lua.spawn_local({
|
||||||
lua.spawn_local(async move {
|
let lua = lua.clone();
|
||||||
let mut shutdown_rx_outer = shutdown_rx.clone();
|
async move {
|
||||||
loop {
|
let handle_dropped = Rc::new(Cell::new(false));
|
||||||
// Create futures for accepting new connections and shutting down
|
loop {
|
||||||
let fut_shutdown = shutdown_rx_outer.changed();
|
// 1. Keep accepting new connections until we should shutdown
|
||||||
let fut_accept = async {
|
let (conn, addr) = if handle_dropped.get() {
|
||||||
let stream = match listener.accept().await {
|
// 1a. Handle has been dropped, and we don't need to listen for shutdown
|
||||||
Err(_) => return,
|
match listener.accept().await {
|
||||||
Ok((s, _)) => s,
|
Ok(acc) => acc,
|
||||||
|
Err(_err) => {
|
||||||
|
// TODO: Propagate error somehow
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 1b. Handle is possibly active, we must listen for shutdown
|
||||||
|
match either(shutdown_rx.recv(), listener.accept()).await {
|
||||||
|
Either::Left(Ok(())) => break,
|
||||||
|
Either::Left(Err(_)) => {
|
||||||
|
// NOTE #1: We will only get a RecvError if the serve handle is dropped,
|
||||||
|
// this means lua has garbage collected it and the user does not want
|
||||||
|
// to manually stop the server using the serve handle. Run forever.
|
||||||
|
handle_dropped.set(true);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Either::Right(Ok(acc)) => acc,
|
||||||
|
Either::Right(Err(_err)) => {
|
||||||
|
// TODO: Propagate error somehow
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let io = TokioIo::new(stream);
|
// 2. For each connection, spawn a new task to handle it
|
||||||
let svc = svc.clone();
|
lua.spawn_local({
|
||||||
let mut shutdown_rx_inner = shutdown_rx.clone();
|
let rx = shutdown_rx.clone();
|
||||||
|
let io = HyperIo::from(conn);
|
||||||
|
|
||||||
lua_inner.spawn_local(async move {
|
let mut svc = service.clone();
|
||||||
let conn = http1::Builder::new()
|
svc.address = addr;
|
||||||
.keep_alive(true) // Web sockets need this
|
|
||||||
.serve_connection(io, svc)
|
let handle_dropped = Rc::clone(&handle_dropped);
|
||||||
.with_upgrades();
|
async move {
|
||||||
// NOTE: Because we need to use keep_alive for websockets, we need to
|
let conn = Http1Builder::new()
|
||||||
// also manually poll this future and handle the shutdown signal here
|
.writev(false)
|
||||||
pin!(conn);
|
.timer(HyperTimer)
|
||||||
tokio::select! {
|
.keep_alive(true)
|
||||||
_ = conn.as_mut() => {}
|
.serve_connection(io, svc)
|
||||||
_ = shutdown_rx_inner.changed() => {
|
.with_upgrades();
|
||||||
conn.as_mut().graceful_shutdown();
|
if handle_dropped.get() {
|
||||||
|
if let Err(_err) = conn.await {
|
||||||
|
// TODO: Propagate error somehow
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// NOTE #2: Because we use keep_alive for websockets above, we need to
|
||||||
|
// also manually poll this future and handle the graceful shutdown,
|
||||||
|
// otherwise the already accepted connection will linger and run
|
||||||
|
// even if the stop method has been called on the serve handle
|
||||||
|
pin!(conn);
|
||||||
|
match either(rx.recv(), conn.as_mut()).await {
|
||||||
|
Either::Left(Ok(())) => conn.as_mut().graceful_shutdown(),
|
||||||
|
Either::Left(Err(_)) => {
|
||||||
|
// Same as note #1
|
||||||
|
handle_dropped.set(true);
|
||||||
|
if let Err(_err) = conn.await {
|
||||||
|
// TODO: Propagate error somehow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Either::Right(Ok(())) => {}
|
||||||
|
Either::Right(Err(_err)) => {
|
||||||
|
// TODO: Propagate error somehow
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
|
||||||
|
|
||||||
// Wait for either a new connection or a shutdown signal
|
|
||||||
tokio::select! {
|
|
||||||
() = 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
|
|
||||||
// to manually stop the server using the serve handle. Run forever.
|
|
||||||
if res.is_ok() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
TableBuilder::new(lua)?
|
Ok(handle)
|
||||||
.with_value("ip", addr.ip().to_string())?
|
|
||||||
.with_value("port", addr.port())?
|
|
||||||
.with_function("stop", move |_, (): ()| match shutdown_tx.send(true) {
|
|
||||||
Ok(()) => Ok(()),
|
|
||||||
Err(_) => Err(LuaError::runtime("Server already stopped")),
|
|
||||||
})?
|
|
||||||
.build_readonly()
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,56 +0,0 @@
|
||||||
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,89 +0,0 @@
|
||||||
use std::str::FromStr;
|
|
||||||
|
|
||||||
use bstr::{BString, ByteSlice};
|
|
||||||
use http_body_util::Full;
|
|
||||||
use hyper::{
|
|
||||||
body::Bytes,
|
|
||||||
header::{HeaderName, HeaderValue},
|
|
||||||
HeaderMap, Response,
|
|
||||||
};
|
|
||||||
|
|
||||||
use mlua::prelude::*;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub(super) enum LuaResponseKind {
|
|
||||||
PlainText,
|
|
||||||
Table,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) struct LuaResponse {
|
|
||||||
pub(super) kind: LuaResponseKind,
|
|
||||||
pub(super) status: u16,
|
|
||||||
pub(super) headers: HeaderMap,
|
|
||||||
pub(super) body: Option<Vec<u8>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LuaResponse {
|
|
||||||
pub(super) fn into_response(self) -> LuaResult<Response<Full<Bytes>>> {
|
|
||||||
Ok(match self.kind {
|
|
||||||
LuaResponseKind::PlainText => Response::builder()
|
|
||||||
.status(200)
|
|
||||||
.header("Content-Type", "text/plain")
|
|
||||||
.body(Full::new(Bytes::from(self.body.unwrap())))
|
|
||||||
.into_lua_err()?,
|
|
||||||
LuaResponseKind::Table => {
|
|
||||||
let mut response = Response::builder()
|
|
||||||
.status(self.status)
|
|
||||||
.body(Full::new(Bytes::from(self.body.unwrap_or_default())))
|
|
||||||
.into_lua_err()?;
|
|
||||||
response.headers_mut().extend(self.headers);
|
|
||||||
response
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromLua<'_> for LuaResponse {
|
|
||||||
fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
|
|
||||||
match value {
|
|
||||||
// Plain strings from the handler are plaintext responses
|
|
||||||
LuaValue::String(s) => Ok(Self {
|
|
||||||
kind: LuaResponseKind::PlainText,
|
|
||||||
status: 200,
|
|
||||||
headers: HeaderMap::new(),
|
|
||||||
body: Some(s.as_bytes().to_vec()),
|
|
||||||
}),
|
|
||||||
// Tables are more detailed responses with potential status, headers, body
|
|
||||||
LuaValue::Table(t) => {
|
|
||||||
let status: Option<u16> = t.get("status")?;
|
|
||||||
let headers: Option<LuaTable> = t.get("headers")?;
|
|
||||||
let body: Option<BString> = t.get("body")?;
|
|
||||||
|
|
||||||
let mut headers_map = HeaderMap::new();
|
|
||||||
if let Some(headers) = headers {
|
|
||||||
for pair in headers.pairs::<String, LuaString>() {
|
|
||||||
let (h, v) = pair?;
|
|
||||||
let name = HeaderName::from_str(&h).into_lua_err()?;
|
|
||||||
let value = HeaderValue::from_bytes(v.as_bytes()).into_lua_err()?;
|
|
||||||
headers_map.insert(name, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let body_bytes = body.map(|s| s.as_bytes().to_vec());
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
kind: LuaResponseKind::Table,
|
|
||||||
status: status.unwrap_or(200),
|
|
||||||
headers: headers_map,
|
|
||||||
body: body_bytes,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
// Anything else is an error
|
|
||||||
value => Err(LuaError::FromLuaConversionError {
|
|
||||||
from: value.type_name(),
|
|
||||||
to: "NetServeResponse",
|
|
||||||
message: None,
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,82 +1,116 @@
|
||||||
use std::{future::Future, net::SocketAddr, pin::Pin, rc::Rc};
|
use std::{future::Future, net::SocketAddr, pin::Pin};
|
||||||
|
|
||||||
use http_body_util::{BodyExt, Full};
|
use async_tungstenite::{tungstenite::protocol::Role, WebSocketStream};
|
||||||
use hyper::{
|
use hyper::{
|
||||||
body::{Bytes, Incoming},
|
body::Incoming, service::Service as HyperService, Request as HyperRequest,
|
||||||
service::Service,
|
Response as HyperResponse, StatusCode,
|
||||||
Request, Response,
|
|
||||||
};
|
};
|
||||||
use hyper_tungstenite::{is_upgrade_request, upgrade};
|
|
||||||
|
|
||||||
use mlua::prelude::*;
|
use mlua::prelude::*;
|
||||||
use mlua_luau_scheduler::{LuaSchedulerExt, LuaSpawnExt};
|
use mlua_luau_scheduler::{LuaSchedulerExt, LuaSpawnExt};
|
||||||
|
|
||||||
use super::{
|
use crate::{
|
||||||
super::websocket::NetWebSocket, keys::SvcKeys, request::LuaRequest, response::LuaResponse,
|
body::ReadableBody,
|
||||||
|
server::{
|
||||||
|
config::ServeConfig,
|
||||||
|
upgrade::{is_upgrade_request, make_upgrade_response},
|
||||||
|
},
|
||||||
|
shared::{hyper::HyperIo, request::Request, response::Response, websocket::Websocket},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub(super) struct Svc {
|
pub(super) struct Service {
|
||||||
pub(super) lua: Rc<Lua>,
|
pub(super) lua: Lua,
|
||||||
pub(super) addr: SocketAddr,
|
pub(super) address: SocketAddr, // NOTE: This must be the remote address of the connected client
|
||||||
pub(super) keys: SvcKeys,
|
pub(super) config: ServeConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Service<Request<Incoming>> for Svc {
|
impl HyperService<HyperRequest<Incoming>> for Service {
|
||||||
type Response = Response<Full<Bytes>>;
|
type Response = HyperResponse<ReadableBody>;
|
||||||
type Error = LuaError;
|
type Error = LuaError;
|
||||||
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
|
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
|
||||||
|
|
||||||
fn call(&self, req: Request<Incoming>) -> Self::Future {
|
fn call(&self, req: HyperRequest<Incoming>) -> Self::Future {
|
||||||
let lua = self.lua.clone();
|
if is_upgrade_request(&req) {
|
||||||
let addr = self.addr;
|
if let Some(handler) = self.config.handle_web_socket.clone() {
|
||||||
let keys = self.keys;
|
let lua = self.lua.clone();
|
||||||
|
return Box::pin(async move {
|
||||||
|
let response = match make_upgrade_response(&req) {
|
||||||
|
Ok(res) => res,
|
||||||
|
Err(err) => {
|
||||||
|
return Ok(HyperResponse::builder()
|
||||||
|
.status(StatusCode::BAD_REQUEST)
|
||||||
|
.body(ReadableBody::from(err.to_string()))
|
||||||
|
.unwrap())
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if keys.has_websocket_handler() && is_upgrade_request(&req) {
|
lua.spawn_local({
|
||||||
Box::pin(async move {
|
let lua = lua.clone();
|
||||||
let (res, sock) = upgrade(req, None).into_lua_err()?;
|
async move {
|
||||||
|
if let Err(_err) = handle_websocket(lua, handler, req).await {
|
||||||
|
// TODO: Propagate the error somehow?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let lua_inner = lua.clone();
|
Ok(response)
|
||||||
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 handler_websocket: LuaFunction =
|
|
||||||
keys.websocket_handler(&lua_inner).unwrap().unwrap();
|
|
||||||
|
|
||||||
lua_inner
|
|
||||||
.push_thread_back(handler_websocket, lua_tab)
|
|
||||||
.unwrap();
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
Ok(res)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
let (head, body) = req.into_parts();
|
|
||||||
|
|
||||||
Box::pin(async move {
|
|
||||||
let handler_request: LuaFunction = keys.request_handler(&lua).unwrap();
|
|
||||||
|
|
||||||
let body = body.collect().await.into_lua_err()?;
|
|
||||||
let body = body.to_bytes().to_vec();
|
|
||||||
|
|
||||||
let lua_req = LuaRequest {
|
|
||||||
_remote_addr: addr,
|
|
||||||
head,
|
|
||||||
body,
|
|
||||||
};
|
|
||||||
let lua_req_table = lua_req.into_lua_table(&lua)?;
|
|
||||||
|
|
||||||
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
|
|
||||||
.get_thread_result(thread_id)
|
|
||||||
.expect("Missing handler thread result")?;
|
|
||||||
|
|
||||||
LuaResponse::from_lua_multi(thread_res, &lua)?.into_response()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let lua = self.lua.clone();
|
||||||
|
let address = self.address;
|
||||||
|
let handler = self.config.handle_request.clone();
|
||||||
|
Box::pin(async move {
|
||||||
|
match handle_request(lua, handler, req, address).await {
|
||||||
|
Ok(response) => Ok(response),
|
||||||
|
Err(_err) => {
|
||||||
|
// TODO: Propagate the error somehow?
|
||||||
|
Ok(HyperResponse::builder()
|
||||||
|
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||||
|
.body(ReadableBody::from("Lune: Internal server error"))
|
||||||
|
.unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn handle_request(
|
||||||
|
lua: Lua,
|
||||||
|
handler: LuaFunction,
|
||||||
|
request: HyperRequest<Incoming>,
|
||||||
|
address: SocketAddr,
|
||||||
|
) -> LuaResult<HyperResponse<ReadableBody>> {
|
||||||
|
let request = Request::from_incoming(request, true)
|
||||||
|
.await?
|
||||||
|
.with_address(address);
|
||||||
|
|
||||||
|
let thread_id = lua.push_thread_back(handler, request)?;
|
||||||
|
lua.track_thread(thread_id);
|
||||||
|
lua.wait_for_thread(thread_id).await;
|
||||||
|
|
||||||
|
let thread_res = lua
|
||||||
|
.get_thread_result(thread_id)
|
||||||
|
.expect("Missing handler thread result")?;
|
||||||
|
|
||||||
|
let response = Response::from_lua_multi(thread_res, &lua)?;
|
||||||
|
Ok(response.into_inner())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_websocket(
|
||||||
|
lua: Lua,
|
||||||
|
handler: LuaFunction,
|
||||||
|
request: HyperRequest<Incoming>,
|
||||||
|
) -> LuaResult<()> {
|
||||||
|
let upgraded = hyper::upgrade::on(request).await.into_lua_err()?;
|
||||||
|
|
||||||
|
let stream =
|
||||||
|
WebSocketStream::from_raw_socket(HyperIo::from(upgraded), Role::Server, None).await;
|
||||||
|
|
||||||
|
let websocket = Websocket::from(stream);
|
||||||
|
lua.push_thread_back(handler, websocket)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
56
crates/lune-std-net/src/server/upgrade.rs
Normal file
56
crates/lune-std-net/src/server/upgrade.rs
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
use async_tungstenite::tungstenite::{error::ProtocolError, handshake::derive_accept_key};
|
||||||
|
|
||||||
|
use hyper::{
|
||||||
|
body::Incoming,
|
||||||
|
header::{HeaderName, CONNECTION, UPGRADE},
|
||||||
|
HeaderMap, Request as HyperRequest, Response as HyperResponse, StatusCode,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::body::ReadableBody;
|
||||||
|
|
||||||
|
const SEC_WEBSOCKET_VERSION: HeaderName = HeaderName::from_static("sec-websocket-version");
|
||||||
|
const SEC_WEBSOCKET_KEY: HeaderName = HeaderName::from_static("sec-websocket-key");
|
||||||
|
const SEC_WEBSOCKET_ACCEPT: HeaderName = HeaderName::from_static("sec-websocket-accept");
|
||||||
|
|
||||||
|
pub fn is_upgrade_request(request: &HyperRequest<Incoming>) -> bool {
|
||||||
|
fn check_header_contains(headers: &HeaderMap, header_name: HeaderName, value: &str) -> bool {
|
||||||
|
headers.get(header_name).is_some_and(|header| {
|
||||||
|
header.to_str().map_or_else(
|
||||||
|
|_| false,
|
||||||
|
|header_str| {
|
||||||
|
header_str
|
||||||
|
.split(',')
|
||||||
|
.any(|part| part.trim().eq_ignore_ascii_case(value))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
check_header_contains(request.headers(), CONNECTION, "Upgrade")
|
||||||
|
&& check_header_contains(request.headers(), UPGRADE, "websocket")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn make_upgrade_response(
|
||||||
|
request: &HyperRequest<Incoming>,
|
||||||
|
) -> Result<HyperResponse<ReadableBody>, ProtocolError> {
|
||||||
|
let key = request
|
||||||
|
.headers()
|
||||||
|
.get(SEC_WEBSOCKET_KEY)
|
||||||
|
.ok_or(ProtocolError::MissingSecWebSocketKey)?;
|
||||||
|
|
||||||
|
if request
|
||||||
|
.headers()
|
||||||
|
.get(SEC_WEBSOCKET_VERSION)
|
||||||
|
.is_none_or(|v| v.as_bytes() != b"13")
|
||||||
|
{
|
||||||
|
return Err(ProtocolError::MissingSecWebSocketVersionHeader);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(HyperResponse::builder()
|
||||||
|
.status(StatusCode::SWITCHING_PROTOCOLS)
|
||||||
|
.header(CONNECTION, "upgrade")
|
||||||
|
.header(UPGRADE, "websocket")
|
||||||
|
.header(SEC_WEBSOCKET_ACCEPT, derive_accept_key(key.as_bytes()))
|
||||||
|
.body(ReadableBody::from("switching to websocket protocol"))
|
||||||
|
.unwrap())
|
||||||
|
}
|
19
crates/lune-std-net/src/shared/futures.rs
Normal file
19
crates/lune-std-net/src/shared/futures.rs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
use futures_lite::prelude::*;
|
||||||
|
|
||||||
|
pub use http_body_util::Either;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Combines the left and right futures into a single future
|
||||||
|
that resolves to either the left or right output.
|
||||||
|
|
||||||
|
This combinator is biased - if both futures resolve at
|
||||||
|
the same time, the left future's output is returned.
|
||||||
|
*/
|
||||||
|
pub fn either<L: Future, R: Future>(
|
||||||
|
left: L,
|
||||||
|
right: R,
|
||||||
|
) -> impl Future<Output = Either<L::Output, R::Output>> {
|
||||||
|
let fut_left = async move { Either::Left(left.await) };
|
||||||
|
let fut_right = async move { Either::Right(right.await) };
|
||||||
|
fut_left.or(fut_right)
|
||||||
|
}
|
88
crates/lune-std-net/src/shared/headers.rs
Normal file
88
crates/lune-std-net/src/shared/headers.rs
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use hyper::{
|
||||||
|
header::{CONTENT_ENCODING, CONTENT_LENGTH},
|
||||||
|
HeaderMap,
|
||||||
|
};
|
||||||
|
|
||||||
|
use lune_utils::TableBuilder;
|
||||||
|
use mlua::prelude::*;
|
||||||
|
|
||||||
|
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(
|
||||||
|
lua: &Lua,
|
||||||
|
headers: HeaderMap,
|
||||||
|
remove_content_headers: bool,
|
||||||
|
) -> LuaResult<LuaTable> {
|
||||||
|
let mut string_map = HashMap::<String, Vec<String>>::new();
|
||||||
|
|
||||||
|
for (name, value) in headers {
|
||||||
|
if let Some(name) = name {
|
||||||
|
if let Ok(value) = value.to_str() {
|
||||||
|
string_map
|
||||||
|
.entry(name.to_string())
|
||||||
|
.or_default()
|
||||||
|
.push(value.to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hash_map_to_table(lua, string_map, remove_content_headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hash_map_to_table(
|
||||||
|
lua: &Lua,
|
||||||
|
map: impl IntoIterator<Item = (String, Vec<String>)>,
|
||||||
|
remove_content_headers: bool,
|
||||||
|
) -> LuaResult<LuaTable> {
|
||||||
|
let mut string_map = HashMap::<String, Vec<String>>::new();
|
||||||
|
for (name, values) in map {
|
||||||
|
let name = name.as_str();
|
||||||
|
|
||||||
|
if remove_content_headers {
|
||||||
|
let content_encoding_header_str = CONTENT_ENCODING.as_str();
|
||||||
|
let content_length_header_str = CONTENT_LENGTH.as_str();
|
||||||
|
if name == content_encoding_header_str || name == content_length_header_str {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for value in values {
|
||||||
|
let value = value.as_str();
|
||||||
|
string_map
|
||||||
|
.entry(name.to_owned())
|
||||||
|
.or_default()
|
||||||
|
.push(value.to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut builder = TableBuilder::new(lua.clone())?;
|
||||||
|
for (name, mut values) in string_map {
|
||||||
|
if values.len() == 1 {
|
||||||
|
let value = values.pop().unwrap().into_lua(lua)?;
|
||||||
|
builder = builder.with_value(name, value)?;
|
||||||
|
} else {
|
||||||
|
let values = TableBuilder::new(lua.clone())?
|
||||||
|
.with_sequential_values(values)?
|
||||||
|
.build_readonly()?
|
||||||
|
.into_lua(lua)?;
|
||||||
|
builder = builder.with_value(name, values)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.build_readonly()
|
||||||
|
}
|
198
crates/lune-std-net/src/shared/hyper.rs
Normal file
198
crates/lune-std-net/src/shared/hyper.rs
Normal file
|
@ -0,0 +1,198 @@
|
||||||
|
use std::{
|
||||||
|
future::Future,
|
||||||
|
io,
|
||||||
|
pin::Pin,
|
||||||
|
slice,
|
||||||
|
task::{Context, Poll},
|
||||||
|
time::{Duration, Instant},
|
||||||
|
};
|
||||||
|
|
||||||
|
use async_io::Timer;
|
||||||
|
use futures_lite::{prelude::*, ready};
|
||||||
|
use hyper::rt::{self, Executor, ReadBuf, ReadBufCursor};
|
||||||
|
use mlua::prelude::*;
|
||||||
|
use mlua_luau_scheduler::LuaSpawnExt;
|
||||||
|
|
||||||
|
// Hyper executor that spawns futures onto our Lua scheduler
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct HyperExecutor {
|
||||||
|
lua: Lua,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
impl HyperExecutor {
|
||||||
|
pub fn execute<Fut>(lua: Lua, fut: Fut)
|
||||||
|
where
|
||||||
|
Fut: Future + Send + 'static,
|
||||||
|
Fut::Output: Send + 'static,
|
||||||
|
{
|
||||||
|
let exec = if let Some(exec) = lua.app_data_ref::<Self>() {
|
||||||
|
exec
|
||||||
|
} else {
|
||||||
|
lua.set_app_data(Self { lua: lua.clone() });
|
||||||
|
lua.app_data_ref::<Self>().unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
exec.execute(fut);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Fut: Future + Send + 'static> rt::Executor<Fut> for HyperExecutor
|
||||||
|
where
|
||||||
|
Fut::Output: Send + 'static,
|
||||||
|
{
|
||||||
|
fn execute(&self, fut: Fut) {
|
||||||
|
self.lua.spawn(fut).detach();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hyper timer & sleep future wrapper for async-io
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct HyperTimer;
|
||||||
|
|
||||||
|
impl rt::Timer for HyperTimer {
|
||||||
|
fn sleep(&self, duration: Duration) -> Pin<Box<dyn rt::Sleep>> {
|
||||||
|
Box::pin(HyperSleep::from(Timer::after(duration)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sleep_until(&self, at: Instant) -> Pin<Box<dyn rt::Sleep>> {
|
||||||
|
Box::pin(HyperSleep::from(Timer::at(at)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset(&self, sleep: &mut Pin<Box<dyn rt::Sleep>>, new_deadline: Instant) {
|
||||||
|
if let Some(mut sleep) = sleep.as_mut().downcast_mut_pin::<HyperSleep>() {
|
||||||
|
sleep.inner.set_at(new_deadline);
|
||||||
|
} else {
|
||||||
|
*sleep = Box::pin(HyperSleep::from(Timer::at(new_deadline)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct HyperSleep {
|
||||||
|
inner: Timer,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Timer> for HyperSleep {
|
||||||
|
fn from(inner: Timer) -> Self {
|
||||||
|
Self { inner }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Future for HyperSleep {
|
||||||
|
type Output = ();
|
||||||
|
|
||||||
|
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
|
||||||
|
match Pin::new(&mut self.inner).poll(cx) {
|
||||||
|
Poll::Ready(_) => Poll::Ready(()),
|
||||||
|
Poll::Pending => Poll::Pending,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl rt::Sleep for HyperSleep {}
|
||||||
|
|
||||||
|
// Hyper I/O wrapper for bidirectional compatibility
|
||||||
|
// between hyper & futures-lite async read/write traits
|
||||||
|
|
||||||
|
pin_project_lite::pin_project! {
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct HyperIo<T> {
|
||||||
|
#[pin]
|
||||||
|
inner: T
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> From<T> for HyperIo<T> {
|
||||||
|
fn from(inner: T) -> Self {
|
||||||
|
Self { inner }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> HyperIo<T> {
|
||||||
|
pub fn pin_mut(self: Pin<&mut Self>) -> Pin<&mut T> {
|
||||||
|
self.project().inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compat for futures-lite -> hyper runtime
|
||||||
|
|
||||||
|
impl<T: AsyncRead> rt::Read for HyperIo<T> {
|
||||||
|
fn poll_read(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
mut buf: ReadBufCursor<'_>,
|
||||||
|
) -> Poll<io::Result<()>> {
|
||||||
|
// Fill the read buffer with initialized data
|
||||||
|
let read_slice = unsafe {
|
||||||
|
let buffer = buf.as_mut();
|
||||||
|
buffer.as_mut_ptr().write_bytes(0, buffer.len());
|
||||||
|
slice::from_raw_parts_mut(buffer.as_mut_ptr().cast::<u8>(), buffer.len())
|
||||||
|
};
|
||||||
|
|
||||||
|
// Read bytes from the underlying source
|
||||||
|
let n = match self.pin_mut().poll_read(cx, read_slice) {
|
||||||
|
Poll::Ready(Ok(n)) => n,
|
||||||
|
Poll::Ready(Err(e)) => return Poll::Ready(Err(e)),
|
||||||
|
Poll::Pending => return Poll::Pending,
|
||||||
|
};
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
buf.advance(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
Poll::Ready(Ok(()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: AsyncWrite> rt::Write for HyperIo<T> {
|
||||||
|
fn poll_write(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
buf: &[u8],
|
||||||
|
) -> Poll<io::Result<usize>> {
|
||||||
|
self.pin_mut().poll_write(cx, buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||||
|
self.pin_mut().poll_flush(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||||
|
self.pin_mut().poll_close(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compat for hyper runtime -> futures-lite
|
||||||
|
|
||||||
|
impl<T: rt::Read> AsyncRead for HyperIo<T> {
|
||||||
|
fn poll_read(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
buf: &mut [u8],
|
||||||
|
) -> Poll<io::Result<usize>> {
|
||||||
|
let mut buf = ReadBuf::new(buf);
|
||||||
|
ready!(self.pin_mut().poll_read(cx, buf.unfilled()))?;
|
||||||
|
Poll::Ready(Ok(buf.filled().len()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: rt::Write> AsyncWrite for HyperIo<T> {
|
||||||
|
fn poll_write(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
buf: &[u8],
|
||||||
|
) -> Poll<Result<usize, std::io::Error>> {
|
||||||
|
self.pin_mut().poll_write(cx, buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), std::io::Error>> {
|
||||||
|
self.pin_mut().poll_flush(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
|
||||||
|
self.pin_mut().poll_shutdown(cx)
|
||||||
|
}
|
||||||
|
}
|
41
crates/lune-std-net/src/shared/lua.rs
Normal file
41
crates/lune-std-net/src/shared/lua.rs
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
use hyper::{
|
||||||
|
header::{HeaderName, HeaderValue},
|
||||||
|
HeaderMap, Method,
|
||||||
|
};
|
||||||
|
|
||||||
|
use mlua::prelude::*;
|
||||||
|
|
||||||
|
pub fn lua_value_to_method(value: &LuaValue) -> LuaResult<Method> {
|
||||||
|
match value {
|
||||||
|
LuaValue::Nil => Ok(Method::GET),
|
||||||
|
LuaValue::String(str) => {
|
||||||
|
let bytes = str.as_bytes().trim_ascii().to_ascii_uppercase();
|
||||||
|
Method::from_bytes(&bytes).into_lua_err()
|
||||||
|
}
|
||||||
|
LuaValue::Buffer(buf) => {
|
||||||
|
let bytes = buf.to_vec().trim_ascii().to_ascii_uppercase();
|
||||||
|
Method::from_bytes(&bytes).into_lua_err()
|
||||||
|
}
|
||||||
|
v => Err(LuaError::FromLuaConversionError {
|
||||||
|
from: v.type_name(),
|
||||||
|
to: "Method".to_string(),
|
||||||
|
message: Some(format!(
|
||||||
|
"Invalid method - expected string or buffer, got {}",
|
||||||
|
v.type_name()
|
||||||
|
)),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lua_table_to_header_map(table: &LuaTable) -> LuaResult<HeaderMap> {
|
||||||
|
let mut headers = HeaderMap::new();
|
||||||
|
|
||||||
|
for pair in table.pairs::<LuaString, LuaString>() {
|
||||||
|
let (key, val) = pair?;
|
||||||
|
let key = HeaderName::from_bytes(&key.as_bytes()).into_lua_err()?;
|
||||||
|
let val = HeaderValue::from_bytes(&val.as_bytes()).into_lua_err()?;
|
||||||
|
headers.insert(key, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(headers)
|
||||||
|
}
|
7
crates/lune-std-net/src/shared/mod.rs
Normal file
7
crates/lune-std-net/src/shared/mod.rs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
pub mod futures;
|
||||||
|
pub mod headers;
|
||||||
|
pub mod hyper;
|
||||||
|
pub mod lua;
|
||||||
|
pub mod request;
|
||||||
|
pub mod response;
|
||||||
|
pub mod websocket;
|
256
crates/lune-std-net/src/shared/request.rs
Normal file
256
crates/lune-std-net/src/shared/request.rs
Normal file
|
@ -0,0 +1,256 @@
|
||||||
|
use std::{collections::HashMap, net::SocketAddr};
|
||||||
|
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
use hyper::{body::Incoming, HeaderMap, Method, Request as HyperRequest};
|
||||||
|
|
||||||
|
use mlua::prelude::*;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
body::{handle_incoming_body, ReadableBody},
|
||||||
|
shared::{
|
||||||
|
headers::{hash_map_to_table, header_map_to_table},
|
||||||
|
lua::{lua_table_to_header_map, lua_value_to_method},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct RequestOptions {
|
||||||
|
pub decompress: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for RequestOptions {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self { decompress: true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromLua for RequestOptions {
|
||||||
|
fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
|
||||||
|
if let LuaValue::Nil = value {
|
||||||
|
// Nil means default options
|
||||||
|
Ok(Self::default())
|
||||||
|
} else if let LuaValue::Table(tab) = value {
|
||||||
|
// Table means custom options
|
||||||
|
let decompress = match tab.get::<Option<bool>>("decompress") {
|
||||||
|
Ok(decomp) => Ok(decomp.unwrap_or(true)),
|
||||||
|
Err(_) => Err(LuaError::RuntimeError(
|
||||||
|
"Invalid option value for 'decompress' in request options".to_string(),
|
||||||
|
)),
|
||||||
|
}?;
|
||||||
|
Ok(Self { decompress })
|
||||||
|
} else {
|
||||||
|
// Anything else is invalid
|
||||||
|
Err(LuaError::FromLuaConversionError {
|
||||||
|
from: value.type_name(),
|
||||||
|
to: "RequestOptions".to_string(),
|
||||||
|
message: Some(format!(
|
||||||
|
"Invalid request options - expected table or nil, got {}",
|
||||||
|
value.type_name()
|
||||||
|
)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Request {
|
||||||
|
pub(crate) inner: HyperRequest<ReadableBody>,
|
||||||
|
pub(crate) address: Option<SocketAddr>,
|
||||||
|
pub(crate) redirects: Option<usize>,
|
||||||
|
pub(crate) decompress: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Request {
|
||||||
|
/**
|
||||||
|
Creates a new request from a raw incoming request.
|
||||||
|
*/
|
||||||
|
pub async fn from_incoming(
|
||||||
|
incoming: HyperRequest<Incoming>,
|
||||||
|
decompress: bool,
|
||||||
|
) -> LuaResult<Self> {
|
||||||
|
let (parts, body) = incoming.into_parts();
|
||||||
|
|
||||||
|
let (body, decompress) = handle_incoming_body(&parts.headers, body, decompress).await?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
inner: HyperRequest::from_parts(parts, ReadableBody::from(body)),
|
||||||
|
address: None,
|
||||||
|
redirects: None,
|
||||||
|
decompress,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Attaches a socket address to the request.
|
||||||
|
|
||||||
|
This will make the `ip` and `port` fields available on the request.
|
||||||
|
*/
|
||||||
|
pub fn with_address(mut self, address: SocketAddr) -> Self {
|
||||||
|
self.address = Some(address);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Returns the method of the request.
|
||||||
|
*/
|
||||||
|
pub fn method(&self) -> Method {
|
||||||
|
self.inner.method().clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Returns the path of the request.
|
||||||
|
*/
|
||||||
|
pub fn path(&self) -> &str {
|
||||||
|
self.inner.uri().path()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Returns the query parameters of the request.
|
||||||
|
*/
|
||||||
|
pub fn query(&self) -> HashMap<String, Vec<String>> {
|
||||||
|
let uri = self.inner.uri();
|
||||||
|
|
||||||
|
let mut result = HashMap::<String, Vec<String>>::new();
|
||||||
|
|
||||||
|
if let Some(query) = uri.query() {
|
||||||
|
for (key, value) in form_urlencoded::parse(query.as_bytes()) {
|
||||||
|
result
|
||||||
|
.entry(key.to_string())
|
||||||
|
.or_default()
|
||||||
|
.push(value.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Returns the headers of the request.
|
||||||
|
*/
|
||||||
|
pub fn headers(&self) -> &HeaderMap {
|
||||||
|
self.inner.headers()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Returns the body of the request.
|
||||||
|
*/
|
||||||
|
pub fn body(&self) -> &[u8] {
|
||||||
|
self.inner.body().as_slice()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Clones the inner `hyper` request.
|
||||||
|
*/
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn clone_inner(&self) -> HyperRequest<ReadableBody> {
|
||||||
|
self.inner.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Takes the inner `hyper` request by ownership.
|
||||||
|
*/
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn into_inner(self) -> HyperRequest<ReadableBody> {
|
||||||
|
self.inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromLua for Request {
|
||||||
|
fn from_lua(value: LuaValue, lua: &Lua) -> LuaResult<Self> {
|
||||||
|
if let LuaValue::String(s) = value {
|
||||||
|
// If we just got a string we assume
|
||||||
|
// its a GET request to a given url
|
||||||
|
let uri = s.to_str()?;
|
||||||
|
let uri = uri.parse().into_lua_err()?;
|
||||||
|
|
||||||
|
let mut request = HyperRequest::new(ReadableBody::empty());
|
||||||
|
*request.uri_mut() = uri;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
inner: request,
|
||||||
|
address: None,
|
||||||
|
redirects: None,
|
||||||
|
decompress: RequestOptions::default().decompress,
|
||||||
|
})
|
||||||
|
} else if let LuaValue::Table(tab) = value {
|
||||||
|
// If we got a table we are able to configure the
|
||||||
|
// entire request, maybe with extra options too
|
||||||
|
let options = match tab.get::<LuaValue>("options") {
|
||||||
|
Ok(opts) => RequestOptions::from_lua(opts, lua)?,
|
||||||
|
Err(_) => RequestOptions::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Extract url (required) + optional structured query params
|
||||||
|
let url = tab.get::<LuaString>("url")?;
|
||||||
|
let mut url = url.to_str()?.parse::<Url>().into_lua_err()?;
|
||||||
|
if let Some(t) = tab.get::<Option<LuaTable>>("query")? {
|
||||||
|
let mut query = url.query_pairs_mut();
|
||||||
|
for pair in t.pairs::<LuaString, LuaString>() {
|
||||||
|
let (key, value) = pair?;
|
||||||
|
let key = key.to_str()?;
|
||||||
|
let value = value.to_str()?;
|
||||||
|
query.append_pair(&key, &value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract method
|
||||||
|
let method = tab.get::<LuaValue>("method")?;
|
||||||
|
let method = lua_value_to_method(&method)?;
|
||||||
|
|
||||||
|
// Extract headers
|
||||||
|
let headers = tab.get::<Option<LuaTable>>("headers")?;
|
||||||
|
let headers = headers
|
||||||
|
.map(|t| lua_table_to_header_map(&t))
|
||||||
|
.transpose()?
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
// Extract body
|
||||||
|
let body = tab.get::<ReadableBody>("body")?;
|
||||||
|
|
||||||
|
// Build the full request
|
||||||
|
let mut request = HyperRequest::new(body);
|
||||||
|
request.headers_mut().extend(headers);
|
||||||
|
*request.uri_mut() = url.to_string().parse().unwrap();
|
||||||
|
*request.method_mut() = method;
|
||||||
|
|
||||||
|
// All good, validated and we got what we need
|
||||||
|
Ok(Self {
|
||||||
|
inner: request,
|
||||||
|
address: None,
|
||||||
|
redirects: None,
|
||||||
|
decompress: options.decompress,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// Anything else is invalid
|
||||||
|
Err(LuaError::FromLuaConversionError {
|
||||||
|
from: value.type_name(),
|
||||||
|
to: "Request".to_string(),
|
||||||
|
message: Some(format!(
|
||||||
|
"Invalid request - expected string or table, got {}",
|
||||||
|
value.type_name()
|
||||||
|
)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LuaUserData for Request {
|
||||||
|
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
|
||||||
|
fields.add_field_method_get("ip", |_, this| {
|
||||||
|
Ok(this.address.map(|address| address.ip().to_string()))
|
||||||
|
});
|
||||||
|
fields.add_field_method_get("port", |_, this| {
|
||||||
|
Ok(this.address.map(|address| address.port()))
|
||||||
|
});
|
||||||
|
fields.add_field_method_get("method", |_, this| Ok(this.method().to_string()));
|
||||||
|
fields.add_field_method_get("path", |_, this| Ok(this.path().to_string()));
|
||||||
|
fields.add_field_method_get("query", |lua, this| {
|
||||||
|
hash_map_to_table(lua, this.query(), false)
|
||||||
|
});
|
||||||
|
fields.add_field_method_get("headers", |lua, this| {
|
||||||
|
header_map_to_table(lua, this.headers().clone(), this.decompress)
|
||||||
|
});
|
||||||
|
fields.add_field_method_get("body", |lua, this| lua.create_string(this.body()));
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue