Compare commits

..

No commits in common. "main" and "v0.8.5" have entirely different histories.
main ... v0.8.5

301 changed files with 4447 additions and 11516 deletions

4
.gitattributes vendored
View file

@ -1,5 +1,9 @@
* text=auto
# Temporarily highlight luau as normal lua files
# until we get native linguist support for Luau
*.luau linguist-language=Lua
# Ensure all lua files use LF
*.lua eol=lf
*.luau eol=lf

View file

@ -2,16 +2,15 @@ name: CI
on:
push:
pull_request:
workflow_dispatch:
defaults:
run:
shell: bash
env:
CARGO_TERM_COLOR: always
jobs:
fmt:
name: Check formatting
runs-on: ubuntu-latest
@ -24,8 +23,11 @@ jobs:
with:
components: rustfmt
- name: Install Just
uses: extractions/setup-just@v2
- name: Install Tooling
uses: CompeyDev/setup-rokit@v0.1.2
uses: ok-nick/setup-aftman@v0.4.2
- name: Check Formatting
run: just fmt-check
@ -38,8 +40,11 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v4
- name: Install Just
uses: extractions/setup-just@v2
- name: Install Tooling
uses: CompeyDev/setup-rokit@v0.1.2
uses: ok-nick/setup-aftman@v0.4.2
- name: Analyze
run: just analyze
@ -80,26 +85,20 @@ jobs:
components: clippy
targets: ${{ matrix.cargo-target }}
- name: Install binstall
uses: cargo-bins/cargo-binstall@main
- name: Install nextest
run: cargo binstall cargo-nextest
- name: Build
run: |
cargo build --workspace \
cargo build \
--locked --all-features \
--target ${{ matrix.cargo-target }}
- name: Lint
run: |
cargo clippy --workspace \
cargo clippy \
--locked --all-features \
--target ${{ matrix.cargo-target }}
- name: Test
run: |
cargo nextest run --no-fail-fast \
cargo test \
--locked --all-features \
--target ${{ matrix.cargo-target }}

View file

@ -46,7 +46,7 @@ jobs:
registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
build:
needs: ["init"] # , "dry-run"]
needs: ["init", "dry-run"]
strategy:
fail-fast: false
matrix:
@ -112,7 +112,7 @@ jobs:
release-github:
name: Release (GitHub)
runs-on: ubuntu-latest
needs: ["init", "build"] # , "dry-run", "build"]
needs: ["init", "dry-run", "build"]
steps:
- name: Checkout repository
uses: actions/checkout@v4

5
.gitignore vendored
View file

@ -21,12 +21,7 @@ lune.yml
luneDocs.json
luneTypes.d.luau
# Dirs generated by runtime or build scripts
/types
# Files generated by runtime or build scripts
scripts/brick_color.rs
scripts/font_enum_map.rs
scripts/physical_properties_enum_map.rs

View file

@ -45,7 +45,7 @@ test-bin *ARGS:
fmt:
#!/usr/bin/env bash
set -euo pipefail
stylua .lune crates scripts tests \
stylua .lune scripts tests types \
--glob "tests/**/*.luau" \
--glob "!tests/roblox/rbx-test-files/**"
cargo fmt
@ -55,7 +55,7 @@ fmt:
fmt-check:
#!/usr/bin/env bash
set -euo pipefail
stylua .lune crates scripts tests \
stylua .lune scripts tests types \
--glob "tests/**/*.luau" \
--glob "!tests/roblox/rbx-test-files/**"
cargo fmt --check
@ -65,11 +65,10 @@ fmt-check:
analyze:
#!/usr/bin/env bash
set -euo pipefail
lune run scripts/analyze_copy_typedefs
luau-lsp analyze \
--settings=".vscode/settings.json" \
--ignore="tests/roblox/rbx-test-files/**" \
.lune crates scripts tests
.lune scripts tests types
# Zips up the built binary into a single zip file
[no-exit-message]

View file

@ -1,7 +1,6 @@
local fs = require("@lune/fs")
local net = require("@lune/net")
local process = require("@lune/process")
local serde = require("@lune/serde")
local stdio = require("@lune/stdio")
local task = require("@lune/task")
@ -130,7 +129,7 @@ end
]]
print("Sending 4 pings to google 🌏")
local result = process.exec("ping", {
local result = process.spawn("ping", {
"google.com",
"-c 4",
})
@ -146,8 +145,10 @@ local result = process.exec("ping", {
if result.ok then
assert(#result.stdout > 0, "Result output was empty")
local min, avg, max, stddev =
string.match(result.stdout, "min/avg/max/stddev = ([%d%.]+)/([%d%.]+)/([%d%.]+)/([%d%.]+) ms")
local min, avg, max, stddev = string.match(
result.stdout,
"min/avg/max/stddev = ([%d%.]+)/([%d%.]+)/([%d%.]+)/([%d%.]+) ms"
)
print(string.format("Minimum ping time: %.3fms", assert(tonumber(min))))
print(string.format("Maximum ping time: %.3fms", assert(tonumber(max))))
print(string.format("Average ping time: %.3fms", assert(tonumber(avg))))
@ -171,7 +172,7 @@ local apiResult = net.request({
headers = {
["Content-Type"] = "application/json",
} :: { [string]: string },
body = serde.encode("json", {
body = net.jsonEncode({
title = "foo",
body = "bar",
}),
@ -191,7 +192,7 @@ type ApiResponse = {
userId: number,
}
local apiResponse: ApiResponse = serde.decode("json", apiResult.body)
local apiResponse: ApiResponse = net.jsonDecode(apiResult.body)
assert(apiResponse.title == "foo", "Invalid json response")
assert(apiResponse.body == "bar", "Invalid json response")
print("Got valid JSON response with changes applied")

View file

@ -3,6 +3,7 @@
local net = require("@lune/net")
local process = require("@lune/process")
local task = require("@lune/task")
local PORT = if process.env.PORT ~= nil and #process.env.PORT > 0
then assert(tonumber(process.env.PORT), "Failed to parse port from env")
@ -10,10 +11,6 @@ local PORT = if process.env.PORT ~= nil and #process.env.PORT > 0
-- Create our responder functions
local function root(_request: net.ServeRequest): string
return `Hello from Lune server!`
end
local function pong(request: net.ServeRequest): string
return `Pong!\n{request.path}\n{request.body}`
end
@ -32,12 +29,10 @@ local function notFound(_request: net.ServeRequest): net.ServeResponse
}
end
-- Run the server on the port forever
-- Run the server on port 8080
net.serve(PORT, function(request)
if request.path == "/" then
return root(request)
elseif string.sub(request.path, 1, 5) == "/ping" then
local handle = net.serve(PORT, function(request)
if string.sub(request.path, 1, 5) == "/ping" then
return pong(request)
elseif string.sub(request.path, 1, 7) == "/teapot" then
return teapot(request)
@ -47,4 +42,12 @@ net.serve(PORT, function(request)
end)
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)

View file

@ -28,8 +28,8 @@ end)
for _ = 1, 5 do
local start = os.clock()
socket:send(tostring(1))
local response = socket:next()
socket.send(tostring(1))
local response = socket.next()
local elapsed = os.clock() - start
print(`Got response '{response}' in {elapsed * 1_000} milliseconds`)
task.wait(1 - elapsed)
@ -38,7 +38,7 @@ end
-- Everything went well, and we are done with the socket, so we can close it
print("Closing web socket...")
socket:close()
socket.close()
task.cancel(forceExit)
print("Done! 🌙")

View file

@ -15,9 +15,9 @@ local handle = net.serve(PORT, {
handleWebSocket = function(socket)
print("Got new web socket connection!")
repeat
local message = socket:next()
local message = socket.next()
if message ~= nil then
socket:send("Echo - " .. message)
socket.send("Echo - " .. message)
end
until message == nil
print("Web socket disconnected.")

View file

@ -8,220 +8,6 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## 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
### Added
- Added a compression level option to `serde.compress` ([#224])
- Added missing vector methods to the `roblox` library ([#228])
### Changed
- Updated to Luau version `0.635`
- Updated to rbx-dom database version `0.634`
### Fixed
- Fixed `fs.readDir` with trailing forward-slash on Windows ([#220])
- Fixed `__type` and `__tostring` metamethods not always being respected when formatting tables
[#220]: https://github.com/lune-org/lune/pull/220
[#224]: https://github.com/lune-org/lune/pull/224
[#228]: https://github.com/lune-org/lune/pull/228
## `0.8.6` - June 23rd, 2024
### Added
- Added a builtin API for hashing and calculating HMACs as part of the `serde` library ([#193])
Basic usage:
```lua
local serde = require("@lune/serde")
local hash = serde.hash("sha256", "a message to hash")
local hmac = serde.hmac("sha256", "a message to hash", "a secret string")
print(hash)
print(hmac)
```
The returned hashes are sequences of lowercase hexadecimal digits. The following algorithms are supported:
`md5`, `sha1`, `sha224`, `sha256`, `sha384`, `sha512`, `sha3-224`, `sha3-256`, `sha3-384`, `sha3-512`, `blake3`
- Added two new options to `luau.load`:
- `codegenEnabled` - whether or not codegen should be enabled for the loaded chunk.
- `injectGlobals` - whether or not to inject globals into a passed `environment`.
By default, globals are injected and codegen is disabled.
Check the documentation for the `luau` standard library for more information.
- Implemented support for floor division operator / `__idiv` for the `Vector2` and `Vector3` types in the `roblox` standard library ([#196])
- Fixed the `_VERSION` global containing an incorrect Lune version string.
### Changed
- Sandboxing and codegen in the Luau VM is now fully enabled, resulting in up to 2x or faster code execution.
This should not result in any behavior differences in Lune, but if it does, please open an issue.
- Improved formatting of custom error objects (such as when `fs.readFile` returns an error) when printed or formatted using `stdio.format`.
### Fixed
- Fixed `__type` and `__tostring` metamethods on userdatas and tables not being respected when printed or formatted using `stdio.format`.
[#193]: https://github.com/lune-org/lune/pull/193
[#196]: https://github.com/lune-org/lune/pull/196
## `0.8.5` - June 1st, 2024
### Changed

2837
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -16,7 +16,6 @@ members = [
"crates/lune-std-stdio",
"crates/lune-std-task",
"crates/lune-utils",
"crates/mlua-luau-scheduler",
]
# Profile for building the release binary, with the following options set:

View file

@ -33,7 +33,7 @@ Lune provides fully asynchronous APIs wherever possible, and is built in Rust
## Features
- 🌙 Strictly minimal but powerful interface that is easy to read and remember, just like Luau itself
- 🧰 Fully featured APIs for the filesystem, networking, stdio, all included in the small (~5mb zipped) executable
- 🧰 Fully featured APIs for the filesystem, networking, stdio, all included in the small (~5mb) executable
- 📚 World-class documentation, on the web _or_ directly in your editor, no network connection necessary
- 🏡 Familiar runtime environment for Roblox developers, with an included 1-to-1 task scheduler port
- ✏️ Optional built-in library for manipulating Roblox place & model files, and their instances

4
aftman.toml Normal file
View file

@ -0,0 +1,4 @@
[tools]
luau-lsp = "JohnnyMorganz/luau-lsp@1.29.0"
selene = "Kampfkarren/selene@0.27.1"
stylua = "JohnnyMorganz/StyLua@0.20.0"

View file

@ -1,6 +1,6 @@
[package]
name = "lune-roblox"
version = "0.2.2"
version = "0.1.1"
edition = "2021"
license = "MPL-2.0"
repository = "https://github.com/lune-org/lune"
@ -13,16 +13,17 @@ path = "src/lib.rs"
workspace = true
[dependencies]
mlua = { version = "0.10.3", features = ["luau"] }
mlua = { version = "0.9.7", features = ["luau"] }
glam = "0.30"
rand = "0.9"
thiserror = "2.0"
glam = "0.27"
rand = "0.8"
thiserror = "1.0"
once_cell = "1.17"
rbx_binary = "1.0"
rbx_dom_weak = "3.0"
rbx_reflection = "5.0"
rbx_reflection_database = "1.0"
rbx_xml = "1.0"
rbx_binary = "0.7.3"
rbx_dom_weak = "2.6.0"
rbx_reflection = "4.4.0"
rbx_reflection_database = "0.2.9"
rbx_xml = "0.13.2"
lune-utils = { version = "0.2.2", path = "../lune-utils" }
lune-utils = { version = "0.1.0", path = "../lune-utils" }

View file

@ -47,7 +47,6 @@ pub fn ensure_valid_attribute_value(value: &DomValue) -> LuaResult<()> {
| DomType::CFrame
| DomType::Color3
| DomType::ColorSequence
| DomType::EnumItem
| DomType::Float32
| DomType::Float64
| DomType::Font

View file

@ -6,7 +6,7 @@ use crate::{datatypes::extension::DomValueExt, instance::Instance};
use super::*;
pub(crate) trait LuaToDomValue {
pub(crate) trait LuaToDomValue<'lua> {
/**
Converts a lua value into a weak dom value.
@ -15,16 +15,16 @@ pub(crate) trait LuaToDomValue {
*/
fn lua_to_dom_value(
&self,
lua: &Lua,
lua: &'lua Lua,
variant_type: Option<DomType>,
) -> DomConversionResult<DomValue>;
}
pub(crate) trait DomValueToLua: Sized {
pub(crate) trait DomValueToLua<'lua>: Sized {
/**
Converts a weak dom value into a lua value.
*/
fn dom_value_to_lua(lua: &Lua, variant: &DomValue) -> DomConversionResult<Self>;
fn dom_value_to_lua(lua: &'lua Lua, variant: &DomValue) -> DomConversionResult<Self>;
}
/*
@ -37,8 +37,8 @@ pub(crate) trait DomValueToLua: Sized {
*/
impl DomValueToLua for LuaValue {
fn dom_value_to_lua(lua: &Lua, variant: &DomValue) -> DomConversionResult<Self> {
impl<'lua> DomValueToLua<'lua> for LuaValue<'lua> {
fn dom_value_to_lua(lua: &'lua Lua, variant: &DomValue) -> DomConversionResult<Self> {
use rbx_dom_weak::types as dom;
match LuaAnyUserData::dom_value_to_lua(lua, variant) {
@ -51,7 +51,7 @@ impl DomValueToLua for LuaValue {
DomValue::Float32(n) => Ok(LuaValue::Number(*n as f64)),
DomValue::String(s) => Ok(LuaValue::String(lua.create_string(s)?)),
DomValue::BinaryString(s) => Ok(LuaValue::String(lua.create_string(s)?)),
DomValue::ContentId(s) => Ok(LuaValue::String(
DomValue::Content(s) => Ok(LuaValue::String(
lua.create_string(AsRef::<str>::as_ref(s))?,
)),
@ -76,10 +76,10 @@ impl DomValueToLua for LuaValue {
}
}
impl LuaToDomValue for LuaValue {
impl<'lua> LuaToDomValue<'lua> for LuaValue<'lua> {
fn lua_to_dom_value(
&self,
lua: &Lua,
lua: &'lua Lua,
variant_type: Option<DomType>,
) -> DomConversionResult<DomValue> {
use rbx_dom_weak::types as dom;
@ -102,10 +102,10 @@ impl LuaToDomValue for LuaValue {
Ok(DomValue::String(s.to_str()?.to_string()))
}
(LuaValue::String(s), DomType::BinaryString) => {
Ok(DomValue::BinaryString(s.as_bytes().to_vec().into()))
Ok(DomValue::BinaryString(s.as_ref().into()))
}
(LuaValue::String(s), DomType::ContentId) => {
Ok(DomValue::ContentId(s.to_str()?.to_string().into()))
(LuaValue::String(s), DomType::Content) => {
Ok(DomValue::Content(s.to_str()?.to_string().into()))
}
// NOTE: Some values are either optional or default and we
@ -186,9 +186,9 @@ macro_rules! userdata_to_dom {
};
}
impl DomValueToLua for LuaAnyUserData {
impl<'lua> DomValueToLua<'lua> for LuaAnyUserData<'lua> {
#[rustfmt::skip]
fn dom_value_to_lua(lua: &Lua, variant: &DomValue) -> DomConversionResult<Self> {
fn dom_value_to_lua(lua: &'lua Lua, variant: &DomValue) -> DomConversionResult<Self> {
use super::types::*;
use rbx_dom_weak::types as dom;
@ -200,8 +200,6 @@ impl DomValueToLua for LuaAnyUserData {
DomValue::Color3(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::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::Font(value) => dom_to_userdata!(lua, value => Font),
DomValue::NumberRange(value) => dom_to_userdata!(lua, value => NumberRange),
@ -235,9 +233,13 @@ impl DomValueToLua for LuaAnyUserData {
}
}
impl LuaToDomValue for LuaAnyUserData {
impl<'lua> LuaToDomValue<'lua> for LuaAnyUserData<'lua> {
#[rustfmt::skip]
fn lua_to_dom_value(&self, _: &Lua, variant_type: Option<DomType>) -> DomConversionResult<DomValue> {
fn lua_to_dom_value(
&self,
_: &'lua Lua,
variant_type: Option<DomType>,
) -> DomConversionResult<DomValue> {
use super::types::*;
use rbx_dom_weak::types as dom;
@ -254,8 +256,7 @@ impl LuaToDomValue for LuaAnyUserData {
DomType::Color3 => userdata_to_dom!(self as Color3 => dom::Color3),
DomType::Color3uint8 => userdata_to_dom!(self as Color3 => dom::Color3uint8),
DomType::ColorSequence => userdata_to_dom!(self as ColorSequence => dom::ColorSequence),
DomType::Content => userdata_to_dom!(self as Content => dom::Content),
DomType::EnumItem => userdata_to_dom!(self as EnumItem => dom::EnumItem),
DomType::Enum => userdata_to_dom!(self as EnumItem => dom::Enum),
DomType::Faces => userdata_to_dom!(self as Faces => dom::Faces),
DomType::Font => userdata_to_dom!(self as Font => dom::Font),
DomType::NumberRange => userdata_to_dom!(self as NumberRange => dom::NumberRange),
@ -275,13 +276,13 @@ impl LuaToDomValue for LuaAnyUserData {
// NOTE: The none and default variants of these types are handled in
// LuaToDomValue for the LuaValue type instead, allowing for nil/default
DomType::OptionalCFrame => {
match self.borrow::<CFrame>() {
return match self.borrow::<CFrame>() {
Err(_) => unreachable!("Invalid use of conversion method, should be using LuaValue"),
Ok(value) => Ok(DomValue::OptionalCFrame(Some(dom::CFrame::from(*value)))),
}
}
DomType::PhysicalProperties => {
match self.borrow::<PhysicalProperties>() {
return match self.borrow::<PhysicalProperties>() {
Err(_) => unreachable!("Invalid use of conversion method, should be using LuaValue"),
Ok(value) => {
let props = dom::CustomPhysicalProperties::from(*value);
@ -313,7 +314,7 @@ impl LuaToDomValue for LuaAnyUserData {
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::<ColorSequence>() => userdata_to_dom!(value as ColorSequence => dom::ColorSequence),
value if value.is::<EnumItem>() => userdata_to_dom!(value as EnumItem => dom::EnumItem),
value if value.is::<Enum>() => userdata_to_dom!(value as EnumItem => dom::Enum),
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::<Instance>() => userdata_to_dom!(value as Instance => dom::Ref),

View file

@ -19,9 +19,7 @@ impl DomValueExt for DomType {
Color3uint8 => "Color3uint8",
ColorSequence => "ColorSequence",
Content => "Content",
ContentId => "ContentId",
Enum => "Enum",
EnumItem => "EnumItem",
Faces => "Faces",
Float32 => "Float32",
Float64 => "Float64",

View file

@ -21,11 +21,11 @@ pub struct Axes {
pub(crate) z: bool,
}
impl LuaExportsTable for Axes {
impl LuaExportsTable<'_> for Axes {
const EXPORT_NAME: &'static str = "Axes";
fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {
let axes_new = |_: &Lua, args: LuaMultiValue| {
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
let axes_new = |_, args: LuaMultiValue| {
let mut x = false;
let mut y = false;
let mut z = false;
@ -76,7 +76,7 @@ impl LuaExportsTable for Axes {
}
impl LuaUserData for Axes {
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
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("Z", |_, this| Ok(this.z));
@ -88,7 +88,7 @@ impl LuaUserData for Axes {
fields.add_field_method_get("Back", |_, this| Ok(this.z));
}
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
}

View file

@ -1,7 +1,7 @@
use core::fmt;
use mlua::prelude::*;
use rand::prelude::*;
use rand::seq::SliceRandom;
use rbx_dom_weak::types::BrickColor as DomBrickColor;
use lune_utils::TableBuilder;
@ -24,16 +24,16 @@ pub struct BrickColor {
pub(crate) rgb: (u8, u8, u8),
}
impl LuaExportsTable for BrickColor {
impl LuaExportsTable<'_> for 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 ArgsName = String;
type ArgsRgb = (u8, u8, u8);
type ArgsColor3 = LuaUserDataRef<Color3>;
type ArgsColor3<'lua> = LuaUserDataRef<'lua, Color3>;
let brick_color_new = |lua: &Lua, args: LuaMultiValue| {
let brick_color_new = |lua, args: LuaMultiValue| {
if let Ok(number) = ArgsNumber::from_lua_multi(args.clone(), lua) {
Ok(color_from_number(number))
} else if let Ok(name) = ArgsName::from_lua_multi(args.clone(), lua) {
@ -50,7 +50,7 @@ impl LuaExportsTable for BrickColor {
}
};
let brick_color_palette = |_: &Lua, index: u16| {
let brick_color_palette = |_, index: u16| {
if index == 0 {
Err(LuaError::RuntimeError("Invalid index".to_string()))
} 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 = |_: &Lua, ()| {
let number = BRICK_COLOR_PALETTE.choose(&mut rand::rng());
let brick_color_random = |_, ()| {
let number = BRICK_COLOR_PALETTE.choose(&mut rand::thread_rng());
Ok(color_from_number(*number.unwrap()))
};
@ -71,7 +71,7 @@ impl LuaExportsTable for BrickColor {
.with_function("random", brick_color_random)?;
for (name, number) in BRICK_COLOR_CONSTRUCTORS {
let f = |_: &Lua, ()| Ok(color_from_number(*number));
let f = |_, ()| Ok(color_from_number(*number));
builder = builder.with_function(*name, f)?;
}
@ -80,7 +80,7 @@ impl LuaExportsTable for BrickColor {
}
impl LuaUserData for BrickColor {
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
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("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)));
}
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
}

View file

@ -43,28 +43,27 @@ impl CFrame {
}
}
impl LuaExportsTable for CFrame {
impl LuaExportsTable<'_> for CFrame {
const EXPORT_NAME: &'static str = "CFrame";
#[allow(clippy::too_many_lines)]
fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {
let cframe_angles = |_: &Lua, (rx, ry, rz): (f32, f32, f32)| {
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
let cframe_angles = |_, (rx, ry, rz): (f32, f32, f32)| {
Ok(CFrame(Mat4::from_euler(EulerRot::XYZ, rx, ry, rz)))
};
let cframe_from_axis_angle = |_: &Lua, (v, r): (LuaUserDataRef<Vector3>, f32)| {
Ok(CFrame(Mat4::from_axis_angle(v.0, r)))
};
let cframe_from_axis_angle =
|_, (v, r): (LuaUserDataRef<Vector3>, f32)| Ok(CFrame(Mat4::from_axis_angle(v.0, r)));
let cframe_from_euler_angles_xyz = |_: &Lua, (rx, ry, rz): (f32, f32, f32)| {
let cframe_from_euler_angles_xyz = |_, (rx, ry, rz): (f32, f32, f32)| {
Ok(CFrame(Mat4::from_euler(EulerRot::XYZ, rx, ry, rz)))
};
let cframe_from_euler_angles_yxz = |_: &Lua, (rx, ry, rz): (f32, f32, f32)| {
let cframe_from_euler_angles_yxz = |_, (rx, ry, rz): (f32, f32, f32)| {
Ok(CFrame(Mat4::from_euler(EulerRot::YXZ, ry, rx, rz)))
};
let cframe_from_matrix = |_: &Lua,
let cframe_from_matrix = |_,
(pos, rx, ry, rz): (
LuaUserDataRef<Vector3>,
LuaUserDataRef<Vector3>,
@ -80,11 +79,11 @@ impl LuaExportsTable for CFrame {
)))
};
let cframe_from_orientation = |_: &Lua, (rx, ry, rz): (f32, f32, f32)| {
let cframe_from_orientation = |_, (rx, ry, rz): (f32, f32, f32)| {
Ok(CFrame(Mat4::from_euler(EulerRot::YXZ, ry, rx, rz)))
};
let cframe_look_at = |_: &Lua,
let cframe_look_at = |_,
(from, to, up): (
LuaUserDataRef<Vector3>,
LuaUserDataRef<Vector3>,
@ -98,18 +97,18 @@ impl LuaExportsTable for CFrame {
};
// Dynamic args constructor
type ArgsPos = LuaUserDataRef<Vector3>;
type ArgsLook = (
LuaUserDataRef<Vector3>,
LuaUserDataRef<Vector3>,
Option<LuaUserDataRef<Vector3>>,
type ArgsPos<'lua> = LuaUserDataRef<'lua, Vector3>;
type ArgsLook<'lua> = (
LuaUserDataRef<'lua, Vector3>,
LuaUserDataRef<'lua, Vector3>,
Option<LuaUserDataRef<'lua, Vector3>>,
);
type ArgsPosXYZ = (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);
let cframe_new = |lua: &Lua, args: LuaMultiValue| match args.len() {
let cframe_new = |lua, args: LuaMultiValue| match args.len() {
0 => Ok(CFrame(Mat4::IDENTITY)),
1 => match ArgsPos::from_lua_multi(args, lua) {
@ -175,7 +174,7 @@ impl LuaExportsTable for CFrame {
}
impl LuaUserData for CFrame {
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_field_method_get("Position", |_, this| Ok(Vector3(this.position())));
fields.add_field_method_get("Rotation", |_, this| {
Ok(CFrame(Mat4::from_cols(
@ -201,7 +200,7 @@ impl LuaUserData for CFrame {
}
#[allow(clippy::too_many_lines)]
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
// Methods
methods.add_method("Inverse", |_, this, ()| Ok(this.inverse()));
methods.add_method(
@ -324,10 +323,10 @@ impl LuaUserData for CFrame {
} else if let Ok(vec) = ud.borrow::<Vector3>() {
return lua.create_userdata(*this * *vec);
}
}
};
Err(LuaError::FromLuaConversionError {
from: rhs.type_name(),
to: "userdata".to_string(),
to: "userdata",
message: Some(format!(
"Expected CFrame or Vector3, got {}",
rhs.type_name()

View file

@ -28,11 +28,11 @@ pub struct Color3 {
pub(crate) b: f32,
}
impl LuaExportsTable for Color3 {
impl LuaExportsTable<'_> for Color3 {
const EXPORT_NAME: &'static str = "Color3";
fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {
let color3_from_rgb = |_: &Lua, (r, g, b): (Option<u8>, Option<u8>, Option<u8>)| {
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
let color3_from_rgb = |_, (r, g, b): (Option<u8>, Option<u8>, Option<u8>)| {
Ok(Color3 {
r: (r.unwrap_or_default() as f32) / 255f32,
g: (g.unwrap_or_default() as f32) / 255f32,
@ -40,7 +40,7 @@ impl LuaExportsTable for Color3 {
})
};
let color3_from_hsv = |_: &Lua, (h, s, v): (f32, f32, f32)| {
let color3_from_hsv = |_, (h, s, v): (f32, f32, f32)| {
// https://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c
let i = (h * 6.0).floor();
let f = h * 6.0 - i;
@ -61,7 +61,7 @@ impl LuaExportsTable for Color3 {
Ok(Color3 { r, g, b })
};
let color3_from_hex = |_: &Lua, hex: String| {
let color3_from_hex = |_, hex: String| {
let trimmed = hex.trim_start_matches('#').to_ascii_uppercase();
let chars = if trimmed.len() == 3 {
(
@ -94,7 +94,7 @@ impl LuaExportsTable for Color3 {
}
};
let color3_new = |_: &Lua, (r, g, b): (Option<f32>, Option<f32>, Option<f32>)| {
let color3_new = |_, (r, g, b): (Option<f32>, Option<f32>, Option<f32>)| {
Ok(Color3 {
r: r.unwrap_or_default(),
g: g.unwrap_or_default(),
@ -111,14 +111,14 @@ impl LuaExportsTable for Color3 {
}
}
impl FromLua for Color3 {
fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
impl<'lua> FromLua<'lua> for Color3 {
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
if let LuaValue::UserData(ud) = value {
Ok(*ud.borrow::<Color3>()?)
} else {
Err(LuaError::FromLuaConversionError {
from: value.type_name(),
to: "Color3".to_string(),
to: "Color3",
message: None,
})
}
@ -126,13 +126,13 @@ impl FromLua for Color3 {
}
impl LuaUserData for Color3 {
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
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("B", |_, this| Ok(this.b));
}
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
// Methods
methods.add_method(
"Lerp",

View file

@ -21,15 +21,15 @@ pub struct ColorSequence {
pub(crate) keypoints: Vec<ColorSequenceKeypoint>,
}
impl LuaExportsTable for ColorSequence {
impl LuaExportsTable<'_> for ColorSequence {
const EXPORT_NAME: &'static str = "ColorSequence";
fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {
type ArgsColor = LuaUserDataRef<Color3>;
type ArgsColors = (LuaUserDataRef<Color3>, LuaUserDataRef<Color3>);
type ArgsKeypoints = Vec<LuaUserDataRef<ColorSequenceKeypoint>>;
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
type ArgsColor<'lua> = LuaUserDataRef<'lua, Color3>;
type ArgsColors<'lua> = (LuaUserDataRef<'lua, Color3>, LuaUserDataRef<'lua, Color3>);
type ArgsKeypoints<'lua> = Vec<LuaUserDataRef<'lua, ColorSequenceKeypoint>>;
let color_sequence_new = |lua: &Lua, args: LuaMultiValue| {
let color_sequence_new = |lua, args: LuaMultiValue| {
if let Ok(color) = ArgsColor::from_lua_multi(args.clone(), lua) {
Ok(ColorSequence {
keypoints: vec![
@ -75,11 +75,11 @@ impl LuaExportsTable for ColorSequence {
}
impl LuaUserData for ColorSequence {
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_field_method_get("Keypoints", |_, this| Ok(this.keypoints.clone()));
}
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
}

View file

@ -20,17 +20,16 @@ pub struct ColorSequenceKeypoint {
pub(crate) color: Color3,
}
impl LuaExportsTable for ColorSequenceKeypoint {
impl LuaExportsTable<'_> for ColorSequenceKeypoint {
const EXPORT_NAME: &'static str = "ColorSequenceKeypoint";
fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {
let color_sequence_keypoint_new =
|_: &Lua, (time, color): (f32, LuaUserDataRef<Color3>)| {
Ok(ColorSequenceKeypoint {
time,
color: *color,
})
};
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
let color_sequence_keypoint_new = |_, (time, color): (f32, LuaUserDataRef<Color3>)| {
Ok(ColorSequenceKeypoint {
time,
color: *color,
})
};
TableBuilder::new(lua)?
.with_function("new", color_sequence_keypoint_new)?
@ -39,12 +38,12 @@ impl LuaExportsTable for ColorSequenceKeypoint {
}
impl LuaUserData for ColorSequenceKeypoint {
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_field_method_get("Time", |_, this| Ok(this.time));
fields.add_field_method_get("Value", |_, this| Ok(this.color));
}
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
}

View file

@ -1,120 +0,0 @@
use core::fmt;
use mlua::prelude::*;
use rbx_dom_weak::types::{Content as DomContent, ContentType};
use lune_utils::TableBuilder;
use crate::{exports::LuaExportsTable, instance::Instance};
use super::{super::*, EnumItem};
/**
An implementation of the [Content](https://create.roblox.com/docs/reference/engine/datatypes/Content) Roblox datatype.
This implements all documented properties, methods & constructors of the Content type as of April 2025.
*/
#[derive(Debug, Clone, PartialEq)]
pub struct Content(ContentType);
impl LuaExportsTable for Content {
const EXPORT_NAME: &'static str = "Content";
fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {
let from_uri = |_: &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:?}"),
}
}
}

View file

@ -23,7 +23,7 @@ impl Enum {
}
impl LuaUserData for Enum {
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
// Methods
methods.add_method("GetEnumItems", |_, this, ()| {
Ok(this

View file

@ -1,7 +1,7 @@
use core::fmt;
use mlua::prelude::*;
use rbx_dom_weak::types::EnumItem as DomEnumItem;
use rbx_dom_weak::types::Enum as DomEnum;
use super::{super::*, Enum};
@ -62,26 +62,26 @@ impl EnumItem {
}
impl LuaUserData for EnumItem {
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
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("EnumType", |_, this| Ok(this.parent.clone()));
}
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
}
}
impl FromLua for EnumItem {
fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
impl<'lua> FromLua<'lua> for EnumItem {
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
if let LuaValue::UserData(ud) = value {
Ok(ud.borrow::<EnumItem>()?.to_owned())
} else {
Err(LuaError::FromLuaConversionError {
from: value.type_name(),
to: "EnumItem".to_string(),
to: "EnumItem",
message: None,
})
}
@ -100,18 +100,8 @@ impl PartialEq for EnumItem {
}
}
impl From<EnumItem> for DomEnumItem {
impl From<EnumItem> for DomEnum {
fn from(v: EnumItem) -> Self {
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")
DomEnum::from_u32(v.value)
}
}

View file

@ -13,7 +13,7 @@ use super::{super::*, Enum};
pub struct Enums;
impl LuaUserData for Enums {
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
// Methods
methods.add_method("GetEnums", |_, _, ()| {
let db = rbx_reflection_database::get();

View file

@ -26,11 +26,11 @@ pub struct Faces {
pub(crate) front: bool,
}
impl LuaExportsTable for Faces {
impl LuaExportsTable<'_> for Faces {
const EXPORT_NAME: &'static str = "Faces";
fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {
let faces_new = |_: &Lua, args: LuaMultiValue| {
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
let faces_new = |_, args: LuaMultiValue| {
let mut right = false;
let mut top = false;
let mut back = false;
@ -87,7 +87,7 @@ impl LuaExportsTable for Faces {
}
impl LuaUserData for Faces {
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
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("Back", |_, this| Ok(this.back));
@ -96,7 +96,7 @@ impl LuaUserData for Faces {
fields.add_field_method_get("Front", |_, this| Ok(this.front));
}
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
}

View file

@ -40,11 +40,11 @@ impl Font {
}
}
impl LuaExportsTable for Font {
impl LuaExportsTable<'_> for Font {
const EXPORT_NAME: &'static str = "Font";
fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {
let font_from_enum = |_: &Lua, value: LuaUserDataRef<EnumItem>| {
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
let font_from_enum = |_, value: LuaUserDataRef<EnumItem>| {
if value.parent.desc.name == "Font" {
match Font::from_enum_item(&value) {
Some(props) => Ok(props),
@ -62,7 +62,7 @@ impl LuaExportsTable for Font {
};
let font_from_name =
|_: &Lua, (file, weight, style): (String, Option<FontWeight>, Option<FontStyle>)| {
|_, (file, weight, style): (String, Option<FontWeight>, Option<FontStyle>)| {
Ok(Font {
family: format!("rbxasset://fonts/families/{file}.json"),
weight: weight.unwrap_or_default(),
@ -72,7 +72,7 @@ impl LuaExportsTable for Font {
};
let font_from_id =
|_: &Lua, (id, weight, style): (i32, Option<FontWeight>, Option<FontStyle>)| {
|_, (id, weight, style): (i32, Option<FontWeight>, Option<FontStyle>)| {
Ok(Font {
family: format!("rbxassetid://{id}"),
weight: weight.unwrap_or_default(),
@ -82,7 +82,7 @@ impl LuaExportsTable for Font {
};
let font_new =
|_: &Lua, (family, weight, style): (String, Option<FontWeight>, Option<FontStyle>)| {
|_, (family, weight, style): (String, Option<FontWeight>, Option<FontStyle>)| {
Ok(Font {
family,
weight: weight.unwrap_or_default(),
@ -101,7 +101,7 @@ impl LuaExportsTable for Font {
}
impl LuaUserData for Font {
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
// Getters
fields.add_field_method_get("Family", |_, this| Ok(this.family.clone()));
fields.add_field_method_get("Weight", |_, this| Ok(this.weight));
@ -126,7 +126,7 @@ impl LuaUserData for Font {
});
}
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
}
@ -282,8 +282,8 @@ impl std::fmt::Display for FontWeight {
}
}
impl FromLua for FontWeight {
fn from_lua(lua_value: LuaValue, _: &Lua) -> LuaResult<Self> {
impl<'lua> FromLua<'lua> for FontWeight {
fn from_lua(lua_value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
let mut message = None;
if let LuaValue::UserData(ud) = &lua_value {
let value = ud.borrow::<EnumItem>()?;
@ -304,18 +304,18 @@ impl FromLua for FontWeight {
}
Err(LuaError::FromLuaConversionError {
from: lua_value.type_name(),
to: "Enum.FontWeight".to_string(),
to: "Enum.FontWeight",
message,
})
}
}
impl IntoLua for FontWeight {
fn into_lua(self, lua: &Lua) -> LuaResult<LuaValue> {
impl<'lua> IntoLua<'lua> for FontWeight {
fn into_lua(self, lua: &'lua Lua) -> LuaResult<LuaValue<'lua>> {
match EnumItem::from_enum_name_and_name("FontWeight", self.to_string()) {
Some(enum_item) => Ok(LuaValue::UserData(lua.create_userdata(enum_item)?)),
None => Err(LuaError::ToLuaConversionError {
from: "FontWeight".to_string(),
from: "FontWeight",
to: "EnumItem",
message: Some(format!("Found unknown Enum.FontWeight value '{self}'")),
}),
@ -376,8 +376,8 @@ impl std::fmt::Display for FontStyle {
}
}
impl FromLua for FontStyle {
fn from_lua(lua_value: LuaValue, _: &Lua) -> LuaResult<Self> {
impl<'lua> FromLua<'lua> for FontStyle {
fn from_lua(lua_value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
let mut message = None;
if let LuaValue::UserData(ud) = &lua_value {
let value = ud.borrow::<EnumItem>()?;
@ -398,18 +398,18 @@ impl FromLua for FontStyle {
}
Err(LuaError::FromLuaConversionError {
from: lua_value.type_name(),
to: "Enum.FontStyle".to_string(),
to: "Enum.FontStyle",
message,
})
}
}
impl IntoLua for FontStyle {
fn into_lua(self, lua: &Lua) -> LuaResult<LuaValue> {
impl<'lua> IntoLua<'lua> for FontStyle {
fn into_lua(self, lua: &'lua Lua) -> LuaResult<LuaValue<'lua>> {
match EnumItem::from_enum_name_and_name("FontStyle", self.to_string()) {
Some(enum_item) => Ok(LuaValue::UserData(lua.create_userdata(enum_item)?)),
None => Err(LuaError::ToLuaConversionError {
from: "FontStyle".to_string(),
from: "FontStyle",
to: "EnumItem",
message: Some(format!("Found unknown Enum.FontStyle value '{self}'")),
}),

View file

@ -4,7 +4,6 @@ mod cframe;
mod color3;
mod color_sequence;
mod color_sequence_keypoint;
mod content;
mod r#enum;
mod r#enum_item;
mod r#enums;
@ -31,7 +30,6 @@ pub use cframe::CFrame;
pub use color3::Color3;
pub use color_sequence::ColorSequence;
pub use color_sequence_keypoint::ColorSequenceKeypoint;
pub use content::Content;
pub use faces::Faces;
pub use font::Font;
pub use number_range::NumberRange;

View file

@ -20,11 +20,11 @@ pub struct NumberRange {
pub(crate) max: f32,
}
impl LuaExportsTable for NumberRange {
impl LuaExportsTable<'_> for NumberRange {
const EXPORT_NAME: &'static str = "NumberRange";
fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {
let number_range_new = |_: &Lua, (min, max): (f32, Option<f32>)| {
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
let number_range_new = |_, (min, max): (f32, Option<f32>)| {
Ok(match max {
Some(max) => NumberRange {
min: min.min(max),
@ -41,12 +41,12 @@ impl LuaExportsTable for NumberRange {
}
impl LuaUserData for NumberRange {
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_field_method_get("Min", |_, this| Ok(this.min));
fields.add_field_method_get("Max", |_, this| Ok(this.max));
}
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
}

View file

@ -21,15 +21,15 @@ pub struct NumberSequence {
pub(crate) keypoints: Vec<NumberSequenceKeypoint>,
}
impl LuaExportsTable for NumberSequence {
impl LuaExportsTable<'_> for 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 ArgsColors = (f32, f32);
type ArgsKeypoints = Vec<LuaUserDataRef<NumberSequenceKeypoint>>;
type ArgsKeypoints<'lua> = Vec<LuaUserDataRef<'lua, NumberSequenceKeypoint>>;
let number_sequence_new = |lua: &Lua, args: LuaMultiValue| {
let number_sequence_new = |lua, args: LuaMultiValue| {
if let Ok(value) = ArgsColor::from_lua_multi(args.clone(), lua) {
Ok(NumberSequence {
keypoints: vec![
@ -79,11 +79,11 @@ impl LuaExportsTable for NumberSequence {
}
impl LuaUserData for NumberSequence {
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_field_method_get("Keypoints", |_, this| Ok(this.keypoints.clone()));
}
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
}

View file

@ -21,18 +21,17 @@ pub struct NumberSequenceKeypoint {
pub(crate) envelope: f32,
}
impl LuaExportsTable for NumberSequenceKeypoint {
impl LuaExportsTable<'_> for NumberSequenceKeypoint {
const EXPORT_NAME: &'static str = "NumberSequenceKeypoint";
fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {
let number_sequence_keypoint_new =
|_: &Lua, (time, value, envelope): (f32, f32, Option<f32>)| {
Ok(NumberSequenceKeypoint {
time,
value,
envelope: envelope.unwrap_or_default(),
})
};
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
let number_sequence_keypoint_new = |_, (time, value, envelope): (f32, f32, Option<f32>)| {
Ok(NumberSequenceKeypoint {
time,
value,
envelope: envelope.unwrap_or_default(),
})
};
TableBuilder::new(lua)?
.with_function("new", number_sequence_keypoint_new)?
@ -41,13 +40,13 @@ impl LuaExportsTable for NumberSequenceKeypoint {
}
impl LuaUserData for NumberSequenceKeypoint {
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
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("Envelope", |_, this| Ok(this.envelope));
}
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
}

View file

@ -38,14 +38,14 @@ impl PhysicalProperties {
}
}
impl LuaExportsTable for PhysicalProperties {
impl LuaExportsTable<'_> for PhysicalProperties {
const EXPORT_NAME: &'static str = "PhysicalProperties";
fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {
type ArgsMaterial = LuaUserDataRef<EnumItem>;
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
type ArgsMaterial<'lua> = LuaUserDataRef<'lua, EnumItem>;
type ArgsNumbers = (f32, f32, f32, Option<f32>, Option<f32>);
let physical_properties_new = |lua: &Lua, args: LuaMultiValue| {
let physical_properties_new = |lua, args: LuaMultiValue| {
if let Ok(value) = ArgsMaterial::from_lua_multi(args.clone(), lua) {
if value.parent.desc.name == "Material" {
match PhysicalProperties::from_material(&value) {
@ -86,7 +86,7 @@ impl LuaExportsTable for PhysicalProperties {
}
impl LuaUserData for PhysicalProperties {
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
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("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));
}
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
}

View file

@ -32,12 +32,12 @@ impl Ray {
}
}
impl LuaExportsTable for Ray {
impl LuaExportsTable<'_> for 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 =
|_: &Lua, (origin, direction): (LuaUserDataRef<Vector3>, LuaUserDataRef<Vector3>)| {
|_, (origin, direction): (LuaUserDataRef<Vector3>, LuaUserDataRef<Vector3>)| {
Ok(Ray {
origin: origin.0,
direction: direction.0,
@ -51,7 +51,7 @@ impl LuaExportsTable for Ray {
}
impl LuaUserData for Ray {
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
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("Unit", |_, this| {
@ -62,7 +62,7 @@ impl LuaUserData for Ray {
});
}
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
// Methods
methods.add_method("ClosestPoint", |_, this, to: LuaUserDataRef<Vector3>| {
Ok(Vector3(this.closest_point(to.0)))

View file

@ -32,17 +32,17 @@ impl Rect {
}
}
impl LuaExportsTable for Rect {
impl LuaExportsTable<'_> for Rect {
const EXPORT_NAME: &'static str = "Rect";
fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {
type ArgsVector2s = (
Option<LuaUserDataRef<Vector2>>,
Option<LuaUserDataRef<Vector2>>,
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
type ArgsVector2s<'lua> = (
Option<LuaUserDataRef<'lua, Vector2>>,
Option<LuaUserDataRef<'lua, Vector2>>,
);
type ArgsNums = (Option<f32>, Option<f32>, Option<f32>, Option<f32>);
let rect_new = |lua: &Lua, args: LuaMultiValue| {
let rect_new = |lua, args: LuaMultiValue| {
if let Ok((min, max)) = ArgsVector2s::from_lua_multi(args.clone(), lua) {
Ok(Rect::new(
min.map(|m| *m).unwrap_or_default().0,
@ -67,14 +67,14 @@ impl LuaExportsTable for Rect {
}
impl LuaUserData for Rect {
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
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("Width", |_, this| Ok(this.max.x - this.min.x));
fields.add_field_method_get("Height", |_, this| Ok(this.max.y - this.min.y));
}
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
methods.add_meta_method(LuaMetaMethod::Unm, userdata_impl_unm);

View file

@ -22,17 +22,16 @@ pub struct Region3 {
pub(crate) max: Vec3,
}
impl LuaExportsTable for Region3 {
impl LuaExportsTable<'_> for Region3 {
const EXPORT_NAME: &'static str = "Region3";
fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {
let region3_new =
|_: &Lua, (min, max): (LuaUserDataRef<Vector3>, LuaUserDataRef<Vector3>)| {
Ok(Region3 {
min: min.0,
max: max.0,
})
};
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
let region3_new = |_, (min, max): (LuaUserDataRef<Vector3>, LuaUserDataRef<Vector3>)| {
Ok(Region3 {
min: min.0,
max: max.0,
})
};
TableBuilder::new(lua)?
.with_function("new", region3_new)?
@ -41,14 +40,14 @@ impl LuaExportsTable for Region3 {
}
impl LuaUserData for Region3 {
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_field_method_get("CFrame", |_, this| {
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)));
}
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
// Methods
methods.add_method("ExpandToGrid", |_, this, resolution: f32| {
Ok(Region3 {

View file

@ -22,12 +22,12 @@ pub struct Region3int16 {
pub(crate) max: IVec3,
}
impl LuaExportsTable for Region3int16 {
impl LuaExportsTable<'_> for 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 =
|_: &Lua, (min, max): (LuaUserDataRef<Vector3int16>, LuaUserDataRef<Vector3int16>)| {
|_, (min, max): (LuaUserDataRef<Vector3int16>, LuaUserDataRef<Vector3int16>)| {
Ok(Region3int16 {
min: min.0,
max: max.0,
@ -41,12 +41,12 @@ impl LuaExportsTable for Region3int16 {
}
impl LuaUserData for Region3int16 {
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_field_method_get("Min", |_, this| Ok(Vector3int16(this.min)));
fields.add_field_method_get("Max", |_, this| Ok(Vector3int16(this.max)));
}
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
}

View file

@ -27,11 +27,11 @@ impl UDim {
}
}
impl LuaExportsTable for UDim {
impl LuaExportsTable<'_> for UDim {
const EXPORT_NAME: &'static str = "UDim";
fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {
let udim_new = |_: &Lua, (scale, offset): (Option<f32>, Option<i32>)| {
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
let udim_new = |_, (scale, offset): (Option<f32>, Option<i32>)| {
Ok(UDim {
scale: scale.unwrap_or_default(),
offset: offset.unwrap_or_default(),
@ -45,12 +45,12 @@ impl LuaExportsTable for UDim {
}
impl LuaUserData for UDim {
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_field_method_get("Scale", |_, this| Ok(this.scale));
fields.add_field_method_get("Offset", |_, this| Ok(this.offset));
}
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
methods.add_meta_method(LuaMetaMethod::Unm, userdata_impl_unm);

View file

@ -24,27 +24,30 @@ pub struct UDim2 {
pub(crate) y: UDim,
}
impl LuaExportsTable for UDim2 {
impl LuaExportsTable<'_> for UDim2 {
const EXPORT_NAME: &'static str = "UDim2";
fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {
let udim2_from_offset = |_: &Lua, (x, y): (Option<i32>, Option<i32>)| {
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
let udim2_from_offset = |_, (x, y): (Option<i32>, Option<i32>)| {
Ok(UDim2 {
x: UDim::new(0f32, x.unwrap_or_default()),
y: UDim::new(0f32, y.unwrap_or_default()),
})
};
let udim2_from_scale = |_: &Lua, (x, y): (Option<f32>, Option<f32>)| {
let udim2_from_scale = |_, (x, y): (Option<f32>, Option<f32>)| {
Ok(UDim2 {
x: UDim::new(x.unwrap_or_default(), 0),
y: UDim::new(y.unwrap_or_default(), 0),
})
};
type ArgsUDims = (Option<LuaUserDataRef<UDim>>, Option<LuaUserDataRef<UDim>>);
type ArgsUDims<'lua> = (
Option<LuaUserDataRef<'lua, UDim>>,
Option<LuaUserDataRef<'lua, UDim>>,
);
type ArgsNums = (Option<f32>, Option<i32>, Option<f32>, Option<i32>);
let udim2_new = |lua: &Lua, args: LuaMultiValue| {
let udim2_new = |lua, args: LuaMultiValue| {
if let Ok((x, y)) = ArgsUDims::from_lua_multi(args.clone(), lua) {
Ok(UDim2 {
x: x.map(|x| *x).unwrap_or_default(),
@ -72,14 +75,14 @@ impl LuaExportsTable for UDim2 {
}
impl LuaUserData for UDim2 {
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
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("Width", |_, this| Ok(this.x));
fields.add_field_method_get("Height", |_, this| Ok(this.y));
}
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
// Methods
methods.add_method(
"Lerp",

View file

@ -21,11 +21,11 @@ use super::super::*;
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct Vector2(pub Vec2);
impl LuaExportsTable for Vector2 {
impl LuaExportsTable<'_> for Vector2 {
const EXPORT_NAME: &'static str = "Vector2";
fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {
let vector2_new = |_: &Lua, (x, y): (Option<f32>, Option<f32>)| {
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
let vector2_new = |_, (x, y): (Option<f32>, Option<f32>)| {
Ok(Vector2(Vec2 {
x: x.unwrap_or_default(),
y: y.unwrap_or_default(),
@ -43,18 +43,15 @@ impl LuaExportsTable for Vector2 {
}
impl LuaUserData for Vector2 {
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
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("X", |_, this| Ok(this.0.x));
fields.add_field_method_get("Y", |_, this| Ok(this.0.y));
}
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
// Methods
methods.add_method("Angle", |_, this, rhs: LuaUserDataRef<Vector2>| {
Ok(this.0.angle_to(rhs.0))
});
methods.add_method("Cross", |_, this, rhs: LuaUserDataRef<Vector2>| {
let this_v3 = Vec3::new(this.0.x, this.0.y, 0f32);
let rhs_v3 = Vec3::new(rhs.0.x, rhs.0.y, 0f32);
@ -63,14 +60,6 @@ impl LuaUserData for Vector2 {
methods.add_method("Dot", |_, this, rhs: LuaUserDataRef<Vector2>| {
Ok(this.0.dot(rhs.0))
});
methods.add_method(
"FuzzyEq",
|_, this, (rhs, epsilon): (LuaUserDataRef<Vector2>, f32)| {
let eq_x = (rhs.0.x - this.0.x).abs() <= epsilon;
let eq_y = (rhs.0.y - this.0.y).abs() <= epsilon;
Ok(eq_x && eq_y)
},
);
methods.add_method(
"Lerp",
|_, this, (rhs, alpha): (LuaUserDataRef<Vector2>, f32)| {
@ -83,10 +72,6 @@ impl LuaUserData for Vector2 {
methods.add_method("Min", |_, this, rhs: LuaUserDataRef<Vector2>| {
Ok(Vector2(this.0.min(rhs.0)))
});
methods.add_method("Abs", |_, this, ()| Ok(Vector2(this.0.abs())));
methods.add_method("Ceil", |_, this, ()| Ok(Vector2(this.0.ceil())));
methods.add_method("Floor", |_, this, ()| Ok(Vector2(this.0.floor())));
methods.add_method("Sign", |_, this, ()| Ok(Vector2(this.0.signum())));
// Metamethods
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);

View file

@ -21,11 +21,11 @@ use super::super::*;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Vector2int16(pub IVec2);
impl LuaExportsTable for Vector2int16 {
impl LuaExportsTable<'_> for Vector2int16 {
const EXPORT_NAME: &'static str = "Vector2int16";
fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {
let vector2int16_new = |_: &Lua, (x, y): (Option<i16>, Option<i16>)| {
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
let vector2int16_new = |_, (x, y): (Option<i16>, Option<i16>)| {
Ok(Vector2int16(IVec2 {
x: x.unwrap_or_default() as i32,
y: y.unwrap_or_default() as i32,
@ -39,12 +39,12 @@ impl LuaExportsTable for Vector2int16 {
}
impl LuaUserData for Vector2int16 {
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_field_method_get("X", |_, this| Ok(this.0.x));
fields.add_field_method_get("Y", |_, this| Ok(this.0.y));
}
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
methods.add_meta_method(LuaMetaMethod::Unm, userdata_impl_unm);

View file

@ -24,11 +24,11 @@ use super::{super::*, EnumItem};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Vector3(pub Vec3);
impl LuaExportsTable for Vector3 {
impl LuaExportsTable<'_> for Vector3 {
const EXPORT_NAME: &'static str = "Vector3";
fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {
let vector3_from_axis = |_: &Lua, normal_id: LuaUserDataRef<EnumItem>| {
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
let vector3_from_axis = |_, normal_id: LuaUserDataRef<EnumItem>| {
if normal_id.parent.desc.name == "Axis" {
Ok(match normal_id.name.as_str() {
"X" => Vector3(Vec3::X),
@ -48,7 +48,7 @@ impl LuaExportsTable for Vector3 {
}
};
let vector3_from_normal_id = |_: &Lua, normal_id: LuaUserDataRef<EnumItem>| {
let vector3_from_normal_id = |_, normal_id: LuaUserDataRef<EnumItem>| {
if normal_id.parent.desc.name == "NormalId" {
Ok(match normal_id.name.as_str() {
"Left" => Vector3(Vec3::X),
@ -71,7 +71,7 @@ impl LuaExportsTable for Vector3 {
}
};
let vector3_new = |_: &Lua, (x, y, z): (Option<f32>, Option<f32>, Option<f32>)| {
let vector3_new = |_, (x, y, z): (Option<f32>, Option<f32>, Option<f32>)| {
Ok(Vector3(Vec3 {
x: x.unwrap_or_default(),
y: y.unwrap_or_default(),
@ -93,7 +93,7 @@ impl LuaExportsTable for Vector3 {
}
impl LuaUserData for Vector3 {
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
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("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));
}
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
// Methods
methods.add_method("Angle", |_, this, rhs: LuaUserDataRef<Vector3>| {
Ok(this.0.angle_between(rhs.0))
@ -133,10 +133,6 @@ impl LuaUserData for Vector3 {
methods.add_method("Min", |_, this, rhs: LuaUserDataRef<Vector3>| {
Ok(Vector3(this.0.min(rhs.0)))
});
methods.add_method("Abs", |_, this, ()| Ok(Vector3(this.0.abs())));
methods.add_method("Ceil", |_, this, ()| Ok(Vector3(this.0.ceil())));
methods.add_method("Floor", |_, this, ()| Ok(Vector3(this.0.floor())));
methods.add_method("Sign", |_, this, ()| Ok(Vector3(this.0.signum())));
// Metamethods
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);

View file

@ -21,11 +21,11 @@ use super::super::*;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Vector3int16(pub IVec3);
impl LuaExportsTable for Vector3int16 {
impl LuaExportsTable<'_> for Vector3int16 {
const EXPORT_NAME: &'static str = "Vector3int16";
fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {
let vector3int16_new = |_: &Lua, (x, y, z): (Option<i16>, Option<i16>, Option<i16>)| {
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
let vector3int16_new = |_, (x, y, z): (Option<i16>, Option<i16>, Option<i16>)| {
Ok(Vector3int16(IVec3 {
x: x.unwrap_or_default() as i32,
y: y.unwrap_or_default() as i32,
@ -40,13 +40,13 @@ impl LuaExportsTable for Vector3int16 {
}
impl LuaUserData for Vector3int16 {
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
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("Z", |_, this| Ok(this.0.z));
}
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
methods.add_meta_method(LuaMetaMethod::Unm, userdata_impl_unm);

View file

@ -65,7 +65,7 @@ impl DocumentKind {
for child_ref in dom.root().children() {
if let Some(child_inst) = dom.get_by_ref(*child_ref) {
has_top_level_child = true;
if class_is_a_service(child_inst.class).unwrap_or(false) {
if class_is_a_service(&child_inst.class).unwrap_or(false) {
has_top_level_service = true;
break;
}

View file

@ -1,6 +1,6 @@
use rbx_dom_weak::{
types::{Ref as DomRef, VariantType as DomType},
ustr, Instance as DomInstance, WeakDom,
Instance as DomInstance, WeakDom,
};
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");
// Similar story with ScriptGuid - this is used
// in the studio-only cloud script drafts feature
if class_is_a(inst.class, "LuaSourceContainer").unwrap_or(false) {
inst.properties.remove(&ustr("ScriptGuid"));
if class_is_a(&inst.class, "LuaSourceContainer").unwrap_or(false) {
inst.properties.remove("ScriptGuid");
}
});
}
@ -41,8 +41,7 @@ where
}
fn remove_matching_prop(inst: &mut DomInstance, ty: DomType, name: &'static str) {
let name = &ustr(name);
if inst.properties.get(name).is_some_and(|u| u.ty() == ty) {
if inst.properties.get(name).map_or(false, |u| u.ty() == ty) {
inst.properties.remove(name);
}
}

View file

@ -18,10 +18,10 @@ use mlua::prelude::*;
}
}
impl LuaExportsTable for MyType {
impl LuaExportsTable<'_> for 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>| {
Self::new(n.unwrap_or_default())
};
@ -37,10 +37,10 @@ use mlua::prelude::*;
}
```
*/
pub trait LuaExportsTable {
pub trait LuaExportsTable<'lua> {
const EXPORT_NAME: &'static str;
fn create_exports_table(lua: Lua) -> LuaResult<LuaTable>;
fn create_exports_table(lua: &'lua Lua) -> LuaResult<LuaTable<'lua>>;
}
/**
@ -57,12 +57,12 @@ pub trait LuaExportsTable {
let (name2, table2) = export::<Type2>(lua)?;
```
*/
pub fn export<T>(lua: Lua) -> LuaResult<(&'static str, LuaValue)>
pub fn export<'lua, T>(lua: &'lua Lua) -> LuaResult<(&'static str, LuaValue<'lua>)>
where
T: LuaExportsTable,
T: LuaExportsTable<'lua>,
{
Ok((
T::EXPORT_NAME,
<T as LuaExportsTable>::create_exports_table(lua.clone())?.into_lua(&lua)?,
<T as LuaExportsTable>::create_exports_table(lua)?.into_lua(lua)?,
))
}

View file

@ -20,7 +20,7 @@ use crate::{
use super::{data_model, registry::InstanceRegistry, Instance};
#[allow(clippy::too_many_lines)]
pub fn add_methods<M: LuaUserDataMethods<Instance>>(m: &mut M) {
pub fn add_methods<'lua, M: LuaUserDataMethods<'lua, Instance>>(m: &mut M) {
m.add_meta_method(LuaMetaMethod::ToString, |lua, this, ()| {
ensure_not_destroyed(this)?;
userdata_impl_to_string(lua, this, ())
@ -71,7 +71,7 @@ pub fn add_methods<M: LuaUserDataMethods<Instance>>(m: &mut M) {
"FindFirstAncestorWhichIsA",
|lua, this, class_name: String| {
ensure_not_destroyed(this)?;
this.find_ancestor(|child| class_is_a(child.class, &class_name).unwrap_or(false))
this.find_ancestor(|child| class_is_a(&child.class, &class_name).unwrap_or(false))
.into_lua(lua)
},
);
@ -104,7 +104,7 @@ pub fn add_methods<M: LuaUserDataMethods<Instance>>(m: &mut M) {
|lua, this, (class_name, recursive): (String, Option<bool>)| {
ensure_not_destroyed(this)?;
let predicate =
|child: &DomInstance| class_is_a(child.class, &class_name).unwrap_or(false);
|child: &DomInstance| class_is_a(&child.class, &class_name).unwrap_or(false);
if matches!(recursive, Some(true)) {
this.find_descendant(predicate).into_lua(lua)
} else {
@ -113,7 +113,8 @@ pub fn add_methods<M: LuaUserDataMethods<Instance>>(m: &mut M) {
},
);
m.add_method("IsA", |_, this, class_name: String| {
Ok(class_is_a(this.class_name, class_name).unwrap_or(false))
ensure_not_destroyed(this)?;
Ok(class_is_a(&this.class_name, class_name).unwrap_or(false))
});
m.add_method(
"IsAncestorOf",
@ -211,22 +212,25 @@ fn ensure_not_destroyed(inst: &Instance) -> LuaResult<()> {
3. Get a current child of the instance
4. No valid property or instance found, throw error
*/
fn instance_property_get(lua: &Lua, this: &Instance, prop_name: String) -> LuaResult<LuaValue> {
fn instance_property_get<'lua>(
lua: &'lua Lua,
this: &Instance,
prop_name: String,
) -> LuaResult<LuaValue<'lua>> {
ensure_not_destroyed(this)?;
match prop_name.as_str() {
"ClassName" => return this.get_class_name().into_lua(lua),
"Name" => {
return this.get_name().into_lua(lua);
}
"Parent" => {
return this.get_parent().into_lua(lua);
}
_ => {}
}
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(info) = find_property_info(&this.class_name, &prop_name) {
if let Some(prop) = this.get_property(&prop_name) {
if let DomValue::Enum(enum_value) = prop {
let enum_name = info.enum_name.ok_or_else(|| {
@ -271,7 +275,7 @@ fn instance_property_get(lua: &Lua, this: &Instance, prop_name: String) -> LuaRe
} else if let Some(inst) = this.find_child(|inst| inst.name == prop_name) {
Ok(LuaValue::UserData(lua.create_userdata(inst)?))
} else if let Some(getter) = InstanceRegistry::find_property_getter(lua, this, &prop_name) {
getter.call(*this)
getter.call(this.clone())
} else if let Some(method) = InstanceRegistry::find_method(lua, this, &prop_name) {
Ok(LuaValue::Function(method))
} else {
@ -291,10 +295,10 @@ fn instance_property_get(lua: &Lua, this: &Instance, prop_name: String) -> LuaRe
2a. Set a strict enum from a given EnumItem OR
2b. Set a normal property from a given value
*/
fn instance_property_set(
lua: &Lua,
fn instance_property_set<'lua>(
lua: &'lua Lua,
this: &mut Instance,
(prop_name, prop_value): (String, LuaValue),
(prop_name, prop_value): (String, LuaValue<'lua>),
) -> LuaResult<()> {
ensure_not_destroyed(this)?;
@ -315,19 +319,19 @@ fn instance_property_set(
"Failed to set Parent - DataModel can not be reparented".to_string(),
));
}
type Parent = Option<LuaUserDataRef<Instance>>;
type Parent<'lua> = Option<LuaUserDataRef<'lua, Instance>>;
let parent = Parent::from_lua(prop_value, lua)?;
this.set_parent(parent.map(|p| *p));
this.set_parent(parent.map(|p| p.clone()));
return Ok(());
}
_ => {}
}
if let Some(info) = find_property_info(this.class_name, &prop_name) {
if let Some(info) = find_property_info(&this.class_name, &prop_name) {
if let Some(enum_name) = info.enum_name {
match LuaUserDataRef::<EnumItem>::from_lua(prop_value, lua) {
Ok(given_enum) if given_enum.parent.desc.name == enum_name => {
this.set_property(prop_name, DomValue::EnumItem((*given_enum).clone().into()));
this.set_property(prop_name, DomValue::Enum((*given_enum).clone().into()));
Ok(())
}
Ok(given_enum) => Err(LuaError::RuntimeError(format!(
@ -350,7 +354,7 @@ fn instance_property_set(
)))
}
} else if let Some(setter) = InstanceRegistry::find_property_setter(lua, this, &prop_name) {
setter.call((*this, prop_value))
setter.call((this.clone(), prop_value))
} else {
Err(LuaError::RuntimeError(format!(
"{prop_name} is not a valid member of {this}",

View file

@ -12,11 +12,11 @@ use super::Instance;
pub const CLASS_NAME: &str = "DataModel";
pub fn add_fields<F: LuaUserDataFields<Instance>>(f: &mut F) {
pub fn add_fields<'lua, F: LuaUserDataFields<'lua, Instance>>(f: &mut F) {
add_class_restricted_getter(f, CLASS_NAME, "Workspace", data_model_get_workspace);
}
pub fn add_methods<M: LuaUserDataMethods<Instance>>(m: &mut M) {
pub fn add_methods<'lua, M: LuaUserDataMethods<'lua, Instance>>(m: &mut M) {
add_class_restricted_method(m, CLASS_NAME, "GetService", data_model_get_service);
add_class_restricted_method(m, CLASS_NAME, "FindService", data_model_find_service);
}
@ -26,7 +26,7 @@ pub fn add_methods<M: LuaUserDataMethods<Instance>>(m: &mut M) {
### See Also
* [`Terrain`](https://create.roblox.com/docs/reference/engine/classes/Workspace#Terrain)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
fn data_model_get_workspace(_: &Lua, this: &Instance) -> LuaResult<Instance> {
get_or_create_property_ref_instance(this, "Workspace", "Workspace")
@ -37,7 +37,7 @@ fn data_model_get_workspace(_: &Lua, this: &Instance) -> LuaResult<Instance> {
### See Also
* [`GetService`](https://create.roblox.com/docs/reference/engine/classes/ServiceProvider#GetService)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
fn data_model_get_service(_: &Lua, this: &Instance, service_name: String) -> LuaResult<Instance> {
if matches!(class_is_a_service(&service_name), None | Some(false)) {
@ -48,7 +48,7 @@ fn data_model_get_service(_: &Lua, this: &Instance, service_name: String) -> Lua
Ok(service)
} else {
let service = Instance::new_orphaned(service_name);
service.set_parent(Some(*this));
service.set_parent(Some(this.clone()));
Ok(service)
}
}
@ -58,7 +58,7 @@ fn data_model_get_service(_: &Lua, this: &Instance, service_name: String) -> Lua
### See Also
* [`FindService`](https://create.roblox.com/docs/reference/engine/classes/ServiceProvider#FindService)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
fn data_model_find_service(
_: &Lua,

View file

@ -4,13 +4,14 @@ use std::{
collections::{BTreeMap, VecDeque},
fmt,
hash::{Hash, Hasher},
sync::{LazyLock, Mutex},
sync::Mutex,
};
use mlua::prelude::*;
use once_cell::sync::Lazy;
use rbx_dom_weak::{
types::{Attributes as DomAttributes, Ref as DomRef, Variant as DomValue},
ustr, Instance as DomInstance, InstanceBuilder as DomInstanceBuilder, Ustr, WeakDom,
Instance as DomInstance, InstanceBuilder as DomInstanceBuilder, WeakDom,
};
use lune_utils::TableBuilder;
@ -30,13 +31,13 @@ pub mod registry;
const PROPERTY_NAME_ATTRIBUTES: &str = "Attributes";
const PROPERTY_NAME_TAGS: &str = "Tags";
static INTERNAL_DOM: LazyLock<Mutex<WeakDom>> =
LazyLock::new(|| Mutex::new(WeakDom::new(DomInstanceBuilder::new("ROOT"))));
static INTERNAL_DOM: Lazy<Mutex<WeakDom>> =
Lazy::new(|| Mutex::new(WeakDom::new(DomInstanceBuilder::new("ROOT"))));
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone)]
pub struct Instance {
pub(crate) dom_ref: DomRef,
pub(crate) class_name: Ustr,
pub(crate) class_name: String,
}
impl Instance {
@ -44,26 +45,38 @@ impl Instance {
Creates a new `Instance` from an existing dom object ref.
Panics if the instance does not exist in the internal dom,
or if the given dom object ref points to the internal dom root.
or if the given dom object ref points to the dom root.
**WARNING:** Creating a new instance requires locking the internal dom,
any existing lock must first be released to prevent any deadlocking.
*/
#[must_use]
pub fn new(dom_ref: DomRef) -> Self {
Self::new_opt(dom_ref).expect("Failed to find instance in document")
pub(crate) fn new(dom_ref: DomRef) -> Self {
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
let instance = dom
.get_by_ref(dom_ref)
.expect("Failed to find instance in document");
assert!(
!(instance.referent() == dom.root_ref()),
"Instances can not be created from dom roots"
);
Self {
dom_ref,
class_name: instance.class.clone(),
}
}
/**
Creates a new `Instance` from a dom object ref, if the instance exists.
Panics if the given dom object ref points to the internal dom root.
Panics if the given dom object ref points to the dom root.
**WARNING:** Creating a new instance requires locking the internal dom,
any existing lock must first be released to prevent any deadlocking.
*/
#[must_use]
pub fn new_opt(dom_ref: DomRef) -> Option<Self> {
pub(crate) fn new_opt(dom_ref: DomRef) -> Option<Self> {
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
if let Some(instance) = dom.get_by_ref(dom_ref) {
@ -74,7 +87,7 @@ impl Instance {
Some(Self {
dom_ref,
class_name: instance.class,
class_name: instance.class.clone(),
})
} else {
None
@ -84,25 +97,24 @@ impl Instance {
/**
Creates a new orphaned `Instance` with a given class name.
An orphaned instance is an instance at the root of Lune's internal weak dom.
An orphaned instance is an instance at the root of a weak dom.
**WARNING:** Creating a new instance requires locking the internal dom,
any existing lock must first be released to prevent any deadlocking.
*/
#[must_use]
pub fn new_orphaned(class_name: impl AsRef<str>) -> Self {
pub(crate) fn new_orphaned(class_name: impl AsRef<str>) -> Self {
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
let class_name = class_name.as_ref();
let instance = DomInstanceBuilder::new(class_name);
let instance = DomInstanceBuilder::new(class_name.to_string());
let dom_root = dom.root_ref();
let dom_ref = dom.insert(dom_root, instance);
Self {
dom_ref,
class_name: ustr(class_name),
class_name: class_name.to_string(),
}
}
@ -110,11 +122,10 @@ impl Instance {
Creates a new orphaned `Instance` by transferring
it from an external weak dom to the internal one.
An orphaned instance is an instance at the root of Lune's internal weak dom.
An orphaned instance is an instance at the root of a weak dom.
Panics if the given dom ref is the root dom ref of the external weak dom.
*/
#[must_use]
pub fn from_external_dom(external_dom: &mut WeakDom, external_dom_ref: DomRef) -> Self {
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
let dom_root = dom.root_ref();
@ -140,12 +151,6 @@ impl Instance {
cloned
}
/**
Clones multiple instances to an external weak dom.
This will place the instances as children of the
root of the weak dom, and return their referents.
*/
pub fn clone_multiple_into_external_dom(
referents: &[DomRef],
external_dom: &mut WeakDom,
@ -169,7 +174,7 @@ impl Instance {
### See Also
* [`Clone`](https://create.roblox.com/docs/reference/engine/classes/Instance#Clone)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
#[must_use]
pub fn clone_instance(&self) -> Self {
@ -193,7 +198,7 @@ impl Instance {
### See Also
* [`Destroy`](https://create.roblox.com/docs/reference/engine/classes/Instance#Destroy)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
pub fn destroy(&mut self) -> bool {
if self.is_destroyed() {
@ -220,7 +225,7 @@ impl Instance {
### See Also
* [`Instance::Destroy`] for more info about what happens when an instance gets destroyed
* [`ClearAllChildren`](https://create.roblox.com/docs/reference/engine/classes/Instance#ClearAllChildren)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
pub fn clear_all_children(&mut self) {
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
@ -240,10 +245,10 @@ impl Instance {
### See Also
* [`IsA`](https://create.roblox.com/docs/reference/engine/classes/Instance#IsA)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
pub fn is_a(&self, class_name: impl AsRef<str>) -> bool {
class_is_a(self.class_name, class_name).unwrap_or(false)
class_is_a(&self.class_name, class_name).unwrap_or(false)
}
/**
@ -253,7 +258,7 @@ impl Instance {
### See Also
* [`ClassName`](https://create.roblox.com/docs/reference/engine/classes/Instance#ClassName)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
#[must_use]
pub fn get_class_name(&self) -> &str {
@ -265,7 +270,7 @@ impl Instance {
### See Also
* [`Name`](https://create.roblox.com/docs/reference/engine/classes/Instance#Name)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
pub fn get_name(&self) -> String {
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
@ -281,7 +286,7 @@ impl Instance {
### See Also
* [`Name`](https://create.roblox.com/docs/reference/engine/classes/Instance#Name)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
pub fn set_name(&self, name: impl Into<String>) {
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
@ -296,12 +301,15 @@ impl Instance {
### See Also
* [`Parent`](https://create.roblox.com/docs/reference/engine/classes/Instance#Parent)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
pub fn get_parent(&self) -> Option<Instance> {
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
let parent_ref = dom.get_by_ref(self.dom_ref)?.parent();
let parent_ref = dom
.get_by_ref(self.dom_ref)
.expect("Failed to find instance in document")
.parent();
if parent_ref == dom.root_ref() {
None
@ -316,11 +324,11 @@ impl Instance {
If the provided parent is [`None`] the instance will become orphaned.
An orphaned instance is an instance at the root of Lune's internal weak dom.
An orphaned instance is an instance at the root of a weak dom.
### See Also
* [`Parent`](https://create.roblox.com/docs/reference/engine/classes/Instance#Parent)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
pub fn set_parent(&self, parent: Option<Instance>) {
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
@ -340,7 +348,7 @@ impl Instance {
.get_by_ref(self.dom_ref)
.expect("Failed to find instance in document")
.properties
.get(&ustr(name.as_ref()))
.get(name.as_ref())
.cloned()
}
@ -357,7 +365,7 @@ impl Instance {
.get_by_ref_mut(self.dom_ref)
.expect("Failed to find instance in document")
.properties
.insert(ustr(name.as_ref()), value);
.insert(name.as_ref().to_string(), value);
}
/**
@ -365,7 +373,7 @@ impl Instance {
### See Also
* [`GetAttribute`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetAttribute)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
pub fn get_attribute(&self, name: impl AsRef<str>) -> Option<DomValue> {
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
@ -373,7 +381,7 @@ impl Instance {
.get_by_ref(self.dom_ref)
.expect("Failed to find instance in document");
if let Some(DomValue::Attributes(attributes)) =
inst.properties.get(&ustr(PROPERTY_NAME_ATTRIBUTES))
inst.properties.get(PROPERTY_NAME_ATTRIBUTES)
{
attributes.get(name.as_ref()).cloned()
} else {
@ -386,7 +394,7 @@ impl Instance {
### See Also
* [`GetAttributes`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetAttributes)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
pub fn get_attributes(&self) -> BTreeMap<String, DomValue> {
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
@ -394,7 +402,7 @@ impl Instance {
.get_by_ref(self.dom_ref)
.expect("Failed to find instance in document");
if let Some(DomValue::Attributes(attributes)) =
inst.properties.get(&ustr(PROPERTY_NAME_ATTRIBUTES))
inst.properties.get(PROPERTY_NAME_ATTRIBUTES)
{
attributes.clone().into_iter().collect()
} else {
@ -407,7 +415,7 @@ impl Instance {
### See Also
* [`SetAttribute`](https://create.roblox.com/docs/reference/engine/classes/Instance#SetAttribute)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
pub fn set_attribute(&self, name: impl AsRef<str>, value: DomValue) {
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
@ -421,14 +429,14 @@ impl Instance {
value => value,
};
if let Some(DomValue::Attributes(attributes)) =
inst.properties.get_mut(&ustr(PROPERTY_NAME_ATTRIBUTES))
inst.properties.get_mut(PROPERTY_NAME_ATTRIBUTES)
{
attributes.insert(name.as_ref().to_string(), value);
} else {
let mut attributes = DomAttributes::new();
attributes.insert(name.as_ref().to_string(), value);
inst.properties.insert(
ustr(PROPERTY_NAME_ATTRIBUTES),
PROPERTY_NAME_ATTRIBUTES.to_string(),
DomValue::Attributes(attributes),
);
}
@ -448,11 +456,11 @@ impl Instance {
.get_by_ref_mut(self.dom_ref)
.expect("Failed to find instance in document");
if let Some(DomValue::Attributes(attributes)) =
inst.properties.get_mut(&ustr(PROPERTY_NAME_ATTRIBUTES))
inst.properties.get_mut(PROPERTY_NAME_ATTRIBUTES)
{
attributes.remove(name.as_ref());
if attributes.is_empty() {
inst.properties.remove(&ustr(PROPERTY_NAME_ATTRIBUTES));
inst.properties.remove(PROPERTY_NAME_ATTRIBUTES);
}
}
}
@ -462,18 +470,18 @@ impl Instance {
### See Also
* [`AddTag`](https://create.roblox.com/docs/reference/engine/classes/CollectionService#AddTag)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
pub fn add_tag(&self, name: impl AsRef<str>) {
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
let inst = dom
.get_by_ref_mut(self.dom_ref)
.expect("Failed to find instance in document");
if let Some(DomValue::Tags(tags)) = inst.properties.get_mut(&ustr(PROPERTY_NAME_TAGS)) {
if let Some(DomValue::Tags(tags)) = inst.properties.get_mut(PROPERTY_NAME_TAGS) {
tags.push(name.as_ref());
} else {
inst.properties.insert(
ustr(PROPERTY_NAME_TAGS),
PROPERTY_NAME_TAGS.to_string(),
DomValue::Tags(vec![name.as_ref().to_string()].into()),
);
}
@ -484,14 +492,14 @@ impl Instance {
### See Also
* [`GetTags`](https://create.roblox.com/docs/reference/engine/classes/CollectionService#GetTags)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
pub fn get_tags(&self) -> Vec<String> {
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
let inst = dom
.get_by_ref(self.dom_ref)
.expect("Failed to find instance in document");
if let Some(DomValue::Tags(tags)) = inst.properties.get(&ustr(PROPERTY_NAME_TAGS)) {
if let Some(DomValue::Tags(tags)) = inst.properties.get(PROPERTY_NAME_TAGS) {
tags.iter().map(ToString::to_string).collect()
} else {
Vec::new()
@ -503,14 +511,14 @@ impl Instance {
### See Also
* [`HasTag`](https://create.roblox.com/docs/reference/engine/classes/CollectionService#HasTag)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
pub fn has_tag(&self, name: impl AsRef<str>) -> bool {
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
let inst = dom
.get_by_ref(self.dom_ref)
.expect("Failed to find instance in document");
if let Some(DomValue::Tags(tags)) = inst.properties.get(&ustr(PROPERTY_NAME_TAGS)) {
if let Some(DomValue::Tags(tags)) = inst.properties.get(PROPERTY_NAME_TAGS) {
let name = name.as_ref();
tags.iter().any(|tag| tag == name)
} else {
@ -523,19 +531,21 @@ impl Instance {
### See Also
* [`RemoveTag`](https://create.roblox.com/docs/reference/engine/classes/CollectionService#RemoveTag)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
pub fn remove_tag(&self, name: impl AsRef<str>) {
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
let inst = dom
.get_by_ref_mut(self.dom_ref)
.expect("Failed to find instance in document");
if let Some(DomValue::Tags(tags)) = inst.properties.get_mut(&ustr(PROPERTY_NAME_TAGS)) {
if let Some(DomValue::Tags(tags)) = inst.properties.get_mut(PROPERTY_NAME_TAGS) {
let name = name.as_ref();
let mut new_tags = tags.iter().map(ToString::to_string).collect::<Vec<_>>();
new_tags.retain(|tag| tag != name);
inst.properties
.insert(ustr(PROPERTY_NAME_TAGS), DomValue::Tags(new_tags.into()));
inst.properties.insert(
PROPERTY_NAME_TAGS.to_string(),
DomValue::Tags(new_tags.into()),
);
}
}
@ -547,7 +557,7 @@ impl Instance {
### See Also
* [`GetChildren`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetChildren)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
pub fn get_children(&self) -> Vec<Instance> {
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
@ -570,7 +580,7 @@ impl Instance {
### See Also
* [`GetDescendants`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetDescendants)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
pub fn get_descendants(&self) -> Vec<Instance> {
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
@ -604,7 +614,7 @@ impl Instance {
### See Also
* [`GetFullName`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetFullName)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
pub fn get_full_name(&self) -> String {
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
@ -693,7 +703,8 @@ impl Instance {
predicate callback and a breadth-first search.
### See Also
* [`FindFirstDescendant`](https://create.roblox.com/docs/reference/engine/classes/Instance#FindFirstDescendant) on the Roblox Developer Hub
* [`FindFirstDescendant`](https://create.roblox.com/docs/reference/engine/classes/Instance#FindFirstDescendant)
on the Roblox Developer Hub
*/
pub fn find_descendant<F>(&self, predicate: F) -> Option<Instance>
where
@ -723,11 +734,11 @@ impl Instance {
}
}
impl LuaExportsTable for Instance {
impl LuaExportsTable<'_> for Instance {
const EXPORT_NAME: &'static str = "Instance";
fn create_exports_table(lua: Lua) -> LuaResult<LuaTable> {
let instance_new = |lua: &Lua, class_name: String| {
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
let instance_new = |lua, class_name: String| {
if class_exists(&class_name) {
Instance::new_orphaned(class_name).into_lua(lua)
} else {
@ -756,12 +767,12 @@ impl LuaExportsTable for Instance {
instance registry, and register properties + methods from the lua side
*/
impl LuaUserData for Instance {
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
data_model::add_fields(fields);
workspace::add_fields(fields);
}
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
base::add_methods(methods);
data_model::add_methods(methods);
terrain::add_methods(methods);

View file

@ -58,11 +58,11 @@ impl InstanceRegistry {
- If the method already exists in the registry.
*/
pub fn insert_method(
lua: &Lua,
pub fn insert_method<'lua>(
lua: &'lua Lua,
class_name: &str,
method_name: &str,
method: LuaFunction,
method: LuaFunction<'lua>,
) -> Result<(), InstanceRegistryError> {
let registry = Self::get_or_create(lua);
@ -94,11 +94,11 @@ impl InstanceRegistry {
- If the property already exists in the registry.
*/
pub fn insert_property_getter(
lua: &Lua,
pub fn insert_property_getter<'lua>(
lua: &'lua Lua,
class_name: &str,
property_name: &str,
property_getter: LuaFunction,
property_getter: LuaFunction<'lua>,
) -> Result<(), InstanceRegistryError> {
let registry = Self::get_or_create(lua);
@ -130,11 +130,11 @@ impl InstanceRegistry {
- If the property already exists in the registry.
*/
pub fn insert_property_setter(
lua: &Lua,
pub fn insert_property_setter<'lua>(
lua: &'lua Lua,
class_name: &str,
property_name: &str,
property_setter: LuaFunction,
property_setter: LuaFunction<'lua>,
) -> Result<(), InstanceRegistryError> {
let registry = Self::get_or_create(lua);
@ -165,7 +165,11 @@ impl InstanceRegistry {
Returns `None` if the method is not found.
*/
#[must_use]
pub fn find_method(lua: &Lua, instance: &Instance, method_name: &str) -> Option<LuaFunction> {
pub fn find_method<'lua>(
lua: &'lua Lua,
instance: &Instance,
method_name: &str,
) -> Option<LuaFunction<'lua>> {
let registry = Self::get_or_create(lua);
let methods = registry
.methods
@ -188,11 +192,11 @@ impl InstanceRegistry {
Returns `None` if the property getter is not found.
*/
#[must_use]
pub fn find_property_getter(
lua: &Lua,
pub fn find_property_getter<'lua>(
lua: &'lua Lua,
instance: &Instance,
property_name: &str,
) -> Option<LuaFunction> {
) -> Option<LuaFunction<'lua>> {
let registry = Self::get_or_create(lua);
let getters = registry
.getters
@ -215,11 +219,11 @@ impl InstanceRegistry {
Returns `None` if the property setter is not found.
*/
#[must_use]
pub fn find_property_setter(
lua: &Lua,
pub fn find_property_setter<'lua>(
lua: &'lua Lua,
instance: &Instance,
property_name: &str,
) -> Option<LuaFunction> {
) -> Option<LuaFunction<'lua>> {
let registry = Self::get_or_create(lua);
let setters = registry
.setters

View file

@ -10,7 +10,7 @@ use super::Instance;
pub const CLASS_NAME: &str = "Terrain";
pub fn add_methods<M: LuaUserDataMethods<Instance>>(methods: &mut M) {
pub fn add_methods<'lua, M: LuaUserDataMethods<'lua, Instance>>(methods: &mut M) {
add_class_restricted_method(
methods,
CLASS_NAME,
@ -27,8 +27,9 @@ pub fn add_methods<M: LuaUserDataMethods<Instance>>(methods: &mut M) {
}
fn get_or_create_material_colors(instance: &Instance) -> MaterialColors {
if let Some(Variant::MaterialColors(inner)) = instance.get_property("MaterialColors") {
inner
if let Some(Variant::MaterialColors(material_colors)) = instance.get_property("MaterialColors")
{
material_colors
} else {
MaterialColors::default()
}
@ -39,7 +40,7 @@ fn get_or_create_material_colors(instance: &Instance) -> MaterialColors {
### See Also
* [`GetMaterialColor`](https://create.roblox.com/docs/reference/engine/classes/Terrain#GetMaterialColor)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
fn terrain_get_material_color(_: &Lua, this: &Instance, material: EnumItem) -> LuaResult<Color3> {
let material_colors = get_or_create_material_colors(this);
@ -64,7 +65,7 @@ fn terrain_get_material_color(_: &Lua, this: &Instance, material: EnumItem) -> L
### See Also
* [`SetMaterialColor`](https://create.roblox.com/docs/reference/engine/classes/Terrain#SetMaterialColor)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
fn terrain_set_material_color(
_: &Lua,

View file

@ -6,7 +6,7 @@ use super::Instance;
pub const CLASS_NAME: &str = "Workspace";
pub fn add_fields<F: LuaUserDataFields<Instance>>(f: &mut F) {
pub fn add_fields<'lua, F: LuaUserDataFields<'lua, Instance>>(f: &mut F) {
add_class_restricted_getter(f, CLASS_NAME, "Terrain", workspace_get_terrain);
add_class_restricted_getter(f, CLASS_NAME, "CurrentCamera", workspace_get_camera);
}
@ -16,7 +16,7 @@ pub fn add_fields<F: LuaUserDataFields<Instance>>(f: &mut F) {
### See Also
* [`Terrain`](https://create.roblox.com/docs/reference/engine/classes/Workspace#Terrain)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
fn workspace_get_terrain(_: &Lua, this: &Instance) -> LuaResult<Instance> {
get_or_create_property_ref_instance(this, "Terrain", "Terrain")
@ -27,7 +27,7 @@ fn workspace_get_terrain(_: &Lua, this: &Instance) -> LuaResult<Instance> {
### See Also
* [`CurrentCamera`](https://create.roblox.com/docs/reference/engine/classes/Workspace#CurrentCamera)
on the Roblox Developer Hub
on the Roblox Developer Hub
*/
fn workspace_get_camera(_: &Lua, this: &Instance) -> LuaResult<Instance> {
get_or_create_property_ref_instance(this, "CurrentCamera", "Camera")

View file

@ -14,38 +14,37 @@ pub(crate) mod shared;
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 instance::Instance;
Ok(vec![
// Datatypes
export::<Axes>(lua.clone())?,
export::<BrickColor>(lua.clone())?,
export::<CFrame>(lua.clone())?,
export::<Color3>(lua.clone())?,
export::<ColorSequence>(lua.clone())?,
export::<ColorSequenceKeypoint>(lua.clone())?,
export::<Content>(lua.clone())?,
export::<Faces>(lua.clone())?,
export::<Font>(lua.clone())?,
export::<NumberRange>(lua.clone())?,
export::<NumberSequence>(lua.clone())?,
export::<NumberSequenceKeypoint>(lua.clone())?,
export::<PhysicalProperties>(lua.clone())?,
export::<Ray>(lua.clone())?,
export::<Rect>(lua.clone())?,
export::<UDim>(lua.clone())?,
export::<UDim2>(lua.clone())?,
export::<Region3>(lua.clone())?,
export::<Region3int16>(lua.clone())?,
export::<Vector2>(lua.clone())?,
export::<Vector2int16>(lua.clone())?,
export::<Vector3>(lua.clone())?,
export::<Vector3int16>(lua.clone())?,
export::<Axes>(lua)?,
export::<BrickColor>(lua)?,
export::<CFrame>(lua)?,
export::<Color3>(lua)?,
export::<ColorSequence>(lua)?,
export::<ColorSequenceKeypoint>(lua)?,
export::<Faces>(lua)?,
export::<Font>(lua)?,
export::<NumberRange>(lua)?,
export::<NumberSequence>(lua)?,
export::<NumberSequenceKeypoint>(lua)?,
export::<PhysicalProperties>(lua)?,
export::<Ray>(lua)?,
export::<Rect>(lua)?,
export::<UDim>(lua)?,
export::<UDim2>(lua)?,
export::<Region3>(lua)?,
export::<Region3int16>(lua)?,
export::<Vector2>(lua)?,
export::<Vector2int16>(lua)?,
export::<Vector3>(lua)?,
export::<Vector3int16>(lua)?,
// Classes
export::<Instance>(lua.clone())?,
export::<Instance>(lua)?,
// Singletons
("Enum", Enums.into_lua(&lua)?),
("Enum", Enums.into_lua(lua)?),
])
}
@ -59,13 +58,13 @@ fn create_all_exports(lua: Lua) -> LuaResult<Vec<(&'static str, LuaValue)>> {
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
// index the main exports (this return value) table and
// save some memory and startup time. The full exports
// table is quite big and probably won't get any smaller
// since we impl all roblox constructors for each datatype.
let exports = create_all_exports(lua.clone())?;
let exports = create_all_exports(lua)?;
TableBuilder::new(lua)?
.with_values(exports)?
.build_readonly()

View file

@ -85,7 +85,7 @@ impl DatabaseClass {
}
impl LuaUserData for DatabaseClass {
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
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("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()));
}
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
}

View file

@ -45,12 +45,12 @@ impl DatabaseEnum {
}
impl LuaUserData for DatabaseEnum {
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_field_method_get("Name", |_, this| Ok(this.get_name()));
fields.add_field_method_get("Items", |_, this| Ok(this.get_items()));
}
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
}

View file

@ -111,11 +111,11 @@ impl Database {
}
impl LuaUserData for Database {
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_field_method_get("Version", |_, this| Ok(this.get_version()));
}
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
methods.add_method("GetEnumNames", |_, this, (): ()| Ok(this.get_enum_names()));

View file

@ -69,14 +69,14 @@ impl DatabaseProperty {
}
impl LuaUserData for DatabaseProperty {
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
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("Scriptability", |_, this| Ok(this.get_scriptability_str()));
fields.add_field_method_get("Tags", |_, this| Ok(this.get_tags_str()));
}
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
}

View file

@ -6,14 +6,14 @@ use crate::instance::Instance;
use super::instance::class_is_a;
pub(crate) fn add_class_restricted_getter<F: LuaUserDataFields<Instance>, R, G>(
pub(crate) fn add_class_restricted_getter<'lua, F: LuaUserDataFields<'lua, Instance>, R, G>(
fields: &mut F,
class_name: &'static str,
field_name: &'static str,
field_getter: G,
) where
R: IntoLua,
G: 'static + Fn(&Lua, &Instance) -> LuaResult<R>,
R: IntoLua<'lua>,
G: 'static + Fn(&'lua Lua, &Instance) -> LuaResult<R>,
{
fields.add_field_method_get(field_name, move |lua, this| {
if class_is_a(this.get_class_name(), class_name).unwrap_or(false) {
@ -27,14 +27,14 @@ pub(crate) fn add_class_restricted_getter<F: LuaUserDataFields<Instance>, R, G>(
}
#[allow(dead_code)]
pub(crate) fn add_class_restricted_setter<F: LuaUserDataFields<Instance>, A, G>(
pub(crate) fn add_class_restricted_setter<'lua, F: LuaUserDataFields<'lua, Instance>, A, G>(
fields: &mut F,
class_name: &'static str,
field_name: &'static str,
field_getter: G,
) where
A: FromLua,
G: 'static + Fn(&Lua, &Instance, A) -> LuaResult<()>,
A: FromLua<'lua>,
G: 'static + Fn(&'lua Lua, &Instance, A) -> LuaResult<()>,
{
fields.add_field_method_set(field_name, move |lua, this, value| {
if class_is_a(this.get_class_name(), class_name).unwrap_or(false) {
@ -47,15 +47,15 @@ pub(crate) fn add_class_restricted_setter<F: LuaUserDataFields<Instance>, A, G>(
});
}
pub(crate) fn add_class_restricted_method<M: LuaUserDataMethods<Instance>, A, R, F>(
pub(crate) fn add_class_restricted_method<'lua, M: LuaUserDataMethods<'lua, Instance>, A, R, F>(
methods: &mut M,
class_name: &'static str,
method_name: &'static str,
method: F,
) where
A: FromLuaMulti,
R: IntoLuaMulti,
F: 'static + Fn(&Lua, &Instance, A) -> LuaResult<R>,
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
F: 'static + Fn(&'lua Lua, &Instance, A) -> LuaResult<R>,
{
methods.add_method(method_name, move |lua, this, args| {
if class_is_a(this.get_class_name(), class_name).unwrap_or(false) {
@ -68,15 +68,21 @@ pub(crate) fn add_class_restricted_method<M: LuaUserDataMethods<Instance>, A, R,
});
}
pub(crate) fn add_class_restricted_method_mut<M: LuaUserDataMethods<Instance>, A, R, F>(
pub(crate) fn add_class_restricted_method_mut<
'lua,
M: LuaUserDataMethods<'lua, Instance>,
A,
R,
F,
>(
methods: &mut M,
class_name: &'static str,
method_name: &'static str,
method: F,
) where
A: FromLuaMulti,
R: IntoLuaMulti,
F: 'static + Fn(&Lua, &mut Instance, A) -> LuaResult<R>,
A: FromLuaMulti<'lua>,
R: IntoLuaMulti<'lua>,
F: 'static + Fn(&'lua Lua, &mut Instance, A) -> LuaResult<R>,
{
methods.add_method_mut(method_name, move |lua, this, args| {
if class_is_a(this.get_class_name(), class_name).unwrap_or(false) {
@ -116,7 +122,7 @@ pub(crate) fn get_or_create_property_ref_instance(
Ok(inst)
} else {
let inst = Instance::new_orphaned(class_name);
inst.set_parent(Some(*this));
inst.set_parent(Some(this.clone()));
this.set_property(prop_name, DomValue::Ref(inst.dom_ref));
Ok(inst)
}

View file

@ -23,7 +23,7 @@ pub fn make_list_writer() -> Box<ListWriter> {
})
}
/*
/**
Userdata metamethod implementations
Note that many of these return [`LuaResult`] even though they don't
@ -87,10 +87,10 @@ where
}
}
_ => {}
}
};
Err(LuaError::FromLuaConversionError {
from: rhs.type_name(),
to: type_name::<D>().to_string(),
to: type_name::<D>(),
message: Some(format!(
"Expected {} or number, got {}",
type_name::<D>(),
@ -112,10 +112,10 @@ where
}
}
_ => {}
}
};
Err(LuaError::FromLuaConversionError {
from: rhs.type_name(),
to: type_name::<D>().to_string(),
to: type_name::<D>(),
message: Some(format!(
"Expected {} or number, got {}",
type_name::<D>(),
@ -137,10 +137,10 @@ where
}
}
_ => {}
}
};
Err(LuaError::FromLuaConversionError {
from: rhs.type_name(),
to: type_name::<D>().to_string(),
to: type_name::<D>(),
message: Some(format!(
"Expected {} or number, got {}",
type_name::<D>(),
@ -168,10 +168,10 @@ where
}
}
_ => {}
}
};
Err(LuaError::FromLuaConversionError {
from: rhs.type_name(),
to: type_name::<D>().to_string(),
to: type_name::<D>(),
message: Some(format!(
"Expected {} or number, got {}",
type_name::<D>(),
@ -193,10 +193,10 @@ where
}
}
_ => {}
}
};
Err(LuaError::FromLuaConversionError {
from: rhs.type_name(),
to: type_name::<D>().to_string(),
to: type_name::<D>(),
message: Some(format!(
"Expected {} or number, got {}",
type_name::<D>(),

View file

@ -1,6 +1,6 @@
[package]
name = "lune-std-datetime"
version = "0.2.2"
version = "0.1.1"
edition = "2021"
license = "MPL-2.0"
repository = "https://github.com/lune-org/lune"
@ -13,10 +13,10 @@ path = "src/lib.rs"
workspace = true
[dependencies]
mlua = { version = "0.10.3", features = ["luau"] }
mlua = { version = "0.9.7", features = ["luau"] }
thiserror = "2.0"
thiserror = "1.0"
chrono = "0.4.38"
chrono_lc = "0.1.6"
lune-utils = { version = "0.2.2", path = "../lune-utils" }
lune-utils = { version = "0.1.0", path = "../lune-utils" }

View file

@ -159,7 +159,7 @@ impl DateTime {
}
/**
Parses a time string in the RFC 3339 format, such as
Parses a time string in the ISO 8601 format, such as
`1996-12-19T16:39:57-08:00`, into a new `DateTime` struct.
See [`chrono::DateTime::parse_from_rfc3339`] for additional details.
@ -168,23 +168,8 @@ impl DateTime {
Returns an error if the input string is not a valid RFC 3339 date-time.
*/
pub fn from_rfc_3339(date: impl AsRef<str>) -> DateTimeResult<Self> {
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);
pub fn from_iso_date(iso_date: impl AsRef<str>) -> DateTimeResult<Self> {
let inner = ChronoDateTime::parse_from_rfc3339(iso_date.as_ref())?.with_timezone(&Utc);
Ok(Self { inner })
}
@ -207,35 +192,25 @@ impl DateTime {
}
/**
Formats a time string in the RFC 3339 format, such as `1996-12-19T16:39:57-08:00`.
Formats a time string in the ISO 8601 format, such as `1996-12-19T16:39:57-08:00`.
See [`chrono::DateTime::to_rfc3339`] for additional details.
*/
#[must_use]
pub fn to_rfc_3339(self) -> String {
pub fn to_iso_date(self) -> String {
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 {
fn add_fields<F: LuaUserDataFields<Self>>(fields: &mut F) {
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
fields.add_field_method_get("unixTimestamp", |_, this| Ok(this.inner.timestamp()));
fields.add_field_method_get("unixTimestampMillis", |_, this| {
Ok(this.inner.timestamp_millis())
});
}
fn add_methods<M: LuaUserDataMethods<Self>>(methods: &mut M) {
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
// Metamethods to compare DateTime as instants in time
methods.add_meta_method(
LuaMetaMethod::Eq,
@ -254,9 +229,7 @@ impl LuaUserData for DateTime {
},
);
// Normal methods
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("toIsoDate", |_, this, ()| Ok(this.to_iso_date()));
methods.add_method(
"formatUniversalTime",
|_, this, (format, locale): (Option<String>, Option<String>)| {

View file

@ -10,16 +10,6 @@ mod values;
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.
@ -27,16 +17,10 @@ pub fn typedefs() -> String {
Errors when out of memory.
*/
pub fn module(lua: Lua) -> LuaResult<LuaTable> {
pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
TableBuilder::new(lua)?
.with_function("fromIsoDate", |_, date: String| {
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("fromIsoDate", |_, iso_date: String| {
Ok(DateTime::from_iso_date(iso_date)?)
})?
.with_function("fromLocalTime", |_, values| {
Ok(DateTime::from_local_time(&values)?)

View file

@ -60,7 +60,7 @@ where
}
}
/*
/**
Conversion methods between `DateTimeValues` and plain lua tables
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
*/
impl FromLua for DateTimeValues {
impl FromLua<'_> for DateTimeValues {
fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
if !value.is_table() {
return Err(LuaError::FromLuaConversionError {
from: value.type_name(),
to: "DateTimeValues".to_string(),
to: "DateTimeValues",
message: Some("value must be a table".to_string()),
});
}
};
let value = value.as_table().unwrap();
let values = Self {
@ -93,16 +93,16 @@ impl FromLua for DateTimeValues {
Ok(dt) => Ok(dt),
Err(e) => Err(LuaError::FromLuaConversionError {
from: "table",
to: "DateTimeValues".to_string(),
to: "DateTimeValues",
message: Some(e.to_string()),
}),
}
}
}
impl IntoLua for DateTimeValues {
impl IntoLua<'_> for DateTimeValues {
fn into_lua(self, lua: &Lua) -> LuaResult<LuaValue> {
let tab = TableBuilder::new(lua.clone())?
let tab = TableBuilder::new(lua)?
.with_value("year", self.year)?
.with_values(vec![
("month", self.month),
@ -117,7 +117,7 @@ impl IntoLua for DateTimeValues {
}
}
/*
/**
Conversion methods between chrono's timezone-aware `DateTime` to
and from our non-timezone-aware `DateTimeValues` values struct
*/

View file

@ -1,6 +1,6 @@
[package]
name = "lune-std-fs"
version = "0.2.2"
version = "0.1.0"
edition = "2021"
license = "MPL-2.0"
repository = "https://github.com/lune-org/lune"
@ -13,11 +13,11 @@ path = "src/lib.rs"
workspace = true
[dependencies]
mlua = { version = "0.10.3", features = ["luau"] }
mlua = { version = "0.9.7", features = ["luau"] }
async-fs = "2.1"
bstr = "1.9"
futures-lite = "2.6"
lune-utils = { version = "0.2.2", path = "../lune-utils" }
lune-std-datetime = { version = "0.2.2", path = "../lune-std-datetime" }
tokio = { version = "1", default-features = false, features = ["fs"] }
lune-utils = { version = "0.1.0", path = "../lune-utils" }
lune-std-datetime = { version = "0.1.0", path = "../lune-std-datetime" }

View file

@ -2,9 +2,8 @@ use std::collections::VecDeque;
use std::io::ErrorKind;
use std::path::{Path, PathBuf};
use async_fs as fs;
use futures_lite::prelude::*;
use mlua::prelude::*;
use tokio::fs;
use super::options::FsWriteOptions;
@ -25,8 +24,8 @@ async fn get_contents_at(root: PathBuf, _: FsWriteOptions) -> LuaResult<CopyCont
})?;
// Push initial children of the root path into the queue
let mut reader = fs::read_dir(&normalized_root).await?;
while let Some(entry) = reader.try_next().await? {
let mut entries = fs::read_dir(&normalized_root).await?;
while let Some(entry) = entries.next_entry().await? {
queue.push_back((1, entry.path()));
}
@ -43,7 +42,7 @@ async fn get_contents_at(root: PathBuf, _: FsWriteOptions) -> LuaResult<CopyCont
} else if meta.is_dir() {
// FUTURE: Add an option in FsWriteOptions for max depth and limit it here
let mut entries = fs::read_dir(&current_path).await?;
while let Some(entry) = entries.try_next().await? {
while let Some(entry) = entries.next_entry().await? {
queue.push_back((current_depth + 1, entry.path()));
}
dirs.push((current_depth, current_path));

64
crates/lune-std-fs/src/lib.rs Executable file → Normal file
View file

@ -1,12 +1,11 @@
#![allow(clippy::cargo_common_metadata)]
use std::io::ErrorKind as IoErrorKind;
use std::path::PathBuf;
use std::path::{PathBuf, MAIN_SEPARATOR};
use async_fs as fs;
use bstr::{BString, ByteSlice};
use futures_lite::prelude::*;
use mlua::prelude::*;
use tokio::fs;
use lune_utils::TableBuilder;
@ -18,16 +17,6 @@ use self::copy::copy;
use self::metadata::FsMetadata;
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.
@ -35,7 +24,7 @@ pub fn typedefs() -> String {
Errors when out of memory.
*/
pub fn module(lua: Lua) -> LuaResult<LuaTable> {
pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
TableBuilder::new(lua)?
.with_async_function("readFile", fs_read_file)?
.with_async_function("readDir", fs_read_dir)?
@ -51,45 +40,58 @@ pub fn module(lua: Lua) -> LuaResult<LuaTable> {
.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()?;
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 = fs::read_dir(&path).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() {
dir_strings.push(dir_name_str.to_owned());
while let Some(dir_entry) = dir.next_entry().await.into_lua_err()? {
if let Some(dir_path_str) = dir_entry.path().to_str() {
dir_strings.push(dir_path_str.to_owned());
} else {
return Err(LuaError::RuntimeError(format!(
"File name could not be converted into a string: '{}'",
dir_entry.file_name().to_string_lossy()
"File path could not be converted into a string: '{}'",
dir_entry.path().display()
)));
}
}
Ok(dir_strings)
let mut dir_string_prefix = path;
if !dir_string_prefix.ends_with(MAIN_SEPARATOR) {
dir_string_prefix.push(MAIN_SEPARATOR);
}
let dir_strings_no_prefix = dir_strings
.iter()
.map(|inner_path| {
inner_path
.trim()
.trim_start_matches(&dir_string_prefix)
.to_owned()
})
.collect::<Vec<_>>();
Ok(dir_strings_no_prefix)
}
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()
}
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()
}
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()
}
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()
}
async fn fs_metadata(_: Lua, path: String) -> LuaResult<FsMetadata> {
async fn fs_metadata(_: &Lua, path: String) -> LuaResult<FsMetadata> {
match fs::metadata(path).await {
Err(e) if e.kind() == IoErrorKind::NotFound => Ok(FsMetadata::not_found()),
Ok(meta) => Ok(FsMetadata::from(meta)),
@ -97,7 +99,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 {
Err(e) if e.kind() == IoErrorKind::NotFound => Ok(false),
Ok(meta) => Ok(meta.is_file()),
@ -105,7 +107,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 {
Err(e) if e.kind() == IoErrorKind::NotFound => Ok(false),
Ok(meta) => Ok(meta.is_dir()),
@ -113,7 +115,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);
if !path_from.exists() {
return Err(LuaError::RuntimeError(format!(
@ -132,6 +134,6 @@ async fn fs_move(_: Lua, (from, to, options): (String, String, FsWriteOptions))
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
}

View file

@ -61,8 +61,8 @@ impl From<StdFileType> for FsMetadataKind {
}
}
impl IntoLua for FsMetadataKind {
fn into_lua(self, lua: &Lua) -> LuaResult<LuaValue> {
impl<'lua> IntoLua<'lua> for FsMetadataKind {
fn into_lua(self, lua: &'lua Lua) -> LuaResult<LuaValue<'lua>> {
if self == Self::None {
Ok(LuaValue::Nil)
} else {
@ -84,8 +84,8 @@ impl From<StdPermissions> for FsPermissions {
}
}
impl IntoLua for FsPermissions {
fn into_lua(self, lua: &Lua) -> LuaResult<LuaValue> {
impl<'lua> IntoLua<'lua> for FsPermissions {
fn into_lua(self, lua: &'lua Lua) -> LuaResult<LuaValue<'lua>> {
let tab = lua.create_table_with_capacity(0, 1)?;
tab.set("readOnly", self.read_only)?;
tab.set_readonly(true);
@ -116,8 +116,8 @@ impl FsMetadata {
}
}
impl IntoLua for FsMetadata {
fn into_lua(self, lua: &Lua) -> LuaResult<LuaValue> {
impl<'lua> IntoLua<'lua> for FsMetadata {
fn into_lua(self, lua: &'lua Lua) -> LuaResult<LuaValue<'lua>> {
let tab = lua.create_table_with_capacity(0, 6)?;
tab.set("kind", self.kind)?;
tab.set("exists", self.exists)?;

View file

@ -5,8 +5,8 @@ pub struct FsWriteOptions {
pub(crate) overwrite: bool,
}
impl FromLua for FsWriteOptions {
fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
impl<'lua> FromLua<'lua> for FsWriteOptions {
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
Ok(match value {
LuaValue::Nil => Self { overwrite: false },
LuaValue::Boolean(b) => Self { overwrite: b },
@ -19,7 +19,7 @@ impl FromLua for FsWriteOptions {
_ => {
return Err(LuaError::FromLuaConversionError {
from: value.type_name(),
to: "FsWriteOptions".to_string(),
to: "FsWriteOptions",
message: Some(format!(
"Invalid write options - expected boolean or table, got {}",
value.type_name()

View file

@ -1,6 +1,6 @@
[package]
name = "lune-std-luau"
version = "0.2.2"
version = "0.1.0"
edition = "2021"
license = "MPL-2.0"
repository = "https://github.com/lune-org/lune"
@ -13,6 +13,6 @@ path = "src/lib.rs"
workspace = true
[dependencies]
mlua = { version = "0.10.3", features = ["luau", "luau-jit"] }
mlua = { version = "0.9.7", features = ["luau"] }
lune-utils = { version = "0.2.2", path = "../lune-utils" }
lune-utils = { version = "0.1.0", path = "../lune-utils" }

View file

@ -2,21 +2,13 @@
use mlua::prelude::*;
use lune_utils::{jit::JitEnablement, TableBuilder};
use lune_utils::TableBuilder;
mod options;
use self::options::{LuauCompileOptions, LuauLoadOptions};
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()
}
const BYTECODE_ERROR_BYTE: u8 = 0;
/**
Creates the `luau` standard library module.
@ -25,71 +17,53 @@ pub fn typedefs() -> String {
Errors when out of memory.
*/
pub fn module(lua: Lua) -> LuaResult<LuaTable> {
pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
TableBuilder::new(lua)?
.with_function("compile", compile_source)?
.with_function("load", load_source)?
.build_readonly()
}
fn compile_source(
lua: &Lua,
(source, options): (LuaString, LuauCompileOptions),
) -> LuaResult<LuaString> {
options
.into_compiler()
.compile(source.as_bytes())
.and_then(|s| lua.create_string(s))
fn compile_source<'lua>(
lua: &'lua Lua,
(source, options): (LuaString<'lua>, LuauCompileOptions),
) -> LuaResult<LuaString<'lua>> {
let bytecode = options.into_compiler().compile(source);
match bytecode.first() {
Some(&BYTECODE_ERROR_BYTE) => Err(LuaError::RuntimeError(
String::from_utf8_lossy(&bytecode).into_owned(),
)),
Some(_) => lua.create_string(bytecode),
None => panic!("Compiling resulted in empty bytecode"),
}
}
fn load_source(
lua: &Lua,
(source, options): (LuaString, LuauLoadOptions),
) -> LuaResult<LuaFunction> {
let mut chunk = lua
.load(source.as_bytes().to_vec())
.set_name(options.debug_name);
let env_changed = options.environment.is_some();
fn load_source<'lua>(
lua: &'lua Lua,
(source, options): (LuaString<'lua>, LuauLoadOptions),
) -> LuaResult<LuaFunction<'lua>> {
let mut chunk = lua.load(source.as_bytes()).set_name(options.debug_name);
if let Some(custom_environment) = options.environment {
let environment = lua.create_table()?;
if let Some(environment) = options.environment {
let environment_with_globals = lua.create_table()?;
// Inject all globals into the environment
if options.inject_globals {
for pair in lua.globals().pairs() {
let (key, value): (LuaValue, LuaValue) = pair?;
environment.set(key, value)?;
}
if let Some(global_metatable) = lua.globals().metatable() {
environment.set_metatable(Some(global_metatable));
}
} else if let Some(custom_metatable) = custom_environment.metatable() {
// Since we don't need to set the global metatable,
// we can just set a custom metatable if it exists
environment.set_metatable(Some(custom_metatable));
if let Some(meta) = environment.get_metatable() {
environment_with_globals.set_metatable(Some(meta));
}
// Inject the custom environment
for pair in custom_environment.pairs() {
for pair in lua.globals().pairs() {
let (key, value): (LuaValue, LuaValue) = pair?;
environment.set(key, value)?;
environment_with_globals.set(key, value)?;
}
chunk = chunk.set_environment(environment);
for pair in environment.pairs() {
let (key, value): (LuaValue, LuaValue) = pair?;
environment_with_globals.set(key, value)?;
}
chunk = chunk.set_environment(environment_with_globals);
}
// Enable JIT if codegen is enabled and the environment hasn't
// changed, otherwise disable JIT since it'll fall back anyways
lua.enable_jit(options.codegen_enabled && !env_changed);
let function = chunk.into_function()?;
lua.enable_jit(
lua.app_data_ref::<JitEnablement>()
.ok_or(LuaError::runtime(
"Failed to get current JitStatus ref from AppData",
))?
.enabled(),
);
Ok(function)
chunk.into_function()
}

View file

@ -36,8 +36,8 @@ impl Default for LuauCompileOptions {
}
}
impl FromLua for LuauCompileOptions {
fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
impl<'lua> FromLua<'lua> for LuauCompileOptions {
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
Ok(match value {
LuaValue::Nil => Self::default(),
LuaValue::Table(t) => {
@ -68,7 +68,7 @@ impl FromLua for LuauCompileOptions {
_ => {
return Err(LuaError::FromLuaConversionError {
from: value.type_name(),
to: "CompileOptions".to_string(),
to: "CompileOptions",
message: Some(format!(
"Invalid compile options - expected table, got {}",
value.type_name()
@ -79,26 +79,26 @@ impl FromLua for LuauCompileOptions {
}
}
pub struct LuauLoadOptions {
/**
Options for loading Lua source code.
*/
#[derive(Debug, Clone)]
pub struct LuauLoadOptions<'lua> {
pub(crate) debug_name: String,
pub(crate) environment: Option<LuaTable>,
pub(crate) inject_globals: bool,
pub(crate) codegen_enabled: bool,
pub(crate) environment: Option<LuaTable<'lua>>,
}
impl Default for LuauLoadOptions {
impl Default for LuauLoadOptions<'_> {
fn default() -> Self {
Self {
debug_name: DEFAULT_DEBUG_NAME.to_string(),
environment: None,
inject_globals: true,
codegen_enabled: false,
}
}
}
impl FromLua for LuauLoadOptions {
fn from_lua(value: LuaValue, _: &Lua) -> LuaResult<Self> {
impl<'lua> FromLua<'lua> for LuauLoadOptions<'lua> {
fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult<Self> {
Ok(match value {
LuaValue::Nil => Self::default(),
LuaValue::Table(t) => {
@ -112,26 +112,16 @@ impl FromLua for LuauLoadOptions {
options.environment = Some(environment);
}
if let Some(inject_globals) = t.get("injectGlobals")? {
options.inject_globals = inject_globals;
}
if let Some(codegen_enabled) = t.get("codegenEnabled")? {
options.codegen_enabled = codegen_enabled;
}
options
}
LuaValue::String(s) => Self {
debug_name: s.to_string_lossy().to_string(),
environment: None,
inject_globals: true,
codegen_enabled: false,
},
_ => {
return Err(LuaError::FromLuaConversionError {
from: value.type_name(),
to: "LoadOptions".to_string(),
to: "LoadOptions",
message: Some(format!(
"Invalid load options - expected string or table, got {}",
value.type_name()

View file

@ -1,6 +1,6 @@
[package]
name = "lune-std-net"
version = "0.2.2"
version = "0.1.0"
edition = "2021"
license = "MPL-2.0"
repository = "https://github.com/lune-org/lune"
@ -13,30 +13,27 @@ path = "src/lib.rs"
workspace = true
[dependencies]
mlua = { version = "0.10.3", features = ["luau"] }
mlua-luau-scheduler = { version = "0.1.2", path = "../mlua-luau-scheduler" }
mlua = { version = "0.9.7", features = ["luau"] }
mlua-luau-scheduler = "0.0.2"
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"
form_urlencoded = "1.2"
futures = { version = "0.3", default-features = false, features = ["std"] }
futures-lite = "2.6"
futures-rustls = "0.26"
http-body-util = "0.1"
hyper = { version = "1.6", default-features = false, features = ["http1", "client", "server"] }
pin-project-lite = "0.2"
rustls = { version = "0.23", default-features = false, features = ["std", "tls12", "ring"] }
rustls-pki-types = "1.11"
url = "2.5"
futures-util = "0.3"
hyper = { version = "1.1", features = ["full"] }
hyper-util = { version = "0.1", features = ["full"] }
http = "1.0"
http-body-util = { version = "0.1" }
hyper-tungstenite = { version = "0.13" }
reqwest = { version = "0.11", default-features = false, features = [
"rustls-tls",
] }
tokio-tungstenite = { version = "0.21", features = ["rustls-tls-webpki-roots"] }
urlencoding = "2.1"
webpki = "0.22"
webpki-roots = "0.26"
lune-utils = { version = "0.2.2", path = "../lune-utils" }
lune-std-serde = { version = "0.2.2", path = "../lune-std-serde" }
tokio = { version = "1", default-features = false, features = [
"sync",
"net",
"macros",
] }
lune-utils = { version = "0.1.0", path = "../lune-utils" }
lune-std-serde = { version = "0.1.0", path = "../lune-std-serde" }

View file

@ -1,59 +0,0 @@
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,
}
}
}

View file

@ -1,35 +0,0 @@
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))
}

View file

@ -1,110 +0,0 @@
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)
}
}

View file

@ -1,11 +0,0 @@
#![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;

View file

@ -1,105 +0,0 @@
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()
)),
}),
}
}
}

View file

@ -0,0 +1,163 @@
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()
}
}

View file

@ -1,95 +0,0 @@
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())
}

View file

@ -1,125 +0,0 @@
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))
}

View file

@ -1,26 +0,0 @@
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()
});

View file

@ -1,114 +0,0 @@
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())
}

View file

@ -0,0 +1,231 @@
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,
})
}
}
}

View file

@ -1,29 +1,26 @@
#![allow(clippy::cargo_common_metadata)]
use lune_utils::TableBuilder;
use bstr::BString;
use mlua::prelude::*;
use mlua_luau_scheduler::LuaSpawnExt;
pub(crate) mod body;
pub(crate) mod client;
pub(crate) mod server;
pub(crate) mod shared;
pub(crate) mod url;
mod client;
mod config;
mod server;
mod util;
mod websocket;
use lune_utils::TableBuilder;
use self::{
client::ws_stream::WsStream,
server::config::ServeConfig,
shared::{request::Request, response::Response, websocket::Websocket},
client::{NetClient, NetClientBuilder},
config::{RequestConfig, ServeConfig},
server::serve,
util::create_user_agent_header,
websocket::NetWebSocket,
};
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()
}
use lune_std_serde::{decode, encode, EncodeDecodeConfig, EncodeDecodeFormat};
/**
Creates the `net` standard library module.
@ -32,10 +29,14 @@ pub fn typedefs() -> String {
Errors when out of memory.
*/
pub fn module(lua: Lua) -> LuaResult<LuaTable> {
// No initial rustls setup is necessary, the respective
// functions lazily initialize anything there as needed
pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
NetClientBuilder::new()
.headers(&[("User-Agent", create_user_agent_header(lua)?)])?
.build()?
.into_registry(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("socket", net_socket)?
.with_async_function("serve", net_serve)?
@ -44,35 +45,58 @@ pub fn module(lua: Lua) -> LuaResult<LuaTable> {
.build_readonly()
}
async fn net_request(lua: Lua, req: Request) -> LuaResult<Response> {
self::client::send_request(req, lua).await
fn net_json_encode<'lua>(
lua: &'lua Lua,
(val, pretty): (LuaValue<'lua>, Option<bool>),
) -> LuaResult<LuaString<'lua>> {
let config = EncodeDecodeConfig::from((EncodeDecodeFormat::Json, pretty.unwrap_or_default()));
encode(val, lua, config)
}
async fn net_socket(_: Lua, url: String) -> LuaResult<Websocket<WsStream>> {
let url = url.parse().into_lua_err()?;
self::client::connect_websocket(url).await
fn net_json_decode(lua: &Lua, json: BString) -> LuaResult<LuaValue> {
let config = EncodeDecodeConfig::from(EncodeDecodeFormat::Json);
decode(json, lua, config)
}
async fn net_serve(lua: Lua, (port, config): (u16, ServeConfig)) -> LuaResult<LuaTable> {
self::server::serve(lua.clone(), port, config)
.await?
.into_lua_table(lua)
async fn net_request(lua: &Lua, config: RequestConfig) -> LuaResult<LuaTable> {
let client = NetClient::from_registry(lua);
// NOTE: We spawn the request as a background task to free up resources in lua
let res = lua.spawn(async move { client.request(config).await });
res.await?.into_lua_table(lua)
}
fn net_url_encode(
lua: &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_socket(lua: &Lua, url: String) -> LuaResult<LuaTable> {
let (ws, _) = tokio_tungstenite::connect_async(url).await.into_lua_err()?;
NetWebSocket::new(ws).into_lua_table(lua)
}
fn net_url_decode(
lua: &Lua,
(lua_string, as_binary): (LuaString, Option<bool>),
) -> LuaResult<LuaString> {
let as_binary = as_binary.unwrap_or_default();
let bytes = self::url::decode(lua_string, as_binary)?;
lua.create_string(bytes)
async fn net_serve<'lua>(
lua: &'lua Lua,
(port, config): (u16, ServeConfig<'lua>),
) -> LuaResult<LuaTable<'lua>> {
serve(lua, port, config).await
}
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)
}
}

View file

@ -1,87 +0,0 @@
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,
})
}
}
}

View file

@ -1,72 +0,0 @@
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(())
}
});
}
}

View file

@ -0,0 +1,61 @@
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()
}
}

View file

@ -1,121 +1,105 @@
use std::{cell::Cell, net::SocketAddr, rc::Rc};
use std::{
net::SocketAddr,
rc::{Rc, Weak},
};
use async_net::TcpListener;
use futures_lite::pin;
use hyper::server::conn::http1::Builder as Http1Builder;
use hyper::server::conn::http1;
use hyper_util::rt::TokioIo;
use tokio::{net::TcpListener, pin};
use mlua::prelude::*;
use mlua_luau_scheduler::LuaSpawnExt;
use crate::{
server::{config::ServeConfig, handle::ServeHandle, service::Service},
shared::{
futures::{either, Either},
hyper::{HyperIo, HyperTimer},
},
};
use lune_utils::TableBuilder;
pub mod config;
pub mod handle;
pub mod service;
pub mod upgrade;
use super::config::ServeConfig;
/**
Starts an HTTP server using the given port and configuration.
mod keys;
mod request;
mod response;
mod service;
Returns a `ServeHandle` that can be used to gracefully stop the server.
*/
pub async fn serve(lua: Lua, port: u16, config: ServeConfig) -> LuaResult<ServeHandle> {
let address = SocketAddr::from((config.address, port));
let service = Service {
lua: lua.clone(),
address,
config,
use keys::SvcKeys;
use service::Svc;
pub async fn serve<'lua>(
lua: &'lua Lua,
port: u16,
config: ServeConfig<'lua>,
) -> LuaResult<LuaTable<'lua>> {
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 listener = TcpListener::bind(address).await?;
let (handle, shutdown_rx) = ServeHandle::new(address);
let keys = SvcKeys::new(lua, config.handle_request, config.handle_web_socket)?;
let svc = Svc {
lua: lua_svc,
addr,
keys,
};
lua.spawn_local({
let lua = lua.clone();
async move {
let handle_dropped = Rc::new(Cell::new(false));
loop {
// 1. Keep accepting new connections until we should shutdown
let (conn, addr) = if handle_dropped.get() {
// 1a. Handle has been dropped, and we don't need to listen for shutdown
match listener.accept().await {
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 (shutdown_tx, shutdown_rx) = tokio::sync::watch::channel(false);
lua.spawn_local(async move {
let mut shutdown_rx_outer = shutdown_rx.clone();
loop {
// Create futures for accepting new connections and shutting down
let fut_shutdown = shutdown_rx_outer.changed();
let fut_accept = async {
let stream = match listener.accept().await {
Err(_) => return,
Ok((s, _)) => s,
};
// 2. For each connection, spawn a new task to handle it
lua.spawn_local({
let rx = shutdown_rx.clone();
let io = HyperIo::from(conn);
let io = TokioIo::new(stream);
let svc = svc.clone();
let mut shutdown_rx_inner = shutdown_rx.clone();
let mut svc = service.clone();
svc.address = addr;
let handle_dropped = Rc::clone(&handle_dropped);
async move {
let conn = Http1Builder::new()
.writev(false)
.timer(HyperTimer)
.keep_alive(true)
.serve_connection(io, svc)
.with_upgrades();
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
}
}
lua_inner.spawn_local(async move {
let conn = http1::Builder::new()
.keep_alive(true) // Web sockets need this
.serve_connection(io, svc)
.with_upgrades();
// NOTE: Because we need to use keep_alive for websockets, we need to
// also manually poll this future and handle the shutdown signal here
pin!(conn);
tokio::select! {
_ = conn.as_mut() => {}
_ = shutdown_rx_inner.changed() => {
conn.as_mut().graceful_shutdown();
}
}
});
};
// 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;
}
}
}
}
});
Ok(handle)
TableBuilder::new(lua)?
.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()
}

View file

@ -0,0 +1,54 @@
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)?;
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<_>>()?;
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()
}
}

View file

@ -0,0 +1,89 @@
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,
}),
}
}
}

View file

@ -1,116 +1,82 @@
use std::{future::Future, net::SocketAddr, pin::Pin};
use std::{future::Future, net::SocketAddr, pin::Pin, rc::Rc};
use async_tungstenite::{tungstenite::protocol::Role, WebSocketStream};
use http_body_util::{BodyExt, Full};
use hyper::{
body::Incoming, service::Service as HyperService, Request as HyperRequest,
Response as HyperResponse, StatusCode,
body::{Bytes, Incoming},
service::Service,
Request, Response,
};
use hyper_tungstenite::{is_upgrade_request, upgrade};
use mlua::prelude::*;
use mlua_luau_scheduler::{LuaSchedulerExt, LuaSpawnExt};
use crate::{
body::ReadableBody,
server::{
config::ServeConfig,
upgrade::{is_upgrade_request, make_upgrade_response},
},
shared::{hyper::HyperIo, request::Request, response::Response, websocket::Websocket},
use super::{
super::websocket::NetWebSocket, keys::SvcKeys, request::LuaRequest, response::LuaResponse,
};
#[derive(Debug, Clone)]
pub(super) struct Service {
pub(super) lua: Lua,
pub(super) address: SocketAddr, // NOTE: This must be the remote address of the connected client
pub(super) config: ServeConfig,
pub(super) struct Svc {
pub(super) lua: Rc<Lua>,
pub(super) addr: SocketAddr,
pub(super) keys: SvcKeys,
}
impl HyperService<HyperRequest<Incoming>> for Service {
type Response = HyperResponse<ReadableBody>;
impl Service<Request<Incoming>> for Svc {
type Response = Response<Full<Bytes>>;
type Error = LuaError;
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
fn call(&self, req: HyperRequest<Incoming>) -> Self::Future {
if is_upgrade_request(&req) {
if let Some(handler) = self.config.handle_web_socket.clone() {
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())
}
};
lua.spawn_local({
let lua = lua.clone();
async move {
if let Err(_err) = handle_websocket(lua, handler, req).await {
// TODO: Propagate the error somehow?
}
}
});
Ok(response)
});
}
}
fn call(&self, req: Request<Incoming>) -> Self::Future {
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())
}
}
})
let addr = self.addr;
let keys = self.keys;
if keys.has_websocket_handler() && is_upgrade_request(&req) {
Box::pin(async move {
let (res, sock) = upgrade(req, None).into_lua_err()?;
let lua_inner = lua.clone();
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()
})
}
}
}
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(())
}

View file

@ -1,56 +0,0 @@
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())
}

View file

@ -1,19 +0,0 @@
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)
}

View file

@ -1,88 +0,0 @@
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()
}

View file

@ -1,198 +0,0 @@
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)
}
}

View file

@ -1,41 +0,0 @@
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)
}

View file

@ -1,7 +0,0 @@
pub mod futures;
pub mod headers;
pub mod hyper;
pub mod lua;
pub mod request;
pub mod response;
pub mod websocket;

View file

@ -1,256 +0,0 @@
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