Compare commits
416 commits
Author | SHA1 | Date | |
---|---|---|---|
|
27e3efca97 | ||
|
8bd1a9b77d | ||
|
bb8c4bce82 | ||
|
6902ecaa7c | ||
|
dc08b91314 | ||
|
822dd19393 | ||
6cd0234a5f | |||
|
19e7f57284 | ||
|
5d1401cdf6 | ||
|
91af86cca2 | ||
|
c935149c1e | ||
|
e5bda57665 | ||
|
ef294f207c | ||
|
f89d02a60d | ||
|
d090cd2420 | ||
|
99c17795c1 | ||
|
138221b93e | ||
|
8abfc21181 | ||
309c461e11 | |||
|
93fa14d832 | ||
df4fb9be91 | |||
eaac9ff53a | |||
|
0d2f5539b6 | ||
|
0f4cac29aa | ||
|
010cd36375 | ||
|
c17da72815 | ||
|
ff83c401b8 | ||
|
a007fa94a6 | ||
|
1d4d1635eb | ||
|
56f08a88aa | ||
|
833d0e244b | ||
|
3e09807638 | ||
|
ea7013322f | ||
|
98b31b9f67 | ||
|
8364a8e4de | ||
|
473ad80e8f | ||
|
180d20ce4a | ||
|
b585234b08 | ||
|
5379c79488 | ||
|
8aefe88104 | ||
|
cb552af660 | ||
|
95c2ca0965 | ||
|
5167a71e6f | ||
|
eac34d2e7e | ||
|
ff80981282 | ||
|
45493dc23b | ||
|
359f28133f | ||
|
c7cbda98fe | ||
|
a7ac864ca5 | ||
|
9993e03f04 | ||
|
997653eb4a | ||
|
f94fbc685a | ||
|
e2e8beb45c | ||
6b38a21454 | |||
|
430d5683f0 | ||
|
59a7955132 | ||
|
1fb1d3e7b5 | ||
|
0efc2c565b | ||
|
c94ab0cde1 | ||
|
d3b9a4b9e8 | ||
|
3cf2be51bc | ||
|
a3f0f279a8 | ||
|
a94c9d6d54 | ||
|
63493e78de | ||
|
8cb7b8a13a | ||
|
9d9f1685d8 | ||
|
91ac6b00c1 | ||
|
2a85532448 | ||
|
5a292aabc5 | ||
|
cf513c6724 | ||
|
b628601cc8 | ||
|
649bdc4c31 | ||
|
3030158159 | ||
|
23456ae041 | ||
|
4f6f1835d2 | ||
|
636d0bf277 | ||
|
adc74f47c0 | ||
|
1fd17ca0b3 | ||
|
9498620e03 | ||
|
0850f41617 | ||
|
f2c40a4bd5 | ||
|
bfb89dec01 | ||
|
395c36fa8b | ||
|
7e784ba361 | ||
|
95b81d65fe | ||
|
43de7b277a | ||
|
1d0bab5e65 | ||
|
e0f065b678 | ||
|
5b5c46f802 | ||
|
489c8a6609 | ||
|
a13b17fee6 | ||
|
e0b9ceb86d | ||
|
6bc1fa2343 | ||
|
763b3ff6a7 | ||
|
816b6654da | ||
|
efcc3c6028 | ||
|
de71558c5d | ||
|
3f53fc983c | ||
|
089ecb3a4c | ||
|
7a46f12c02 | ||
|
96eed54a65 | ||
|
abe7217a58 | ||
|
54081c1b0f | ||
|
12f5824da9 | ||
|
e11302766b | ||
|
70f0c55d35 | ||
|
476125cc74 | ||
|
b8196d6284 | ||
|
a11c1558ed | ||
|
cec77a9bd9 | ||
|
0685e62a8f | ||
|
53463641b8 | ||
66ed1a0b72 | |||
f830ce7fad | |||
7fb48dfa1f | |||
|
fa7f6c6f51 | ||
|
753897222a | ||
|
4a28499aaa | ||
|
03c773fd79 | ||
fe55442dac | |||
|
34fc23d024 | ||
|
fcfb5ed3a8 | ||
|
f36ec1483a | ||
|
aa04fb345c | ||
|
3f79756f70 | ||
|
8220216893 | ||
0d302cf0c1 | |||
|
1e3a604d0f | ||
|
a65ef2ae1b | ||
|
94ba331ed0 | ||
|
59aa780654 | ||
|
0116d405b2 | ||
|
9f58414e99 | ||
|
a52dfc1483 | ||
|
65413e1c94 | ||
|
edeb4003fd | ||
|
4844fa1ead | ||
|
cd34dcb0dd | ||
1f211ca0ab | |||
20b9fce7df | |||
|
baa03424d4 | ||
|
347da823b7 | ||
|
fe4ba1db02 | ||
|
3c68d8e314 | ||
|
1b25ed2904 | ||
|
1ab6a7a9fd | ||
|
474ceb139a | ||
|
b322d3dfa5 | ||
|
7b662801f2 | ||
|
ce21063d7c | ||
|
7b4942fc3d | ||
|
b07202a64b | ||
|
0392c163a0 | ||
|
e5e83d1ad6 | ||
|
ef9550ced5 | ||
|
d7c603e881 | ||
|
3565c897c0 | ||
|
9c27057bf3 | ||
6f8b1e4896 | |||
|
5040deddb6 | ||
|
c9ce29741b | ||
|
cd78fea1f5 | ||
|
0e54d7f124 | ||
|
2511bb00a7 | ||
|
39d9557559 | ||
|
507d88e63e | ||
|
779a11c85a | ||
|
f29636b038 | ||
|
f860821498 | ||
|
e3981c8db2 | ||
|
1c814285c6 | ||
|
56949aad09 | ||
|
7e9aaeafe6 | ||
|
4e68c64daf | ||
|
37afe7b05e | ||
ec6060a6cb | |||
|
e16c28fd40 | ||
|
1aa6aef679 | ||
|
9fe3b02d71 | ||
|
02d812f103 | ||
|
ed07ba8613 | ||
|
ede3365f64 | ||
|
e92cb4ee64 | ||
|
dc7e3888d7 | ||
|
eb1bddf44e | ||
8865692f1d | |||
|
c43648faec | ||
|
74d7f3d66f | ||
|
fda0e9ab5e | ||
|
96adf83fad | ||
|
e4850666c5 | ||
|
462e9a4f5a | ||
|
414d8ff8f3 | ||
|
f620f453f2 | ||
|
bb23d0a9cd | ||
|
5efc8da300 | ||
|
3ddd8b4676 | ||
|
bdec11996d | ||
|
165987d60b | ||
|
3b9830336e | ||
|
de0a570a40 | ||
|
dd1db5dcab | ||
|
5f7bf6d3f2 | ||
|
854c3c6f71 | ||
|
3df7750b54 | ||
|
8ddafdf996 | ||
|
2b222d8e2a | ||
|
b1ee221555 | ||
|
83ac971792 | ||
|
8c853fb99e | ||
|
bfcd78c43e | ||
|
2e53cdcad7 | ||
|
fbee7c85bd | ||
|
a86a62ae1f | ||
|
2963751f6c | ||
|
1dfffdc31b | ||
|
015908a3b0 | ||
|
28b5b6fcaa | ||
|
be6b2c8a71 | ||
|
ac186190e5 | ||
|
0cd23eecfa | ||
|
278057dddf | ||
|
5a6a8a0931 | ||
|
2e9ff358cc | ||
|
fad48b9603 | ||
|
7a05563f0f | ||
|
67fe1d3e4d | ||
|
210b64a86d | ||
40aeed70af | |||
|
821c66c5a2 | ||
|
5d49cf463d | ||
|
79ca711d68 | ||
|
a9b60db54f | ||
|
505f06d973 | ||
|
6a28ae21d8 | ||
e2aef015fa | |||
|
3967c1ecbb | ||
|
1e6660ab9b | ||
|
37d6f1de2d | ||
|
a120963451 | ||
|
ccabfa335a | ||
|
75549a0b1c | ||
|
a3feece9d4 | ||
|
066df67ffa | ||
|
b79d3ce4e2 | ||
|
bcfc7d2f55 | ||
|
603cc10b42 | ||
|
ab3c832960 | ||
|
64b152e34b | ||
|
f3afbcadd0 | ||
|
de0f017540 | ||
|
57e2072bc0 | ||
|
c1876392bc | ||
|
52a6c4f4da | ||
|
83db30496d | ||
|
5ae8f662b9 | ||
|
fc90630781 | ||
|
3450d0ed4f | ||
|
b18e1ed519 | ||
|
58d64fc0c8 | ||
|
1cb8ea99a3 | ||
|
e5ed668a33 | ||
|
38a91a3dc3 | ||
|
5309060af8 | ||
|
1f11ceb91a | ||
|
609eba08a8 | ||
|
851589c695 | ||
|
2f3fb07b7c | ||
|
b4b119cf42 | ||
|
acae6a6369 | ||
|
8e4fc4b65e | ||
|
30dc027e3e | ||
|
888f00dd8b | ||
|
dbf5c989c2 | ||
|
3f5371a7c1 | ||
|
70caf89295 | ||
|
49864eb162 | ||
|
d7404679c7 | ||
|
ecf5c9db44 | ||
|
5dd5fb2ad1 | ||
|
e3449e7fd9 | ||
|
6db01d7b94 | ||
|
1baf10fa87 | ||
|
02c9bce645 | ||
|
4412d02b34 | ||
|
9b4ca94c13 | ||
|
029873fd5f | ||
|
616846c316 | ||
|
ebdc8a6261 | ||
|
90fce384d0 | ||
|
3b0f6ebab2 | ||
|
59788d9116 | ||
|
59aef5c170 | ||
|
9bb3854554 | ||
|
a3b364ae23 | ||
|
f0099ac5e8 | ||
|
577f8d7928 | ||
|
dc6903cfae | ||
|
57677278c4 | ||
|
5905b2d6cf | ||
|
3cd7a8945c | ||
|
98bb475afe | ||
|
7e7cfd7cd0 | ||
|
a8db74d40d | ||
|
3ab15e63e8 | ||
|
ee21921601 | ||
|
4eb7d5ab8b | ||
|
38994b941c | ||
|
9182427a0a | ||
|
d6c31f67ba | ||
|
a91e24eb01 | ||
|
c484ae73d6 | ||
|
d40a7b6b4f | ||
|
8a2c5f65bb | ||
|
67e1d6c7fc | ||
|
780f155377 | ||
|
d6dbe5c861 | ||
|
73361d5a52 | ||
|
e4cf40789c | ||
|
0757d6f293 | ||
|
7a63987cbe | ||
|
24b6498774 | ||
|
2762a43dbb | ||
|
b69f824b57 | ||
|
4acc730d38 | ||
|
b1847bf84c | ||
|
2960f960de | ||
|
dcb989fd92 | ||
|
bcef44e286 | ||
|
7d73601a58 | ||
|
7fe43a969f | ||
|
0dbf466817 | ||
|
6e83958653 | ||
|
b4bbc0630a | ||
|
1f72033a6a | ||
|
e1fa89cf60 | ||
|
62e6130223 | ||
|
dc80b1c28f | ||
|
1b7287a742 | ||
|
ab386e000d | ||
|
6416ef5fb7 | ||
|
e08908e22e | ||
|
7c8af9730f | ||
|
0a5305b947 | ||
|
eafc42531f | ||
|
d1a2dc2fa6 | ||
|
4fa76aa27f | ||
|
6757e1a1a8 | ||
|
c80e504283 | ||
|
d5400f370f | ||
|
60026b1c85 | ||
|
2b855156bf | ||
|
d8f6703c70 | ||
bff6dffe90 | |||
|
74a41c1bf1 | ||
|
d87d3f6cee | ||
|
72adb2172f | ||
|
c86190def1 | ||
|
0ab32ffffd | ||
|
375f1b9334 | ||
|
02459483e8 | ||
|
768aadc225 | ||
|
0a8773dc04 | ||
|
e57b8c3b15 | ||
|
78c23507c9 | ||
|
2407800aca | ||
|
061742ed07 | ||
|
ee3d0ba502 | ||
|
036c171eb3 | ||
|
9f662f21f1 | ||
|
2867ccb9b8 | ||
|
fea97972a2 | ||
|
55a2338a3e | ||
|
483713e635 | ||
|
cacaa97b6e | ||
|
b4c3cc77ac | ||
|
9ff142e6e2 | ||
|
df8570b16e | ||
|
7a2f31f8db | ||
|
924832dede | ||
|
82e5844dad | ||
|
0869b16ba6 | ||
|
31e625aa71 | ||
|
eafb566e91 | ||
|
7b8335a704 | ||
|
4c876cb809 | ||
|
ed4d99fa6a | ||
|
103f7fecfb | ||
|
aafc1fb13a | ||
|
5876ce9f22 | ||
|
9a91bf999d | ||
|
57f730e788 | ||
|
4cea07675c | ||
|
857da2a9f3 | ||
|
a0f92227f3 | ||
|
623af1c312 | ||
|
8c14c3cda3 | ||
|
462ccb6d15 | ||
|
f4609ffba6 | ||
|
8853aad620 | ||
|
49ae85af03 | ||
|
02132a2eec | ||
|
b0f23a406b | ||
|
bca3de9454 | ||
|
2f464f846a | ||
|
bee8562d45 | ||
|
689994552f | ||
|
151cfe9b2f | ||
|
8eda0532fa | ||
|
fccfe8b5b0 | ||
|
7db61c9114 | ||
|
7237b7819a | ||
|
9e28822e8f | ||
|
b0861ce0fb | ||
|
63e2b926e6 | ||
|
33be2ed716 |
7
.gitattributes
vendored
|
@ -1,9 +1,8 @@
|
|||
* 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
|
||||
|
||||
# Ensure all txt files within tests use LF
|
||||
tests/**/*.txt eol=lf
|
||||
|
|
|
@ -1,5 +0,0 @@
|
|||
root: ./docs
|
||||
|
||||
structure:
|
||||
readme: ./README.md
|
||||
summary: ./SUMMARY.md
|
93
.github/workflows/ci.yaml
vendored
|
@ -5,30 +5,97 @@ on:
|
|||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
name: CI
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
|
||||
fmt:
|
||||
name: Check formatting
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: rustfmt
|
||||
|
||||
- name: Install Tooling
|
||||
uses: CompeyDev/setup-rokit@v0.1.2
|
||||
|
||||
- name: Check Formatting
|
||||
run: just fmt-check
|
||||
|
||||
analyze:
|
||||
needs: ["fmt"]
|
||||
name: Analyze and lint Luau files
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Tooling
|
||||
uses: CompeyDev/setup-rokit@v0.1.2
|
||||
|
||||
- name: Analyze
|
||||
run: just analyze
|
||||
|
||||
ci:
|
||||
needs: ["fmt"]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- name: Windows x86_64
|
||||
runner-os: windows-latest
|
||||
cargo-target: x86_64-pc-windows-msvc
|
||||
|
||||
- name: Linux x86_64
|
||||
runner-os: ubuntu-latest
|
||||
cargo-target: x86_64-unknown-linux-gnu
|
||||
|
||||
- name: macOS x86_64
|
||||
runner-os: macos-13
|
||||
cargo-target: x86_64-apple-darwin
|
||||
|
||||
- name: macOS aarch64
|
||||
runner-os: macos-14
|
||||
cargo-target: aarch64-apple-darwin
|
||||
|
||||
name: CI - ${{ matrix.name }}
|
||||
runs-on: ${{ matrix.runner-os }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Rustfmt
|
||||
run: cargo fmt -- --check
|
||||
components: clippy
|
||||
targets: ${{ matrix.cargo-target }}
|
||||
|
||||
- name: Build
|
||||
run: cargo build --locked
|
||||
run: |
|
||||
cargo build \
|
||||
--workspace \
|
||||
--locked --all-features \
|
||||
--target ${{ matrix.cargo-target }}
|
||||
|
||||
- name: Clippy
|
||||
run: cargo clippy
|
||||
- name: Lint
|
||||
run: |
|
||||
cargo clippy \
|
||||
--workspace \
|
||||
--locked --all-features \
|
||||
--target ${{ matrix.cargo-target }}
|
||||
|
||||
- name: Test
|
||||
run: cargo test --package lune -- --test-threads 1
|
||||
run: |
|
||||
cargo test \
|
||||
--lib --workspace \
|
||||
--locked --all-features \
|
||||
--target ${{ matrix.cargo-target }}
|
||||
|
|
24
.github/workflows/publish.yaml
vendored
|
@ -1,24 +0,0 @@
|
|||
name: Publish
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
name: Publish
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
- name: Publish to crates.io
|
||||
uses: katyo/publish-crates@v2
|
||||
with:
|
||||
registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||
ignore-unpublished-changes: true
|
139
.github/workflows/release.yaml
vendored
|
@ -6,8 +6,9 @@ on:
|
|||
permissions:
|
||||
contents: write
|
||||
|
||||
env:
|
||||
CARGO_TARGET_DIR: output
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
init:
|
||||
|
@ -17,18 +18,37 @@ jobs:
|
|||
version: ${{ steps.get_version.outputs.value }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Get version from manifest
|
||||
uses: SebRollen/toml-action@9062fbef52816d61278d24ce53c8070440e1e8dd
|
||||
uses: SebRollen/toml-action@v1.2.0
|
||||
id: get_version
|
||||
with:
|
||||
file: Cargo.toml
|
||||
field: workspace.package.version
|
||||
file: crates/lune/Cargo.toml
|
||||
field: package.version
|
||||
|
||||
# dry-run:
|
||||
# name: Dry-run
|
||||
# needs: ["init"]
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - name: Checkout repository
|
||||
# uses: actions/checkout@v4
|
||||
|
||||
# - name: Install Rust
|
||||
# uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
# - name: Publish (dry-run)
|
||||
# uses: katyo/publish-crates@v2
|
||||
# with:
|
||||
# dry-run: true
|
||||
# check-repo: true
|
||||
# registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||
|
||||
build:
|
||||
needs: ["init"]
|
||||
needs: ["init"] # , "dry-run"]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- name: Windows x86_64
|
||||
|
@ -60,14 +80,17 @@ jobs:
|
|||
runs-on: ${{ matrix.runner-os }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: ${{ matrix.cargo-target }}
|
||||
|
||||
- name: Install tooling (aarch64-unknown-linux-gnu)
|
||||
- name: Install Just
|
||||
uses: extractions/setup-just@v2
|
||||
|
||||
- name: Install build tooling (aarch64-unknown-linux-gnu)
|
||||
if: matrix.cargo-target == 'aarch64-unknown-linux-gnu'
|
||||
run: |
|
||||
sudo apt-get update -y
|
||||
|
@ -75,79 +98,61 @@ jobs:
|
|||
sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
|
||||
|
||||
- name: Build binary
|
||||
shell: bash
|
||||
run: |
|
||||
cargo build \
|
||||
--locked --release --all-features \
|
||||
--target ${{ matrix.cargo-target }}
|
||||
run: just build --locked --release --target ${{ matrix.cargo-target }}
|
||||
|
||||
- name: Create binary archive
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p staging
|
||||
if [ "${{ matrix.runner-os }}" = "windows-latest" ]; then
|
||||
cp "output/${{ matrix.cargo-target }}/release/lune.exe" staging/
|
||||
cd staging
|
||||
7z a ../release.zip *
|
||||
else
|
||||
cp "output/${{ matrix.cargo-target }}/release/lune" staging/
|
||||
cd staging
|
||||
chmod +x lune
|
||||
zip ../release.zip *
|
||||
fi
|
||||
- name: Create release archive
|
||||
run: just zip-release ${{ matrix.cargo-target }}
|
||||
|
||||
- name: Upload binary artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
- name: Upload release artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.artifact-name }}
|
||||
path: release.zip
|
||||
|
||||
release:
|
||||
name: Release
|
||||
release-github:
|
||||
name: Release (GitHub)
|
||||
runs-on: ubuntu-latest
|
||||
needs: ["init", "build"]
|
||||
needs: ["init", "build"] # , "dry-run", "build"]
|
||||
steps:
|
||||
- name: Download binaries
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
path: ./binaries
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Discover binaries
|
||||
shell: bash
|
||||
run: |
|
||||
cd ./binaries
|
||||
echo ""
|
||||
echo "Binaries dir:"
|
||||
ls -lhrt
|
||||
echo ""
|
||||
echo "Searching for zipped releases..."
|
||||
for DIR in * ; do
|
||||
if [ -d "$DIR" ]; then
|
||||
cd "$DIR"
|
||||
for FILE in * ; do
|
||||
if [ ! -d "$FILE" ]; then
|
||||
if [ "$FILE" = "release.zip" ]; then
|
||||
echo "Found zipped release '$DIR'"
|
||||
mv "$FILE" "../$DIR.zip"
|
||||
rm -rf "../$DIR/"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
cd ..
|
||||
fi
|
||||
done
|
||||
echo ""
|
||||
echo "Binaries dir:"
|
||||
ls -lhrt
|
||||
cd ..
|
||||
- name: Install Just
|
||||
uses: extractions/setup-just@v2
|
||||
|
||||
- name: Download releases
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: ./releases
|
||||
|
||||
- name: Unpack releases
|
||||
run: just unpack-releases "./releases"
|
||||
|
||||
- name: Create release
|
||||
uses: softprops/action-gh-release@v1
|
||||
uses: softprops/action-gh-release@v2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
name: ${{ needs.init.outputs.version }}
|
||||
tag_name: v${{ needs.init.outputs.version }}
|
||||
fail_on_unmatched_files: true
|
||||
files: ./binaries/*.zip
|
||||
files: ./releases/*.zip
|
||||
draft: true
|
||||
|
||||
# release-crates:
|
||||
# name: Release (crates.io)
|
||||
# runs-on: ubuntu-latest
|
||||
# needs: ["init", "dry-run", "build"]
|
||||
# steps:
|
||||
# - name: Checkout repository
|
||||
# uses: actions/checkout@v4
|
||||
|
||||
# - name: Install Rust
|
||||
# uses: dtolnay/rust-toolchain@stable
|
||||
|
||||
# - name: Publish crates
|
||||
# uses: katyo/publish-crates@v2
|
||||
# with:
|
||||
# dry-run: false
|
||||
# check-repo: true
|
||||
# registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||
|
|
19
.gitignore
vendored
|
@ -3,12 +3,17 @@
|
|||
/.DS_Store
|
||||
/**/.DS_Store
|
||||
|
||||
|
||||
# Autogenerated dirs
|
||||
|
||||
/bin
|
||||
/out
|
||||
/target
|
||||
/gitbook
|
||||
/staging
|
||||
|
||||
/**/bin
|
||||
/**/out
|
||||
/**/target
|
||||
/**/staging
|
||||
|
||||
# Autogenerated files
|
||||
|
||||
|
@ -16,11 +21,7 @@ lune.yml
|
|||
luneDocs.json
|
||||
luneTypes.d.luau
|
||||
|
||||
# NOTE: Don't remove the typedefs file in the docs dir!
|
||||
# We depend on it to autogenerate all of the above files.
|
||||
!/docs/luneTypes.d.luau
|
||||
|
||||
# Files generated by runtime or build scripts
|
||||
packages/lib-roblox/scripts/brick_color.rs
|
||||
packages/lib-roblox/scripts/font_enum_map.rs
|
||||
packages/lib-roblox/scripts/physical_properties_enum_map.rs
|
||||
scripts/brick_color.rs
|
||||
scripts/font_enum_map.rs
|
||||
scripts/physical_properties_enum_map.rs
|
||||
|
|
139
.justfile
|
@ -1,30 +1,129 @@
|
|||
# Run an individual test using the Lune CLI
|
||||
run-test TEST_NAME:
|
||||
cargo run -- "tests/{{TEST_NAME}}"
|
||||
EXT := if os() == "windows" { ".exe" } else { "" }
|
||||
CWD := invocation_directory()
|
||||
BIN_NAME := "lune"
|
||||
|
||||
# Default hidden recipe for listing other recipes + cwd
|
||||
[no-cd]
|
||||
[no-exit-message]
|
||||
[private]
|
||||
default:
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
printf "Current directory:\n {{CWD}}\n"
|
||||
just --list
|
||||
|
||||
# Builds the Lune CLI binary
|
||||
[no-exit-message]
|
||||
build *ARGS:
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
cargo build --bin {{BIN_NAME}} {{ARGS}}
|
||||
|
||||
# Run an individual file using the Lune CLI
|
||||
run-file FILE_NAME:
|
||||
cargo run -- "{{FILE_NAME}}"
|
||||
[no-exit-message]
|
||||
run FILE_PATH:
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
cargo run --bin {{BIN_NAME}} -- run "{{FILE_PATH}}"
|
||||
|
||||
# Run tests for the Lune library
|
||||
test:
|
||||
cargo test --package lune -- --test-threads 1
|
||||
[no-exit-message]
|
||||
test *ARGS:
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
cargo test --lib -- {{ARGS}}
|
||||
|
||||
# Generate gitbook directory
|
||||
generate-gitbook:
|
||||
rm -rf ./gitbook
|
||||
# Run tests for the Lune binary
|
||||
[no-exit-message]
|
||||
test-bin *ARGS:
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
cargo test --bin {{BIN_NAME}} -- {{ARGS}}
|
||||
|
||||
mkdir gitbook
|
||||
mkdir gitbook/docs
|
||||
# Apply formatting for all Rust & Luau files
|
||||
[no-exit-message]
|
||||
fmt:
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
stylua .lune scripts tests types \
|
||||
--glob "tests/**/*.luau" \
|
||||
--glob "!tests/roblox/rbx-test-files/**"
|
||||
cargo fmt
|
||||
|
||||
cp -R docs gitbook
|
||||
cp README.md gitbook/docs/README.md
|
||||
cp .gitbook.yaml gitbook/.gitbook.yaml
|
||||
# Check formatting for all Rust & Luau files
|
||||
[no-exit-message]
|
||||
fmt-check:
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
stylua .lune scripts tests types \
|
||||
--glob "tests/**/*.luau" \
|
||||
--glob "!tests/roblox/rbx-test-files/**"
|
||||
cargo fmt --check
|
||||
|
||||
rm -rf gitbook/docs/typedefs
|
||||
# Analyze and lint Luau files using luau-lsp
|
||||
[no-exit-message]
|
||||
analyze:
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
luau-lsp analyze \
|
||||
--settings=".vscode/settings.json" \
|
||||
--ignore="tests/roblox/rbx-test-files/**" \
|
||||
.lune scripts tests types
|
||||
|
||||
cargo run -- --generate-gitbook-dir
|
||||
# Zips up the built binary into a single zip file
|
||||
[no-exit-message]
|
||||
zip-release TARGET_TRIPLE:
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
rm -rf staging
|
||||
rm -rf release.zip
|
||||
mkdir -p staging
|
||||
cp "target/{{TARGET_TRIPLE}}/release/{{BIN_NAME}}{{EXT}}" staging/
|
||||
cd staging
|
||||
if [ "{{os_family()}}" = "windows" ]; then
|
||||
7z a ../release.zip *
|
||||
else
|
||||
chmod +x {{BIN_NAME}}
|
||||
zip ../release.zip *
|
||||
fi
|
||||
cd "{{CWD}}"
|
||||
rm -rf staging
|
||||
|
||||
# Publish gitbook directory to gitbook branch
|
||||
publish-gitbook: generate-gitbook
|
||||
npx push-dir --dir=gitbook --branch=gitbook
|
||||
# Used in GitHub workflow to move per-matrix release zips
|
||||
[no-exit-message]
|
||||
[private]
|
||||
unpack-releases RELEASES_DIR:
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
#
|
||||
if [ ! -d "{{RELEASES_DIR}}" ]; then
|
||||
echo "Releases directory is missing"
|
||||
exit 1
|
||||
fi
|
||||
#
|
||||
cd "{{RELEASES_DIR}}"
|
||||
echo ""
|
||||
echo "Releases dir:"
|
||||
ls -lhrt
|
||||
echo ""
|
||||
echo "Searching for zipped releases..."
|
||||
#
|
||||
for DIR in * ; do
|
||||
if [ -d "$DIR" ]; then
|
||||
cd "$DIR"
|
||||
for FILE in * ; do
|
||||
if [ ! -d "$FILE" ]; then
|
||||
if [ "$FILE" = "release.zip" ]; then
|
||||
echo "Found zipped release '$DIR'"
|
||||
mv "$FILE" "../$DIR.zip"
|
||||
rm -rf "../$DIR/"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
cd ..
|
||||
fi
|
||||
done
|
||||
#
|
||||
echo ""
|
||||
echo "Releases dir:"
|
||||
ls -lhrt
|
||||
|
|
7
.luaurc
|
@ -7,5 +7,10 @@
|
|||
"typeErrors": true,
|
||||
"globals": [
|
||||
"warn"
|
||||
]
|
||||
],
|
||||
"aliases": {
|
||||
"lune": "./types/",
|
||||
"tests": "./tests",
|
||||
"require-tests": "./tests/require/tests"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -129,7 +129,7 @@ end
|
|||
]]
|
||||
|
||||
print("Sending 4 pings to google 🌏")
|
||||
local result = process.spawn("ping", {
|
||||
local result = process.exec("ping", {
|
||||
"google.com",
|
||||
"-c 4",
|
||||
})
|
||||
|
@ -171,7 +171,7 @@ local apiResult = net.request({
|
|||
method = "PATCH",
|
||||
headers = {
|
||||
["Content-Type"] = "application/json",
|
||||
},
|
||||
} :: { [string]: string },
|
||||
body = net.jsonEncode({
|
||||
title = "foo",
|
||||
body = "bar",
|
||||
|
|
|
@ -28,8 +28,8 @@ end)
|
|||
|
||||
for _ = 1, 5 do
|
||||
local start = os.clock()
|
||||
socket.send(tostring(1))
|
||||
local response = socket.next()
|
||||
socket:send(tostring(1))
|
||||
local response = socket:next()
|
||||
local elapsed = os.clock() - start
|
||||
print(`Got response '{response}' in {elapsed * 1_000} milliseconds`)
|
||||
task.wait(1 - elapsed)
|
||||
|
@ -38,7 +38,7 @@ end
|
|||
-- Everything went well, and we are done with the socket, so we can close it
|
||||
|
||||
print("Closing web socket...")
|
||||
socket.close()
|
||||
socket:close()
|
||||
|
||||
task.cancel(forceExit)
|
||||
print("Done! 🌙")
|
||||
|
|
|
@ -15,9 +15,9 @@ local handle = net.serve(PORT, {
|
|||
handleWebSocket = function(socket)
|
||||
print("Got new web socket connection!")
|
||||
repeat
|
||||
local message = socket.next()
|
||||
local message = socket:next()
|
||||
if message ~= nil then
|
||||
socket.send("Echo - " .. message)
|
||||
socket:send("Echo - " .. message)
|
||||
end
|
||||
until message == nil
|
||||
print("Web socket disconnected.")
|
||||
|
|
10
.vscode/settings.json
vendored
|
@ -1,21 +1,17 @@
|
|||
{
|
||||
// Luau - disable Roblox features, enable Lune typedefs & requires
|
||||
"luau-lsp.sourcemap.enabled": false,
|
||||
"luau-lsp.types.roblox": false,
|
||||
"luau-lsp.require.mode": "relativeToFile",
|
||||
"luau-lsp.require.directoryAliases": {
|
||||
"@lune/": "./docs/typedefs/"
|
||||
"@lune/": "./types/",
|
||||
"@tests/": "./tests/",
|
||||
"@require-tests/": "./tests/require/tests/"
|
||||
},
|
||||
// Luau - ignore type defs file in docs dir and dev scripts we use
|
||||
"luau-lsp.ignoreGlobs": [
|
||||
"docs/*.d.luau",
|
||||
"packages/lib-roblox/scripts/*.luau",
|
||||
"tests/roblox/rbx-test-files/**/*.lua",
|
||||
"tests/roblox/rbx-test-files/**/*.luau"
|
||||
],
|
||||
// Rust
|
||||
"rust-analyzer.check.command": "clippy",
|
||||
// Formatting
|
||||
"editor.formatOnSave": true,
|
||||
"stylua.searchParentDirectories": true,
|
||||
"prettier.tabWidth": 2,
|
||||
|
|
675
CHANGELOG.md
|
@ -8,6 +8,669 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## `0.9.0`
|
||||
|
||||
### Breaking changes
|
||||
|
||||
- Added two new process spawning functions - `process.create` and `process.exec`, removing the previous `process.spawn` API completely. ([#211])
|
||||
|
||||
To migrate from `process.spawn`, use the new `process.exec` API which retains the same behavior as the old function.
|
||||
|
||||
The new `process.create` function is a non-blocking process creation API and can be used to interactively
|
||||
read and write stdio of the process.
|
||||
|
||||
```lua
|
||||
local child = process.create("program", {
|
||||
"cli-argument",
|
||||
"other-cli-argument"
|
||||
})
|
||||
|
||||
-- Writing to stdin
|
||||
child.stdin:write("Hello from Lune!")
|
||||
|
||||
-- Reading from stdout
|
||||
local data = child.stdout:read()
|
||||
print(buffer.tostring(data))
|
||||
```
|
||||
|
||||
- WebSocket methods in `net.socket` and `net.serve` now use standard Lua method calling convention and colon syntax.
|
||||
This means `socket.send(...)` is now `socket:send(...)`, `socket.close(...)` is now `socket:close(...)`, and so on.
|
||||
|
||||
- `Runtime::run` now returns a more useful value instead of an `ExitCode` ([#178])
|
||||
|
||||
### Changed
|
||||
|
||||
- Documentation comments for several standard library properties have been improved ([#248], [#250])
|
||||
- Error messages no longer contain redundant or duplicate stack trace information
|
||||
|
||||
[#178]: https://github.com/lune-org/lune/pull/178
|
||||
[#211]: https://github.com/lune-org/lune/pull/211
|
||||
[#248]: https://github.com/lune-org/lune/pull/248
|
||||
[#250]: https://github.com/lune-org/lune/pull/250
|
||||
|
||||
## `0.8.9` - October 7th, 2024
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated to Luau version `0.640`
|
||||
|
||||
## `0.8.8` - August 22nd, 2024
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed errors when deserializing `Lighting.AttributesSerialize` by updating `rbx-dom` dependencies ([#245])
|
||||
|
||||
[#245]: https://github.com/lune-org/lune/pull/245
|
||||
|
||||
## `0.8.7` - August 10th, 2024
|
||||
|
||||
### Added
|
||||
|
||||
- Added a compression level option to `serde.compress` ([#224])
|
||||
- Added missing vector methods to the `roblox` library ([#228])
|
||||
|
||||
### Changed
|
||||
|
||||
- Updated to Luau version `0.635`
|
||||
- Updated to rbx-dom database version `0.634`
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `fs.readDir` with trailing forward-slash on Windows ([#220])
|
||||
- Fixed `__type` and `__tostring` metamethods not always being respected when formatting tables
|
||||
|
||||
[#220]: https://github.com/lune-org/lune/pull/220
|
||||
[#224]: https://github.com/lune-org/lune/pull/224
|
||||
[#228]: https://github.com/lune-org/lune/pull/228
|
||||
|
||||
## `0.8.6` - June 23rd, 2024
|
||||
|
||||
### Added
|
||||
|
||||
- Added a builtin API for hashing and calculating HMACs as part of the `serde` library ([#193])
|
||||
|
||||
Basic usage:
|
||||
|
||||
```lua
|
||||
local serde = require("@lune/serde")
|
||||
local hash = serde.hash("sha256", "a message to hash")
|
||||
local hmac = serde.hmac("sha256", "a message to hash", "a secret string")
|
||||
|
||||
print(hash)
|
||||
print(hmac)
|
||||
```
|
||||
|
||||
The returned hashes are sequences of lowercase hexadecimal digits. The following algorithms are supported:
|
||||
`md5`, `sha1`, `sha224`, `sha256`, `sha384`, `sha512`, `sha3-224`, `sha3-256`, `sha3-384`, `sha3-512`, `blake3`
|
||||
|
||||
- Added two new options to `luau.load`:
|
||||
|
||||
- `codegenEnabled` - whether or not codegen should be enabled for the loaded chunk.
|
||||
- `injectGlobals` - whether or not to inject globals into a passed `environment`.
|
||||
|
||||
By default, globals are injected and codegen is disabled.
|
||||
Check the documentation for the `luau` standard library for more information.
|
||||
|
||||
- Implemented support for floor division operator / `__idiv` for the `Vector2` and `Vector3` types in the `roblox` standard library ([#196])
|
||||
- Fixed the `_VERSION` global containing an incorrect Lune version string.
|
||||
|
||||
### Changed
|
||||
|
||||
- Sandboxing and codegen in the Luau VM is now fully enabled, resulting in up to 2x or faster code execution.
|
||||
This should not result in any behavior differences in Lune, but if it does, please open an issue.
|
||||
- Improved formatting of custom error objects (such as when `fs.readFile` returns an error) when printed or formatted using `stdio.format`.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `__type` and `__tostring` metamethods on userdatas and tables not being respected when printed or formatted using `stdio.format`.
|
||||
|
||||
[#193]: https://github.com/lune-org/lune/pull/193
|
||||
[#196]: https://github.com/lune-org/lune/pull/196
|
||||
|
||||
## `0.8.5` - June 1st, 2024
|
||||
|
||||
### Changed
|
||||
|
||||
- Improved table pretty formatting when using `print`, `warn`, and `stdio.format`:
|
||||
|
||||
- Keys are sorted numerically / alphabetically when possible.
|
||||
- Keys of different types are put in distinct sections for mixed tables.
|
||||
- Tables that are arrays no longer display their keys.
|
||||
- Empty tables are no longer spread across lines.
|
||||
|
||||
## Fixed
|
||||
|
||||
- Fixed formatted values in tables not being separated by newlines.
|
||||
- Fixed panicking (crashing) when using `process.spawn` with a program that does not exist.
|
||||
- Fixed `instance:SetAttribute("name", nil)` throwing an error and not removing the attribute.
|
||||
|
||||
## `0.8.4` - May 12th, 2024
|
||||
|
||||
### Added
|
||||
|
||||
- Added a builtin API for regular expressions.
|
||||
|
||||
Example basic usage:
|
||||
|
||||
```lua
|
||||
local Regex = require("@lune/regex")
|
||||
|
||||
local re = Regex.new("hello")
|
||||
|
||||
if re:isMatch("hello, world!") then
|
||||
print("Matched!")
|
||||
end
|
||||
|
||||
local caps = re:captures("hello, world! hello, again!")
|
||||
|
||||
print(#caps) -- 2
|
||||
print(caps:get(1)) -- "hello"
|
||||
print(caps:get(2)) -- "hello"
|
||||
print(caps:get(3)) -- nil
|
||||
```
|
||||
|
||||
Check out the documentation for more details.
|
||||
|
||||
- Added support for buffers as arguments in builtin APIs ([#148])
|
||||
|
||||
This includes APIs such as `fs.writeFile`, `serde.encode`, and more.
|
||||
|
||||
- Added support for cross-compilation of standalone binaries ([#162])
|
||||
|
||||
You can now compile standalone binaries for other platforms by passing
|
||||
an additional `target` argument to the `build` subcommand:
|
||||
|
||||
```sh
|
||||
lune build my-file.luau --output my-bin --target windows-x86_64
|
||||
```
|
||||
|
||||
Currently supported targets are the same as the ones included with each
|
||||
release of Lune on GitHub. Check releases for a full list of targets.
|
||||
|
||||
- Added `stdio.readToEnd()` for reading the entire stdin passed to Lune
|
||||
|
||||
### Changed
|
||||
|
||||
- Split the repository into modular crates instead of a monolith. ([#188])
|
||||
|
||||
If you previously depended on Lune as a crate, nothing about it has changed for version `0.8.4`, but now each individual sub-crate has also been published and is available for use:
|
||||
|
||||
- `lune` (old)
|
||||
- `lune-utils`
|
||||
- `lune-roblox`
|
||||
- `lune-std-*` for every builtin library
|
||||
|
||||
When depending on the main `lune` crate, each builtin library also has a feature flag that can be toggled in the format `std-*`.
|
||||
|
||||
In general, this should mean that it is now much easier to make your own Lune builtin, publish your own flavor of a Lune CLI, or take advantage of all the work that has been done for Lune as a runtime when making your own Rust programs.
|
||||
|
||||
- Changed the `User-Agent` header in `net.request` to be more descriptive ([#186])
|
||||
- Updated to Luau version `0.622`.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed not being able to decompress `lz4` format in high compression mode
|
||||
- Fixed stack overflow for tables with circular keys ([#183])
|
||||
- Fixed `net.serve` no longer accepting ipv6 addresses
|
||||
- Fixed headers in `net.serve` being raw bytes instead of strings
|
||||
|
||||
[#148]: https://github.com/lune-org/lune/pull/148
|
||||
[#162]: https://github.com/lune-org/lune/pull/162
|
||||
[#183]: https://github.com/lune-org/lune/pull/183
|
||||
[#186]: https://github.com/lune-org/lune/pull/186
|
||||
[#188]: https://github.com/lune-org/lune/pull/188
|
||||
|
||||
## `0.8.3` - April 15th, 2024
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `require` not throwing syntax errors ([#168])
|
||||
- Fixed `require` caching not working correctly ([#171])
|
||||
- Fixed case-sensitivity issue in `require` with aliases ([#173])
|
||||
- Fixed `itertools` dependency being marked optional even though it is mandatory ([#176])
|
||||
- Fixed test cases for the `net` built-in library on Windows ([#177])
|
||||
|
||||
[#168]: https://github.com/lune-org/lune/pull/168
|
||||
[#171]: https://github.com/lune-org/lune/pull/171
|
||||
[#173]: https://github.com/lune-org/lune/pull/173
|
||||
[#176]: https://github.com/lune-org/lune/pull/176
|
||||
[#177]: https://github.com/lune-org/lune/pull/177
|
||||
|
||||
## `0.8.2` - March 12th, 2024
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed REPL panicking after the first evaluation / run.
|
||||
- Fixed globals reloading on each run in the REPL, causing unnecessary slowdowns.
|
||||
- Fixed `net.serve` requests no longer being plain tables in Lune `0.8.1`, breaking usage of things such as `table.clone`.
|
||||
|
||||
## `0.8.1` - March 11th, 2024
|
||||
|
||||
### Added
|
||||
|
||||
- Added the ability to specify an address in `net.serve`. ([#142])
|
||||
|
||||
### Changed
|
||||
|
||||
- Update to Luau version `0.616`.
|
||||
- Major performance improvements when using a large amount of threads / asynchronous Lune APIs. ([#165])
|
||||
- Minor performance improvements and less overhead for `net.serve` and `net.socket`. ([#165])
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `fs.copy` not working with empty dirs. ([#155])
|
||||
- Fixed stack overflow when printing tables with cyclic references. ([#158])
|
||||
- Fixed not being able to yield in `net.serve` handlers without blocking other requests. ([#165])
|
||||
- Fixed various scheduler issues / panics. ([#165])
|
||||
|
||||
[#142]: https://github.com/lune-org/lune/pull/142
|
||||
[#155]: https://github.com/lune-org/lune/pull/155
|
||||
[#158]: https://github.com/lune-org/lune/pull/158
|
||||
[#165]: https://github.com/lune-org/lune/pull/165
|
||||
|
||||
## `0.8.0` - January 14th, 2024
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- The Lune CLI now uses subcommands instead of flag options: <br/>
|
||||
|
||||
- `lune script_name arg1 arg2 arg3` -> `lune run script_name arg1 arg2 arg3`
|
||||
- `lune --list` -> `lune list`
|
||||
- `lune --setup` -> `lune setup`
|
||||
|
||||
This unfortunately hurts ergonomics for quickly running scripts but is a necessary change to allow us to add more commands, such as the new `build` subcommand.
|
||||
|
||||
- The `createdAt`, `modifiedAt`, and `accessedAt` properties returned from `fs.metadata` are now `DateTime` values instead of numbers.
|
||||
|
||||
- The `Lune` struct has been renamed to `Runtime` in the Lune rust crate.
|
||||
|
||||
### Added
|
||||
|
||||
- Added support for compiling single Lune scripts into standalone executables! ([#140])
|
||||
|
||||
Example usage:
|
||||
|
||||
```lua
|
||||
-- my_cool_script.luau
|
||||
print("Hello, standalone!")
|
||||
```
|
||||
|
||||
```sh
|
||||
> lune build my_cool_script.luau
|
||||
# Creates `my_cool_script.exe` (Windows) or `my_cool_script` (macOS / Linux)
|
||||
```
|
||||
|
||||
```sh
|
||||
> ./my_cool_script.exe # Windows
|
||||
> ./my_cool_script # macOS / Linux
|
||||
> "Hello, standalone!"
|
||||
```
|
||||
|
||||
To compile scripts that use `require` and reference multiple files, a bundler such as [darklua](https://github.com/seaofvoices/darklua) should preferrably be used. You may also distribute files alongside the standalone binary, they will still be able to be `require`-d. This limitation will be lifted in the future and Lune will automatically bundle any referenced scripts.
|
||||
|
||||
- Added support for path aliases using `.luaurc` config files!
|
||||
|
||||
For full documentation and reference, check out the [official Luau RFC](https://rfcs.luau-lang.org/require-by-string-aliases.html), but here's a quick example:
|
||||
|
||||
```jsonc
|
||||
// .luaurc
|
||||
{
|
||||
"aliases": {
|
||||
"modules": "./some/long/path/to/modules"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```lua
|
||||
-- ./some/long/path/to/modules/foo.luau
|
||||
return { World = "World!" }
|
||||
|
||||
-- ./anywhere/you/want/my_script.luau
|
||||
local mod = require("@modules/foo")
|
||||
print("Hello, " .. mod.World)
|
||||
```
|
||||
|
||||
- Added support for multiple values for a single query, and multiple values for a single header, in `net.request`. This is a part of the HTTP specification that is not widely used but that may be useful in certain cases. To clarify:
|
||||
|
||||
- Single values remain unchanged and will work exactly the same as before. <br/>
|
||||
|
||||
```lua
|
||||
-- https://example.com/?foo=bar&baz=qux
|
||||
local net = require("@lune/net")
|
||||
net.request({
|
||||
url = "example.com",
|
||||
query = {
|
||||
foo = "bar",
|
||||
baz = "qux",
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
- Multiple values _on a single query / header_ are represented as an ordered array of strings. <br/>
|
||||
|
||||
```lua
|
||||
-- https://example.com/?foo=first&foo=second&foo=third&bar=baz
|
||||
local net = require("@lune/net")
|
||||
net.request({
|
||||
url = "example.com",
|
||||
query = {
|
||||
foo = { "first", "second", "third" },
|
||||
bar = "baz",
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
[#140]: https://github.com/lune-org/lune/pull/140
|
||||
|
||||
### Changed
|
||||
|
||||
- Update to Luau version `0.606`.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed the `print` and `warn` global functions yielding the thread, preventing them from being used in places such as the callback to `table.sort`.
|
||||
- Fixed the `overwrite` option for `fs.move` not correctly removing existing files / directories. ([#133])
|
||||
|
||||
[#133]: https://github.com/lune-org/lune/pull/133
|
||||
|
||||
## `0.7.11` - October 29th, 2023
|
||||
|
||||
### Changed
|
||||
|
||||
- Update to Luau version `0.601`.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `roblox.getAuthCookie` not being compatible with the latest cookie format by upgrading rbx_cookie.
|
||||
|
||||
## `0.7.10` - October 25th, 2023
|
||||
|
||||
### Added
|
||||
|
||||
- Added the `GetDebugId` instance method to the `roblox` built-in. This will return the internal id used by the instance, and as the name implies, it should be primarily used for _debugging_ purposes and cases where you need a globally unique identifier for an instance. It is guaranteed to be a 32-digit hexadecimal string.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed issues with `SecurityCapabilities` on instances in the `roblox` built-in by upgrading rbx-dom.
|
||||
|
||||
## `0.7.9` - October 21st, 2023
|
||||
|
||||
### Added
|
||||
|
||||
- Added `implementProperty` and `implementMethod` to the `roblox` built-in library to fill in missing functionality that Lune does not aim to implement itself.
|
||||
|
||||
Example usage:
|
||||
|
||||
```lua
|
||||
local roblox = require("@lune/roblox")
|
||||
|
||||
local part = roblox.Instance.new("Part")
|
||||
|
||||
roblox.implementMethod("BasePart", "TestMethod", function(_, ...)
|
||||
print("Tried to call TestMethod with", ...)
|
||||
end)
|
||||
|
||||
part:TestMethod("Hello", "world!")
|
||||
```
|
||||
|
||||
### Changed
|
||||
|
||||
- Update to Luau version `0.599`.
|
||||
- Stdio options when using `process.spawn` can now be set with more granularity, allowing stderr & stdout to be disabled individually and completely to improve memory usage when they are not being used.
|
||||
|
||||
## `0.7.8` - October 5th, 2023
|
||||
|
||||
### Added
|
||||
|
||||
- Added a new `datetime` built-in library for handling date & time values, parsing, formatting, and more. ([#94])
|
||||
|
||||
Example usage:
|
||||
|
||||
```lua
|
||||
local DateTime = require("@lune/datetime")
|
||||
|
||||
-- Creates a DateTime for the current exact moment in time
|
||||
local now = DateTime.now()
|
||||
|
||||
-- Formats the current moment in time as an ISO 8601 string
|
||||
print(now:toIsoDate())
|
||||
|
||||
-- Formats the current moment in time, using the local
|
||||
-- time, the French locale, and the specified time string
|
||||
print(now:formatLocalTime("%A, %d %B %Y", "fr"))
|
||||
|
||||
-- Returns a specific moment in time as a DateTime instance
|
||||
local someDayInTheFuture = DateTime.fromLocalTime({
|
||||
year = 3033,
|
||||
month = 8,
|
||||
day = 26,
|
||||
hour = 16,
|
||||
minute = 56,
|
||||
second = 28,
|
||||
millisecond = 892,
|
||||
})
|
||||
|
||||
-- Extracts the current local date & time as separate values (same values as above table)
|
||||
print(now:toLocalTime())
|
||||
|
||||
-- Returns a DateTime instance from a given float, where the whole
|
||||
-- denotes the seconds and the fraction denotes the milliseconds
|
||||
-- Note that the fraction for millis here is completely optional
|
||||
DateTime.fromUnixTimestamp(871978212313.321)
|
||||
|
||||
-- Extracts the current universal (UTC) date & time as separate values
|
||||
print(now:toUniversalTime())
|
||||
```
|
||||
|
||||
- Added support for passing `stdin` in `process.spawn` ([#106])
|
||||
- Added support for setting a custom environment in load options for `luau.load`, not subject to `getfenv` / `setfenv` deoptimizations
|
||||
- Added [Terrain:GetMaterialColor](https://create.roblox.com/docs/reference/engine/classes/Terrain#GetMaterialColor) and [Terrain:SetMaterialColor](https://create.roblox.com/docs/reference/engine/classes/Terrain#SetMaterialColor) ([#93])
|
||||
- Added support for a variable number of arguments for CFrame methods ([#85])
|
||||
|
||||
### Changed
|
||||
|
||||
- Update to Luau version `0.596`.
|
||||
- Update to rbx-dom database version `0.596`.
|
||||
- `process.spawn` now uses `powershell` instead of `/bin/bash` as the shell on Windows, with `shell = true`.
|
||||
- CFrame and Vector3 values are now rounded to the nearest 2 ^ 16 decimal place to reduce floating point errors and diff noise. Note that this does not affect intermediate calculations done in lua, and only happens when a property value is set on an Instance.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed the `process` built-in library not loading correctly when using Lune in REPL mode.
|
||||
- Fixed list subcommand not listing global scripts without a local `.lune` / `lune` directory present.
|
||||
- Fixed `net.serve` stopping when the returned `ServeHandle` is garbage collected.
|
||||
- Fixed missing trailing newline when using the `warn` global.
|
||||
- Fixed constructor for `CFrame` in the `roblox` built-in library not parsing the 12-arg overload correctly. ([#102])
|
||||
- Fixed various functions for `CFrame` in the `roblox` built-in library being incorrect, specifically row-column ordering and some flipped signs. ([#103])
|
||||
- Fixed cross-service Instance references disappearing when using the `roblox` built-in library ([#117])
|
||||
|
||||
[#85]: https://github.com/lune-org/lune/pull/85
|
||||
[#93]: https://github.com/lune-org/lune/pull/93
|
||||
[#94]: https://github.com/lune-org/lune/pull/94
|
||||
[#102]: https://github.com/lune-org/lune/pull/102
|
||||
[#103]: https://github.com/lune-org/lune/pull/103
|
||||
[#106]: https://github.com/lune-org/lune/pull/106
|
||||
[#117]: https://github.com/lune-org/lune/pull/117
|
||||
|
||||
## `0.7.7` - August 23rd, 2023
|
||||
|
||||
### Added
|
||||
|
||||
- Added a [REPL](https://en.wikipedia.org/wiki/Read–eval–print_loop) to Lune. ([#83])
|
||||
|
||||
This allows you to run scripts within Lune without writing files!
|
||||
|
||||
Example usage, inside your favorite terminal:
|
||||
|
||||
```bash
|
||||
# 1. Run the Lune executable, without any arguments
|
||||
lune
|
||||
|
||||
# 2. You will be shown the current Lune version and a blank prompt arrow:
|
||||
Lune v0.7.7
|
||||
>
|
||||
|
||||
# 3. Start typing, and hit enter when you want to run your script!
|
||||
# Your script will run until completion and output things along the way.
|
||||
> print(2 + 3)
|
||||
5
|
||||
> print("Hello, lune changelog!")
|
||||
Hello, lune changelog!
|
||||
|
||||
# 4. You can also set variables that will get preserved between runs.
|
||||
# Note that local variables do not get preserved here.
|
||||
> myVariable = 123
|
||||
> print(myVariable)
|
||||
123
|
||||
|
||||
# 5. Press either of these key combinations to exit the REPL:
|
||||
# - Ctrl + D
|
||||
# - Ctrl + C
|
||||
```
|
||||
|
||||
- Added a new `luau` built-in library for manually compiling and loading Luau source code. ([#82])
|
||||
|
||||
Example usage:
|
||||
|
||||
```lua
|
||||
local luau = require("@lune/luau")
|
||||
|
||||
local bytecode = luau.compile("print('Hello, World!')")
|
||||
local callableFn = luau.load(bytecode)
|
||||
|
||||
callableFn()
|
||||
|
||||
-- Additionally, we can skip the bytecode generation and
|
||||
-- load a callable function directly from the code itself.
|
||||
local callableFn2 = luau.load("print('Hello, World!')")
|
||||
|
||||
callableFn2()
|
||||
```
|
||||
|
||||
### Changed
|
||||
|
||||
- Update to Luau version `0.591`.
|
||||
- Lune's internal task scheduler and `require` functionality has been completely rewritten. <br/>
|
||||
The new scheduler is much more stable, conforms to a larger test suite, and has a few additional benefits:
|
||||
|
||||
- Built-in libraries are now lazily loaded, meaning nothing gets allocated until the built-in library gets loaded using `require("@lune/builtin-name")`. This also improves startup times slightly.
|
||||
- Spawned processes using `process.spawn` now run on different thread(s), freeing up resources for the main thread where luau runs.
|
||||
- Serving requests using `net.serve` now processes requests on background threads, also freeing up resources. In the future, this will also allow us to offload heavy tasks such as compression/decompression to background threads.
|
||||
- Groundwork for custom / user-defined require aliases has been implemented, as well as absolute / cwd-relative requires. These will both be exposed as options and be made available to use some time in the future.
|
||||
|
||||
- When using the `serde` built-in library, keys are now sorted during serialization. This means that the output of `encode` is now completely deterministic, and wont cause issues when committing generated files to git etc.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed not being able to pass arguments to the thread using `coroutine.resume`. ([#86])
|
||||
- Fixed a large number of long-standing issues, from the task scheduler rewrite:
|
||||
|
||||
- Fixed `require` hanging indefinitely when the module being require-d uses an async function in its main body.
|
||||
- Fixed background tasks (such as `net.serve`) not keeping Lune alive even if there are no lua threads to run.
|
||||
- Fixed spurious panics and error messages such as `Tried to resume next queued future but none are queued`.
|
||||
- Fixed not being able to catch non-string errors properly, errors were accidentally being wrapped in an opaque `userdata` type.
|
||||
|
||||
[#82]: https://github.com/lune-org/lune/pull/82
|
||||
[#83]: https://github.com/lune-org/lune/pull/83
|
||||
[#86]: https://github.com/lune-org/lune/pull/86
|
||||
|
||||
## `0.7.6` - August 9th, 2023
|
||||
|
||||
### Changed
|
||||
|
||||
- Update to Luau version `0.588`
|
||||
- Enabled Luau JIT backend for potential performance improvements 🚀 <br/>
|
||||
If you run into any strange behavior please open an issue!
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed publishing of the Lune library to `crates.io`
|
||||
- Fixed `serde.decode` deserializing `null` values as `userdata` instead of `nil`.
|
||||
- Fixed not being able to require files with multiple extensions, eg. `module.spec.luau` was not require-able using `require("module.spec")`.
|
||||
- Fixed instances and `roblox` built-in library APIs erroring when used asynchronously/concurrently.
|
||||
|
||||
## `0.7.5` - July 22nd, 2023
|
||||
|
||||
### Added
|
||||
|
||||
- Lune now has a new documentation site! </br>
|
||||
This addresses new APIs from version `0.7.0` not being available on the docs site, brings much improved searching functionality, and will help us keep documentation more up-to-date going forward with a more automated process. You can check out the new site at [lune-org.github.io](https://lune-org.github.io/docs).
|
||||
|
||||
- Added `fs.copy` to recursively copy files and directories.
|
||||
|
||||
Example usage:
|
||||
|
||||
```lua
|
||||
local fs = require("@lune/fs")
|
||||
|
||||
fs.writeDir("myCoolDir")
|
||||
fs.writeFile("myCoolDir/myAwesomeFile.json", "{}")
|
||||
|
||||
fs.copy("myCoolDir", "myCoolDir2")
|
||||
|
||||
assert(fs.isDir("myCoolDir2"))
|
||||
assert(fs.isFile("myCoolDir2/myAwesomeFile.json"))
|
||||
assert(fs.readFile("myCoolDir2/myAwesomeFile.json") == "{}")
|
||||
```
|
||||
|
||||
- Added `fs.metadata` to get metadata about files and directories.
|
||||
|
||||
Example usage:
|
||||
|
||||
```lua
|
||||
local fs = require("@lune/fs")
|
||||
|
||||
fs.writeFile("myAwesomeFile.json", "{}")
|
||||
|
||||
local meta = fs.metadata("myAwesomeFile.json")
|
||||
|
||||
print(meta.exists) --> true
|
||||
print(meta.kind) --> "file"
|
||||
print(meta.createdAt) --> 1689848548.0577152 (unix timestamp)
|
||||
print(meta.permissions) --> { readOnly: false }
|
||||
```
|
||||
|
||||
- Added `roblox.getReflectionDatabase` to access the builtin database containing information about classes and enums.
|
||||
|
||||
Example usage:
|
||||
|
||||
```lua
|
||||
local roblox = require("@lune/roblox")
|
||||
|
||||
local db = roblox.getReflectionDatabase()
|
||||
|
||||
print("There are", #db:GetClassNames(), "classes in the reflection database")
|
||||
|
||||
print("All base instance properties:")
|
||||
|
||||
local class = db:GetClass("Instance")
|
||||
for name, prop in class.Properties do
|
||||
print(string.format(
|
||||
"- %s with datatype %s and default value %s",
|
||||
prop.Name,
|
||||
prop.Datatype,
|
||||
tostring(class.DefaultProperties[prop.Name])
|
||||
))
|
||||
end
|
||||
```
|
||||
|
||||
- Added support for running directories with an `init.luau` or `init.lua` file in them in the CLI.
|
||||
|
||||
### Changed
|
||||
|
||||
- Update to Luau version `0.583`
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed publishing of Lune to crates.io by migrating away from a monorepo.
|
||||
- Fixed crashes when writing a very deeply nested `Instance` to a file. ([#62])
|
||||
- Fixed not being able to read & write to WebSocket objects at the same time. ([#68])
|
||||
- Fixed tab character at the start of a script causing it not to parse correctly. ([#72])
|
||||
|
||||
[#62]: https://github.com/lune-org/lune/pull/62
|
||||
[#68]: https://github.com/lune-org/lune/pull/66
|
||||
[#72]: https://github.com/lune-org/lune/pull/72
|
||||
|
||||
## `0.7.4` - July 7th, 2023
|
||||
|
||||
### Added
|
||||
|
@ -24,7 +687,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
- When using `roblox.serializeModel`, Lune will no longer keep internal unique ids. <br/>
|
||||
This is consistent with what Roblox does and prevents Lune from always generating a new and unique file. <br/>
|
||||
This previously caused unnecessary diffs when using git or other kinds of source control. ([Relevant issue](https://github.com/filiptibell/lune/issues/61))
|
||||
This previously caused unnecessary diffs when using git or other kinds of source control. ([Relevant issue](https://github.com/lune-org/lune/issues/61))
|
||||
|
||||
## `0.7.2` - June 28th, 2023
|
||||
|
||||
|
@ -35,7 +698,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
### Changed
|
||||
|
||||
- The `lune --setup` command is now much more user-friendly
|
||||
- The `lune --setup` command is now much more user-friendly.
|
||||
- Update to Luau version `0.581`
|
||||
|
||||
## `0.7.1` - June 17th, 2023
|
||||
|
@ -49,7 +712,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Fixed `closeCode` erroring when being accessed on websockets. ([#57])
|
||||
- Fixed issues with `UniqueId` when using the `roblox` builtin by downgrading `rbx-dom`.
|
||||
|
||||
[#57]: https://github.com/filiptibell/lune/pull/57
|
||||
[#57]: https://github.com/lune-org/lune/pull/57
|
||||
|
||||
## `0.7.0` - June 12th, 2023
|
||||
|
||||
|
@ -118,7 +781,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Fixed `stdio.write` and `stdio.ewrite` not being flushed and causing output to be interleaved. ([#47])
|
||||
- Fixed `typeof` returning `userdata` for roblox types such as `Instance`, `Vector3`, ...
|
||||
|
||||
[#47]: https://github.com/filiptibell/lune/pull/47
|
||||
[#47]: https://github.com/lune-org/lune/pull/47
|
||||
|
||||
## `0.6.7` - May 14th, 2023
|
||||
|
||||
|
@ -188,7 +851,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
### Added
|
||||
|
||||
- Added support for instance tags & `CollectionService` in the `roblox` built-in. <br />
|
||||
Currently implemented methods are listed on the [docs site](https://lune.gitbook.io/lune/roblox/api-status).
|
||||
Currently implemented methods are listed on the [docs site](https://lune-org.github.io/docs/roblox/4-api-status).
|
||||
|
||||
### Fixed
|
||||
|
||||
|
@ -226,7 +889,7 @@ This release adds some new features and fixes for the `roblox` built-in.
|
|||
- Added a `roblox` built-in
|
||||
|
||||
If you're familiar with [Remodel](https://github.com/rojo-rbx/remodel), this new built-in contains more or less the same APIs, integrated into Lune. <br />
|
||||
There are just too many new APIs to list in this changelog, so head over to the [docs sit](https://lune.gitbook.io/lune/roblox/intro) to learn more!
|
||||
There are just too many new APIs to list in this changelog, so head over to the [docs sit](https://lune-org.github.io/docs/roblox/1-introduction) to learn more!
|
||||
|
||||
- Added a `serde` built-in
|
||||
|
||||
|
|
71
CONTRIBUTING.md
Normal file
|
@ -0,0 +1,71 @@
|
|||
<!-- markdownlint-disable MD001 -->
|
||||
<!-- markdownlint-disable MD033 -->
|
||||
|
||||
# Contributing
|
||||
|
||||
---
|
||||
|
||||
### Reporting a Bug
|
||||
|
||||
- Make sure the bug has not already been reported by searching on GitHub under [Issues](https://github.com/lune-org/lune/issues).
|
||||
- If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/lune-org/lune/issues/new). Be sure to include a **title and description**, as much relevant information as possible, and if applicable, a **code sample** or a **test case** demonstrating the expected behavior.
|
||||
|
||||
---
|
||||
|
||||
### Contributing - Bug Fixes
|
||||
|
||||
1. Make sure an [issue](https://github.com/lune-org/lune/issues) has been created for the bug first, so that it can be tracked and searched for in the repository history. This is not mandatory for small fixes.
|
||||
2. Open a new GitHub pull request for it. A pull request for a bug fix must include:
|
||||
- A clear and concise description of the bug it is fixing.
|
||||
- A new test file ensuring there are no regressions after the bug has been fixed.
|
||||
- A link to the relevant issue, or a `Fixes #issue` line, if an issue exists.
|
||||
|
||||
### Contributing - Features
|
||||
|
||||
1. Make sure an [issue](https://github.com/lune-org/lune/issues) has been created for the feature first, so that it can be tracked and searched for in the repository history. If you are making changes to an existing feature, and no issue exists, one should be created for the proposed changes.
|
||||
2. Any API design or considerations should first be brought up and discussed in the relevant issue, to prevent long review times on pull requests and unnecessary work for maintainers.
|
||||
3. Familiarize yourself with the codebase and the tools you will be using. Some important parts include:
|
||||
- The [mlua](https://crates.io/crates/mlua) library, which we use to interface with Luau.
|
||||
- Any [built-in libraries](https://github.com/lune-org/lune/tree/main/src/lune/builtins) that are relevant for your new feature. If you are making a new built-in library, refer to existing ones for structure and implementation details.
|
||||
- Our toolchain, notably [StyLua](https://github.com/JohnnyMorganz/StyLua), [rustfmt](https://github.com/rust-lang/rustfmt), and [clippy](https://github.com/rust-lang/rust-clippy). If you do not use these tools there is a decent chance CI will fail on your pull request, blocking it from getting approved.
|
||||
4. Write some code!
|
||||
5. Open a new GitHub pull request. A pull request for a feature must include:
|
||||
- A clear and concise description of the new feature or changes to the feature.
|
||||
- Test files for any added or changed functionality.
|
||||
- A link to the relevant issue, or a `Closes #issue` line.
|
||||
|
||||
### Contributing - Formatting & Cosmetic Changes
|
||||
|
||||
Changes that are purely cosmetic, and do not add to the stability, functionality, or testability of Lune, will generally not be accepted unless there has been previous discussion about the changes being made.
|
||||
|
||||
### Contributing - Documentation
|
||||
|
||||
#### Documentation Site
|
||||
|
||||
Check out the [docs](https://github.com/lune-org/docs) repository and its contribution guidelines.
|
||||
|
||||
#### Type Definitions
|
||||
|
||||
If type definitions for built-in libraries need improvements:
|
||||
|
||||
1. Check out the [types](https://github.com/lune-org/lune/tree/main/types) directory at the root of the repository.
|
||||
2. Make the desired changes, and verify that they have the desired outcome.
|
||||
3. Open a new GitHub pull request for your changes.
|
||||
|
||||
---
|
||||
|
||||
### Publishing a Release
|
||||
|
||||
The Lune release process is semi-automated, and takes care of most things for you. Here's how to create a new release:
|
||||
|
||||
1. Make sure the changelog is up to date and contains all of the changes since the last release.
|
||||
2. Add the release date in the changelog + set a new version number in `Cargo.toml`.
|
||||
3. Commit and push changes from step 2 to GitHub. This will automatically publish the Lune library to [crates.io](https://crates.io) when the version number changes.
|
||||
4. Trigger the [release](https://github.com/lune-org/lune/actions/workflows/release.yaml) workflow on GitHub manually, and wait for it to finish. Find the new pending release in the [Releases](https://github.com/lune-org/lune/releases) section.
|
||||
5. Add in changes from the changelog for the new pending release into the description, hit "accept" on creating a new version tag, and publish 🚀
|
||||
|
||||
---
|
||||
|
||||
If you have any questions, check out the `#lune` channel in the [Roblox OSS discord](https://discord.gg/H9WqmFAB5Y), where most of our realtime discussion takes place!
|
||||
|
||||
Thank you for contributing to Lune! 🌙
|
2956
Cargo.lock
generated
83
Cargo.toml
|
@ -1,39 +1,23 @@
|
|||
[workspace]
|
||||
members = ["packages/cli", "packages/lib", "packages/lib-roblox"]
|
||||
default-members = ["packages/cli"]
|
||||
|
||||
# Package config values shared across all packages,
|
||||
# such as version, license, and other metadata
|
||||
[workspace.package]
|
||||
version = "0.7.4"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/filiptibell/lune"
|
||||
description = "A Luau script runner"
|
||||
readme = "README.md"
|
||||
keywords = ["cli", "lua", "luau", "scripts"]
|
||||
categories = ["command-line-interface"]
|
||||
|
||||
# Shared dependencies that are used across 2 or more packages
|
||||
# These are declared here to ensure consistent versioning
|
||||
[workspace.dependencies]
|
||||
console = "0.15"
|
||||
directories = "5.0"
|
||||
futures-util = "0.3"
|
||||
once_cell = "1.17"
|
||||
thiserror = "1.0"
|
||||
|
||||
mlua = { version = "0.9.0-beta.3", features = ["luau", "serialize"] }
|
||||
tokio = { version = "1.24", features = ["full"] }
|
||||
reqwest = { version = "0.11", default-features = false, features = [
|
||||
"rustls-tls",
|
||||
] }
|
||||
|
||||
# Serde dependencies, supporting user-facing formats: json, yaml, toml
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = { version = "1.0", features = ["preserve_order"] }
|
||||
serde_yaml = "0.9"
|
||||
toml = { version = "0.7", features = ["preserve_order"] }
|
||||
resolver = "2"
|
||||
default-members = ["crates/lune"]
|
||||
members = [
|
||||
"crates/lune",
|
||||
"crates/lune-roblox",
|
||||
"crates/lune-std",
|
||||
"crates/lune-std-datetime",
|
||||
"crates/lune-std-fs",
|
||||
"crates/lune-std-luau",
|
||||
"crates/lune-std-net",
|
||||
"crates/lune-std-process",
|
||||
"crates/lune-std-regex",
|
||||
"crates/lune-std-roblox",
|
||||
"crates/lune-std-serde",
|
||||
"crates/lune-std-stdio",
|
||||
"crates/lune-std-task",
|
||||
"crates/lune-utils",
|
||||
"crates/mlua-luau-scheduler",
|
||||
]
|
||||
|
||||
# Profile for building the release binary, with the following options set:
|
||||
#
|
||||
|
@ -49,3 +33,32 @@ toml = { version = "0.7", features = ["preserve_order"] }
|
|||
opt-level = "z"
|
||||
strip = true
|
||||
lto = true
|
||||
|
||||
# Lints for all crates in the workspace
|
||||
#
|
||||
# 1. Error on all lints by default, then make cargo + clippy pedantic lints just warn
|
||||
# 2. Selectively allow some lints that are _too_ pedantic, such as:
|
||||
# - Casts between number types
|
||||
# - Module naming conventions
|
||||
# - Imports and multiple dependency versions
|
||||
[workspace.lints.clippy]
|
||||
all = { level = "deny", priority = -3 }
|
||||
cargo = { level = "warn", priority = -2 }
|
||||
pedantic = { level = "warn", priority = -1 }
|
||||
|
||||
cast_lossless = { level = "allow", priority = 1 }
|
||||
cast_possible_truncation = { level = "allow", priority = 1 }
|
||||
cast_possible_wrap = { level = "allow", priority = 1 }
|
||||
cast_precision_loss = { level = "allow", priority = 1 }
|
||||
cast_sign_loss = { level = "allow", priority = 1 }
|
||||
|
||||
similar_names = { level = "allow", priority = 1 }
|
||||
unnecessary_wraps = { level = "allow", priority = 1 }
|
||||
unnested_or_patterns = { level = "allow", priority = 1 }
|
||||
unreadable_literal = { level = "allow", priority = 1 }
|
||||
|
||||
multiple_crate_versions = { level = "allow", priority = 1 }
|
||||
module_inception = { level = "allow", priority = 1 }
|
||||
module_name_repetitions = { level = "allow", priority = 1 }
|
||||
needless_pass_by_value = { level = "allow", priority = 1 }
|
||||
wildcard_imports = { level = "allow", priority = 1 }
|
||||
|
|
37
README.md
|
@ -1,45 +1,48 @@
|
|||
<!-- markdownlint-disable MD033 -->
|
||||
<!-- markdownlint-disable MD041 -->
|
||||
|
||||
<img align="right" width="250" src="assets/logo/tilt_svg.svg" alt="Lune logo" />
|
||||
|
||||
<h1 align="center">Lune</h1>
|
||||
|
||||
<div align="center">
|
||||
<h1> Lune 🌙 </h1>
|
||||
<div>
|
||||
<a href="https://crates.io/crates/lune">
|
||||
<img src="https://img.shields.io/crates/v/lune.svg?label=Version" alt="Current Lune library version" />
|
||||
</a>
|
||||
<a href="https://github.com/filiptibell/lune/actions">
|
||||
<img src="https://shields.io/endpoint?url=https://badges.readysetplay.io/workflow/filiptibell/lune/ci.yaml" alt="CI status" />
|
||||
<a href="https://github.com/lune-org/lune/actions">
|
||||
<img src="https://shields.io/endpoint?url=https://badges.readysetplay.io/workflow/lune-org/lune/ci.yaml" alt="CI status" />
|
||||
</a>
|
||||
<a href="https://github.com/filiptibell/lune/actions">
|
||||
<img src="https://shields.io/endpoint?url=https://badges.readysetplay.io/workflow/filiptibell/lune/release.yaml" alt="Release status" />
|
||||
<a href="https://github.com/lune-org/lune/actions">
|
||||
<img src="https://shields.io/endpoint?url=https://badges.readysetplay.io/workflow/lune-org/lune/release.yaml" alt="Release status" />
|
||||
</a>
|
||||
<a href="https://github.com/filiptibell/lune/blob/main/LICENSE.txt">
|
||||
<img src="https://img.shields.io/github/license/filiptibell/lune.svg?label=License&color=informational" alt="Lune license" />
|
||||
<a href="https://github.com/lune-org/lune/blob/main/LICENSE.txt">
|
||||
<img src="https://img.shields.io/github/license/lune-org/lune.svg?label=License&color=informational" alt="Lune license" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
---
|
||||
<br/>
|
||||
|
||||
A standalone [Luau](https://luau-lang.org) script runtime.
|
||||
A standalone [Luau](https://luau-lang.org) runtime.
|
||||
|
||||
Write and run scripts, similar to runtimes for other languages such as [Node](https://nodejs.org) / [Deno](https://deno.land), or [Luvit](https://luvit.io) for vanilla Lua.
|
||||
Write and run programs, similar to runtimes for other languages such as [Node](https://nodejs.org), [Deno](https://deno.land), [Bun](https://bun.sh), or [Luvit](https://luvit.io) for vanilla Lua.
|
||||
|
||||
Lune provides fully asynchronous APIs wherever possible, and is built in Rust 🦀 for optimal safety and correctness.
|
||||
Lune provides fully asynchronous APIs wherever possible, and is built in Rust 🦀 for speed, safety and correctness.
|
||||
|
||||
## Features
|
||||
|
||||
- 🌙 A 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 (~4mb) executable
|
||||
- 🌙 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
|
||||
- 📚 World-class documentation, on the web _or_ directly in your editor, no network connection necessary
|
||||
- 🏡 A familiar scripting environment for Roblox developers, with an included 1-to-1 task scheduler port
|
||||
- 🏡 Familiar runtime environment for Roblox developers, with an included 1-to-1 task scheduler port
|
||||
- ✏️ Optional built-in library for manipulating Roblox place & model files, and their instances
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Making scripts short and terse - proper autocomplete / intellisense make scripting using Lune just as quick, and readability is important
|
||||
- Running full Roblox game scripts outside of Roblox - there is some compatibility, but Lune is meant for different purposes
|
||||
- Making programs short and terse - proper autocomplete / intellisense make using Lune just as quick, and readability is important
|
||||
- Running full Roblox games outside of Roblox - there is some compatibility, but Lune is meant for different purposes
|
||||
|
||||
## Where do I start?
|
||||
|
||||
Head over to the [installation](https://lune.gitbook.io/lune/home/installation) page to get started using Lune!
|
||||
Head over to the [Installation](https://lune-org.github.io/docs/getting-started/1-installation) page to get started using Lune!
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
[tools]
|
||||
luau-lsp = "JohnnyMorganz/luau-lsp@1.20.0"
|
||||
selene = "Kampfkarren/selene@0.24.0"
|
||||
stylua = "JohnnyMorganz/StyLua@0.17.0"
|
BIN
assets/logo/no-tilt-grid.ico
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
assets/logo/no-tilt-grid.png
Normal file
After Width: | Height: | Size: 148 KiB |
69
assets/logo/no-tilt-grid_svg.svg
Normal file
|
@ -0,0 +1,69 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_i_6_19)">
|
||||
<g clip-path="url(#clip0_6_19)">
|
||||
<rect x="2" y="2" width="16" height="16" rx="1" fill="url(#paint0_linear_6_19)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.875 0.125L18.125 0.125V1.875H19.875V0.125ZM19.875 2.125H18.125V3.875H19.875V2.125ZM19.875 4.125H18.125V5.875H19.875V4.125ZM19.875 6.125H18.125V7.875H19.875V6.125ZM19.875 8.125H18.125V9.875H19.875V8.125ZM19.875 10.125H18.125V11.875H19.875V10.125ZM19.875 12.125H18.125V13.875H19.875V12.125ZM19.875 14.125H18.125V15.875H19.875V14.125ZM19.875 16.125H18.125V17.875H19.875V16.125ZM19.875 18.125H18.125V19.875H19.875V18.125ZM20 20V20.125H4.76837e-07V20H-0.125L-0.125 -5.21541e-07L4.76837e-07 -5.1409e-07V-0.125L20 -0.125V2.92063e-06H20.125V20H20ZM0.125 0.125L0.125 1.875H1.87499L1.87499 0.125L0.125 0.125ZM2.12499 0.125V1.875H3.87499V0.125L2.12499 0.125ZM4.12499 0.125V1.875H5.87499V0.125L4.12499 0.125ZM6.12499 0.125V1.875H7.87499V0.125L6.12499 0.125ZM8.12499 0.125V1.875H9.87499V0.125L8.12499 0.125ZM10.125 0.125V1.875H11.875V0.125L10.125 0.125ZM12.125 0.125V1.875H13.875V0.125L12.125 0.125ZM14.125 0.125V1.875H15.875V0.125L14.125 0.125ZM16.125 0.125V1.875H17.875V0.125L16.125 0.125ZM17.875 2.125H16.125V3.875H17.875V2.125ZM17.875 4.125H16.125V5.875H17.875V4.125ZM17.875 6.125H16.125V7.875H17.875V6.125ZM17.875 8.125H16.125V9.875H17.875V8.125ZM17.875 10.125H16.125V11.875H17.875V10.125ZM17.875 12.125H16.125V13.875H17.875V12.125ZM17.875 14.125H16.125V15.875H17.875V14.125ZM17.875 16.125H16.125V17.875H17.875V16.125ZM17.875 18.125H16.125V19.875H17.875V18.125ZM15.875 19.875V18.125H14.125V19.875H15.875ZM13.875 19.875V18.125H12.125V19.875H13.875ZM11.875 19.875V18.125H10.125V19.875H11.875ZM9.87499 19.875V18.125H8.12499V19.875H9.87499ZM7.87499 19.875V18.125H6.12499V19.875H7.87499ZM5.87499 19.875V18.125H4.12499V19.875H5.87499ZM3.87499 19.875V18.125H2.12499V19.875H3.87499ZM1.87499 19.875L1.87499 18.125H0.124999L0.124999 19.875H1.87499ZM0.124999 17.875H1.87499V16.125H0.124999L0.124999 17.875ZM0.124999 15.875H1.87499L1.87499 14.125H0.124999L0.124999 15.875ZM0.124999 13.875H1.87499L1.87499 12.125H0.124999L0.124999 13.875ZM0.124999 11.875H1.87499L1.87499 10.125H0.125L0.124999 11.875ZM0.125 9.875H1.87499V8.125H0.125L0.125 9.875ZM0.125 7.875H1.87499L1.87499 6.125H0.125L0.125 7.875ZM0.125 5.875H1.87499L1.87499 4.125H0.125L0.125 5.875ZM0.125 3.875H1.87499V2.125H0.125L0.125 3.875ZM2.12499 2.125L2.12499 3.875H3.87499L3.87499 2.125H2.12499ZM4.12499 2.125V3.875H5.87499V2.125H4.12499ZM6.12499 2.125V3.875H7.87499V2.125H6.12499ZM8.12499 2.125V3.875H9.87499V2.125H8.12499ZM10.125 2.125V3.875H11.875V2.125H10.125ZM12.125 2.125V3.875H13.875V2.125H12.125ZM14.125 2.125V3.875H15.875V2.125H14.125ZM15.875 4.125H14.125V5.875H15.875V4.125ZM15.875 6.125H14.125V7.875H15.875V6.125ZM15.875 8.125H14.125V9.875H15.875V8.125ZM15.875 10.125H14.125V11.875H15.875V10.125ZM15.875 12.125H14.125V13.875H15.875V12.125ZM15.875 14.125H14.125V15.875H15.875V14.125ZM15.875 16.125H14.125V17.875H15.875V16.125ZM13.875 17.875V16.125H12.125V17.875H13.875ZM11.875 17.875V16.125H10.125V17.875H11.875ZM9.87499 17.875V16.125H8.12499V17.875H9.87499ZM7.87499 17.875V16.125H6.12499V17.875H7.87499ZM5.87499 17.875V16.125H4.12499V17.875H5.87499ZM3.87499 17.875L3.87499 16.125H2.12499L2.12499 17.875H3.87499ZM2.12499 15.875H3.87499V14.125H2.12499V15.875ZM2.12499 13.875H3.87499L3.87499 12.125H2.12499L2.12499 13.875ZM2.12499 11.875H3.87499V10.125H2.12499V11.875ZM2.12499 9.875H3.87499V8.125H2.12499V9.875ZM2.12499 7.875H3.87499L3.87499 6.125H2.12499L2.12499 7.875ZM2.12499 5.875H3.87499V4.125H2.12499V5.875ZM4.12499 4.125L4.12499 5.875H5.87499L5.87499 4.125H4.12499ZM6.12499 4.125L6.12499 5.875H7.87499L7.87499 4.125H6.12499ZM8.12499 4.125V5.875H9.87499V4.125H8.12499ZM10.125 4.125V5.875H11.875V4.125H10.125ZM12.125 4.125V5.875H13.875V4.125H12.125ZM13.875 6.125H12.125V7.875H13.875V6.125ZM13.875 8.125H12.125V9.875H13.875V8.125ZM13.875 10.125H12.125V11.875H13.875V10.125ZM13.875 12.125H12.125V13.875H13.875V12.125ZM13.875 14.125H12.125V15.875H13.875V14.125ZM11.875 15.875V14.125H10.125V15.875H11.875ZM9.87499 15.875V14.125H8.12499V15.875H9.87499ZM7.87499 15.875L7.87499 14.125H6.12499L6.12499 15.875H7.87499ZM5.87499 15.875L5.87499 14.125H4.12499L4.12499 15.875H5.87499ZM4.12499 13.875H5.87499V12.125H4.12499V13.875ZM4.12499 11.875H5.87499V10.125H4.12499V11.875ZM4.12499 9.875H5.87499V8.125H4.12499V9.875ZM4.12499 7.875H5.87499V6.125H4.12499V7.875ZM6.12499 6.125V7.875H7.87499V6.125H6.12499ZM8.12499 6.125V7.875H9.87499V6.125H8.12499ZM10.125 6.125V7.875H11.875V6.125H10.125ZM11.875 8.125H10.125V9.875H11.875V8.125ZM11.875 10.125H10.125V11.875H11.875V10.125ZM11.875 12.125H10.125V13.875H11.875V12.125ZM9.87499 13.875V12.125H8.12499V13.875H9.87499ZM7.87499 13.875V12.125H6.12499V13.875H7.87499ZM6.12499 11.875H7.87499V10.125H6.12499V11.875ZM6.12499 9.875H7.87499V8.125H6.12499V9.875ZM8.12499 8.125V9.875H9.87499V8.125H8.12499ZM9.87499 10.125H8.12499V11.875H9.87499V10.125Z" fill="url(#paint1_linear_6_19)" fill-opacity="0.05"/>
|
||||
<g filter="url(#filter1_di_6_19)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.375 6.25C14.2725 6.25 15 5.52246 15 4.625C15 4.4376 14.9683 4.2576 14.9099 4.09009C15.5446 4.31125 16 4.91494 16 5.625C16 6.52246 15.2725 7.25 14.375 7.25C13.6649 7.25 13.0613 6.79458 12.8401 6.15991C13.0076 6.21828 13.1876 6.25 13.375 6.25Z" fill="white"/>
|
||||
</g>
|
||||
<g filter="url(#filter2_di_6_19)">
|
||||
<path d="M6.625 15.54V16H4.49219V15.54H6.625ZM4.60352 11.7344V16H4.03809V11.7344H4.60352ZM9.00098 15.2676V12.8301H9.5459V16H9.02734L9.00098 15.2676ZM9.10352 14.5996L9.3291 14.5938C9.3291 14.8047 9.30664 15 9.26172 15.1797C9.21875 15.3574 9.14844 15.5117 9.05078 15.6426C8.95312 15.7734 8.8252 15.876 8.66699 15.9502C8.50879 16.0225 8.31641 16.0586 8.08984 16.0586C7.93555 16.0586 7.79395 16.0361 7.66504 15.9912C7.53809 15.9463 7.42871 15.877 7.33691 15.7832C7.24512 15.6895 7.17383 15.5674 7.12305 15.417C7.07422 15.2666 7.0498 15.0859 7.0498 14.875V12.8301H7.5918V14.8809C7.5918 15.0234 7.60742 15.1416 7.63867 15.2354C7.67188 15.3271 7.71582 15.4004 7.77051 15.4551C7.82715 15.5078 7.88965 15.5449 7.95801 15.5664C8.02832 15.5879 8.10059 15.5986 8.1748 15.5986C8.40527 15.5986 8.58789 15.5547 8.72266 15.4668C8.85742 15.377 8.9541 15.2568 9.0127 15.1064C9.07324 14.9541 9.10352 14.7852 9.10352 14.5996ZM10.9141 13.5068V16H10.3721V12.8301H10.8848L10.9141 13.5068ZM10.7852 14.2949L10.5596 14.2861C10.5615 14.0693 10.5938 13.8691 10.6562 13.6855C10.7188 13.5 10.8066 13.3389 10.9199 13.2021C11.0332 13.0654 11.168 12.96 11.3242 12.8857C11.4824 12.8096 11.6572 12.7715 11.8486 12.7715C12.0049 12.7715 12.1455 12.793 12.2705 12.8359C12.3955 12.877 12.502 12.9434 12.5898 13.0352C12.6797 13.127 12.748 13.2461 12.7949 13.3926C12.8418 13.5371 12.8652 13.7139 12.8652 13.9229V16H12.3203V13.917C12.3203 13.751 12.2959 13.6182 12.2471 13.5186C12.1982 13.417 12.127 13.3438 12.0332 13.2988C11.9395 13.252 11.8242 13.2285 11.6875 13.2285C11.5527 13.2285 11.4297 13.2568 11.3184 13.3135C11.209 13.3701 11.1143 13.4482 11.0342 13.5479C10.9561 13.6475 10.8945 13.7617 10.8496 13.8906C10.8066 14.0176 10.7852 14.1523 10.7852 14.2949ZM15.0039 16.0586C14.7832 16.0586 14.583 16.0215 14.4033 15.9473C14.2256 15.8711 14.0723 15.7646 13.9434 15.6279C13.8164 15.4912 13.7188 15.3291 13.6504 15.1416C13.582 14.9541 13.5479 14.749 13.5479 14.5264V14.4033C13.5479 14.1455 13.5859 13.916 13.6621 13.7148C13.7383 13.5117 13.8418 13.3398 13.9727 13.1992C14.1035 13.0586 14.252 12.9521 14.418 12.8799C14.584 12.8076 14.7559 12.7715 14.9336 12.7715C15.1602 12.7715 15.3555 12.8105 15.5195 12.8887C15.6855 12.9668 15.8213 13.0762 15.9268 13.2168C16.0322 13.3555 16.1104 13.5195 16.1611 13.709C16.2119 13.8965 16.2373 14.1016 16.2373 14.3242V14.5674H13.8701V14.125H15.6953V14.084C15.6875 13.9434 15.6582 13.8066 15.6074 13.6738C15.5586 13.541 15.4805 13.4316 15.373 13.3457C15.2656 13.2598 15.1191 13.2168 14.9336 13.2168C14.8105 13.2168 14.6973 13.2432 14.5938 13.2959C14.4902 13.3467 14.4014 13.4229 14.3271 13.5244C14.2529 13.626 14.1953 13.75 14.1543 13.8965C14.1133 14.043 14.0928 14.2119 14.0928 14.4033V14.5264C14.0928 14.6768 14.1133 14.8184 14.1543 14.9512C14.1973 15.082 14.2588 15.1973 14.3389 15.2969C14.4209 15.3965 14.5195 15.4746 14.6348 15.5312C14.752 15.5879 14.8848 15.6162 15.0332 15.6162C15.2246 15.6162 15.3867 15.5771 15.5195 15.499C15.6523 15.4209 15.7686 15.3164 15.8682 15.1855L16.1963 15.4463C16.1279 15.5498 16.041 15.6484 15.9355 15.7422C15.8301 15.8359 15.7002 15.9121 15.5459 15.9707C15.3936 16.0293 15.2129 16.0586 15.0039 16.0586Z" fill="white"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_i_6_19" x="2" y="1.5" width="16.5" height="16.5" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dx="0.5" dy="-0.5"/>
|
||||
<feGaussianBlur stdDeviation="0.25"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0"/>
|
||||
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_6_19"/>
|
||||
</filter>
|
||||
<filter id="filter1_di_6_19" x="10.8401" y="3.09009" width="6.15991" height="6.15991" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dx="-0.5" dy="0.5"/>
|
||||
<feGaussianBlur stdDeviation="0.75"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_6_19"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_6_19" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dx="0.25" dy="-0.25"/>
|
||||
<feGaussianBlur stdDeviation="0.375"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
|
||||
<feBlend mode="normal" in2="shape" result="effect2_innerShadow_6_19"/>
|
||||
</filter>
|
||||
<filter id="filter2_di_6_19" x="2.03809" y="10.7344" width="15.1992" height="7.32422" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dx="-0.5" dy="0.5"/>
|
||||
<feGaussianBlur stdDeviation="0.75"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_6_19"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_6_19" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dx="0.25" dy="-0.25"/>
|
||||
<feGaussianBlur stdDeviation="0.375"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
|
||||
<feBlend mode="normal" in2="shape" result="effect2_innerShadow_6_19"/>
|
||||
</filter>
|
||||
<linearGradient id="paint0_linear_6_19" x1="10" y1="2" x2="10" y2="18" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#E168FF"/>
|
||||
<stop offset="1" stop-color="#C848E9"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_6_19" x1="10" y1="-0.125" x2="10" y2="20.125" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white" stop-opacity="0.5"/>
|
||||
<stop offset="1" stop-color="white"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_6_19">
|
||||
<rect x="2" y="2" width="16" height="16" rx="1" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 12 KiB |
BIN
assets/logo/no-tilt.ico
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
assets/logo/no-tilt.png
Normal file
After Width: | Height: | Size: 138 KiB |
64
assets/logo/no-tilt_svg.svg
Normal file
|
@ -0,0 +1,64 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g filter="url(#filter0_i_6_67)">
|
||||
<g clip-path="url(#clip0_6_67)">
|
||||
<rect x="2" y="2" width="16" height="16" rx="1" fill="url(#paint0_linear_6_67)"/>
|
||||
<g filter="url(#filter1_di_6_67)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.375 6.25C14.2725 6.25 15 5.52246 15 4.625C15 4.43759 14.9683 4.2576 14.9099 4.09009C15.5446 4.31125 16 4.91494 16 5.625C16 6.52246 15.2725 7.25 14.375 7.25C13.6649 7.25 13.0613 6.79458 12.8401 6.15991C13.0076 6.21828 13.1876 6.25 13.375 6.25Z" fill="white"/>
|
||||
</g>
|
||||
<g filter="url(#filter2_di_6_67)">
|
||||
<path d="M6.625 15.54V16H4.49219V15.54H6.625ZM4.60352 11.7344V16H4.03809V11.7344H4.60352ZM9.00098 15.2676V12.8301H9.5459V16H9.02734L9.00098 15.2676ZM9.10352 14.5996L9.3291 14.5938C9.3291 14.8047 9.30664 15 9.26172 15.1797C9.21875 15.3574 9.14844 15.5117 9.05078 15.6426C8.95312 15.7734 8.8252 15.876 8.66699 15.9502C8.50879 16.0225 8.31641 16.0586 8.08984 16.0586C7.93555 16.0586 7.79395 16.0361 7.66504 15.9912C7.53809 15.9463 7.42871 15.877 7.33691 15.7832C7.24512 15.6895 7.17383 15.5674 7.12305 15.417C7.07422 15.2666 7.0498 15.0859 7.0498 14.875V12.8301H7.5918V14.8809C7.5918 15.0234 7.60742 15.1416 7.63867 15.2354C7.67188 15.3271 7.71582 15.4004 7.77051 15.4551C7.82715 15.5078 7.88965 15.5449 7.95801 15.5664C8.02832 15.5879 8.10059 15.5986 8.1748 15.5986C8.40527 15.5986 8.58789 15.5547 8.72266 15.4668C8.85742 15.377 8.9541 15.2568 9.0127 15.1064C9.07324 14.9541 9.10352 14.7852 9.10352 14.5996ZM10.9141 13.5068V16H10.3721V12.8301H10.8848L10.9141 13.5068ZM10.7852 14.2949L10.5596 14.2861C10.5615 14.0693 10.5938 13.8691 10.6562 13.6855C10.7188 13.5 10.8066 13.3389 10.9199 13.2021C11.0332 13.0654 11.168 12.96 11.3242 12.8857C11.4824 12.8096 11.6572 12.7715 11.8486 12.7715C12.0049 12.7715 12.1455 12.793 12.2705 12.8359C12.3955 12.877 12.502 12.9434 12.5898 13.0352C12.6797 13.127 12.748 13.2461 12.7949 13.3926C12.8418 13.5371 12.8652 13.7139 12.8652 13.9229V16H12.3203V13.917C12.3203 13.751 12.2959 13.6182 12.2471 13.5186C12.1982 13.417 12.127 13.3438 12.0332 13.2988C11.9395 13.252 11.8242 13.2285 11.6875 13.2285C11.5527 13.2285 11.4297 13.2568 11.3184 13.3135C11.209 13.3701 11.1143 13.4482 11.0342 13.5479C10.9561 13.6475 10.8945 13.7617 10.8496 13.8906C10.8066 14.0176 10.7852 14.1523 10.7852 14.2949ZM15.0039 16.0586C14.7832 16.0586 14.583 16.0215 14.4033 15.9473C14.2256 15.8711 14.0723 15.7646 13.9434 15.6279C13.8164 15.4912 13.7188 15.3291 13.6504 15.1416C13.582 14.9541 13.5479 14.749 13.5479 14.5264V14.4033C13.5479 14.1455 13.5859 13.916 13.6621 13.7148C13.7383 13.5117 13.8418 13.3398 13.9727 13.1992C14.1035 13.0586 14.252 12.9521 14.418 12.8799C14.584 12.8076 14.7559 12.7715 14.9336 12.7715C15.1602 12.7715 15.3555 12.8105 15.5195 12.8887C15.6855 12.9668 15.8213 13.0762 15.9268 13.2168C16.0322 13.3555 16.1104 13.5195 16.1611 13.709C16.2119 13.8965 16.2373 14.1016 16.2373 14.3242V14.5674H13.8701V14.125H15.6953V14.084C15.6875 13.9434 15.6582 13.8066 15.6074 13.6738C15.5586 13.541 15.4805 13.4316 15.373 13.3457C15.2656 13.2598 15.1191 13.2168 14.9336 13.2168C14.8105 13.2168 14.6973 13.2432 14.5938 13.2959C14.4902 13.3467 14.4014 13.4229 14.3271 13.5244C14.2529 13.626 14.1953 13.75 14.1543 13.8965C14.1133 14.043 14.0928 14.2119 14.0928 14.4033V14.5264C14.0928 14.6768 14.1133 14.8184 14.1543 14.9512C14.1973 15.082 14.2588 15.1973 14.3389 15.2969C14.4209 15.3965 14.5195 15.4746 14.6348 15.5312C14.752 15.5879 14.8848 15.6162 15.0332 15.6162C15.2246 15.6162 15.3867 15.5771 15.5195 15.499C15.6523 15.4209 15.7686 15.3164 15.8682 15.1855L16.1963 15.4463C16.1279 15.5498 16.041 15.6484 15.9355 15.7422C15.8301 15.8359 15.7002 15.9121 15.5459 15.9707C15.3936 16.0293 15.2129 16.0586 15.0039 16.0586Z" fill="white"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_i_6_67" x="2" y="1.5" width="16.5" height="16.5" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dx="0.5" dy="-0.5"/>
|
||||
<feGaussianBlur stdDeviation="0.25"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0"/>
|
||||
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_6_67"/>
|
||||
</filter>
|
||||
<filter id="filter1_di_6_67" x="10.8401" y="3.09009" width="6.15991" height="6.15991" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dx="-0.5" dy="0.5"/>
|
||||
<feGaussianBlur stdDeviation="0.75"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_6_67"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_6_67" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dx="0.25" dy="-0.25"/>
|
||||
<feGaussianBlur stdDeviation="0.375"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
|
||||
<feBlend mode="normal" in2="shape" result="effect2_innerShadow_6_67"/>
|
||||
</filter>
|
||||
<filter id="filter2_di_6_67" x="2.03809" y="10.7344" width="15.1992" height="7.32422" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dx="-0.5" dy="0.5"/>
|
||||
<feGaussianBlur stdDeviation="0.75"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_6_67"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_6_67" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dx="0.25" dy="-0.25"/>
|
||||
<feGaussianBlur stdDeviation="0.375"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
|
||||
<feBlend mode="normal" in2="shape" result="effect2_innerShadow_6_67"/>
|
||||
</filter>
|
||||
<linearGradient id="paint0_linear_6_67" x1="10" y1="2" x2="10" y2="18" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#E168FF"/>
|
||||
<stop offset="1" stop-color="#C848E9"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_6_67">
|
||||
<rect x="2" y="2" width="16" height="16" rx="1" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 6.9 KiB |
BIN
assets/logo/tilt-grid.ico
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
assets/logo/tilt-grid.png
Normal file
After Width: | Height: | Size: 180 KiB |
74
assets/logo/tilt-grid_svg.svg
Normal file
|
@ -0,0 +1,74 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_6_97)">
|
||||
<g filter="url(#filter0_i_6_97)">
|
||||
<g clip-path="url(#clip1_6_97)">
|
||||
<rect x="4.34315" y="0.202042" width="16" height="16" rx="1" transform="rotate(15 4.34315 0.202042)" fill="url(#paint0_linear_6_97)"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M19.875 0.125H18.125V1.875H19.875V0.125ZM19.875 2.125H18.125V3.875H19.875V2.125ZM19.875 4.125H18.125V5.875H19.875V4.125ZM19.875 6.125H18.125V7.875H19.875V6.125ZM19.875 8.125H18.125V9.875H19.875V8.125ZM19.875 10.125H18.125V11.875H19.875V10.125ZM19.875 12.125H18.125V13.875H19.875V12.125ZM19.875 14.125H18.125V15.875H19.875V14.125ZM19.875 16.125H18.125V17.875H19.875V16.125ZM19.875 18.125H18.125V19.875H19.875V18.125ZM20 20V20.125H4.76837e-07V20H-0.125V-5.21541e-07L4.76837e-07 -5.1409e-07V-0.125H20V2.92063e-06H20.125V20H20ZM0.125 0.125L0.125 1.875H1.87499L1.87499 0.125H0.125ZM2.12499 0.125V1.875H3.87499V0.125H2.12499ZM4.12499 0.125V1.875H5.87499V0.125H4.12499ZM6.12499 0.125V1.875H7.87499V0.125H6.12499ZM8.12499 0.125V1.875H9.87499V0.125H8.12499ZM10.125 0.125V1.875H11.875V0.125H10.125ZM12.125 0.125V1.875H13.875V0.125H12.125ZM14.125 0.125V1.875H15.875V0.125H14.125ZM16.125 0.125V1.875H17.875V0.125H16.125ZM17.875 2.125H16.125V3.875H17.875V2.125ZM17.875 4.125H16.125V5.875H17.875V4.125ZM17.875 6.125H16.125V7.875H17.875V6.125ZM17.875 8.125H16.125V9.875H17.875V8.125ZM17.875 10.125H16.125V11.875H17.875V10.125ZM17.875 12.125H16.125V13.875H17.875V12.125ZM17.875 14.125H16.125V15.875H17.875V14.125ZM17.875 16.125H16.125V17.875H17.875V16.125ZM17.875 18.125H16.125V19.875H17.875V18.125ZM15.875 19.875V18.125H14.125V19.875H15.875ZM13.875 19.875V18.125H12.125V19.875H13.875ZM11.875 19.875V18.125H10.125V19.875H11.875ZM9.87499 19.875V18.125H8.12499V19.875H9.87499ZM7.87499 19.875V18.125H6.12499V19.875H7.87499ZM5.87499 19.875V18.125H4.12499V19.875H5.87499ZM3.87499 19.875V18.125H2.12499V19.875H3.87499ZM1.87499 19.875L1.87499 18.125H0.124999L0.124999 19.875H1.87499ZM0.124999 17.875H1.87499V16.125H0.124999L0.124999 17.875ZM0.124999 15.875H1.87499L1.87499 14.125H0.124999L0.124999 15.875ZM0.124999 13.875H1.87499L1.87499 12.125H0.124999L0.124999 13.875ZM0.124999 11.875H1.87499V10.125H0.125L0.124999 11.875ZM0.125 9.875H1.87499V8.125H0.125L0.125 9.875ZM0.125 7.875H1.87499L1.87499 6.125H0.125L0.125 7.875ZM0.125 5.875H1.87499L1.87499 4.125H0.125L0.125 5.875ZM0.125 3.875H1.87499V2.125H0.125L0.125 3.875ZM2.12499 2.125L2.12499 3.875H3.87499L3.87499 2.125H2.12499ZM4.12499 2.125V3.875H5.87499V2.125H4.12499ZM6.12499 2.125V3.875H7.87499V2.125H6.12499ZM8.12499 2.125V3.875H9.87499V2.125H8.12499ZM10.125 2.125V3.875H11.875V2.125H10.125ZM12.125 2.125V3.875H13.875V2.125H12.125ZM14.125 2.125V3.875H15.875V2.125H14.125ZM15.875 4.125H14.125V5.875H15.875V4.125ZM15.875 6.125H14.125V7.875H15.875V6.125ZM15.875 8.125H14.125V9.875H15.875V8.125ZM15.875 10.125H14.125V11.875H15.875V10.125ZM15.875 12.125H14.125V13.875H15.875V12.125ZM15.875 14.125H14.125V15.875H15.875V14.125ZM15.875 16.125H14.125V17.875H15.875V16.125ZM13.875 17.875V16.125H12.125V17.875H13.875ZM11.875 17.875V16.125H10.125V17.875H11.875ZM9.87499 17.875V16.125H8.12499V17.875H9.87499ZM7.87499 17.875V16.125H6.12499V17.875H7.87499ZM5.87499 17.875V16.125H4.12499V17.875H5.87499ZM3.87499 17.875L3.87499 16.125H2.12499L2.12499 17.875H3.87499ZM2.12499 15.875H3.87499V14.125H2.12499V15.875ZM2.12499 13.875H3.87499L3.87499 12.125H2.12499L2.12499 13.875ZM2.12499 11.875H3.87499V10.125H2.12499V11.875ZM2.12499 9.875H3.87499V8.125H2.12499V9.875ZM2.12499 7.875H3.87499L3.87499 6.125H2.12499L2.12499 7.875ZM2.12499 5.875H3.87499V4.125H2.12499V5.875ZM4.12499 4.125L4.12499 5.875H5.87499L5.87499 4.125H4.12499ZM6.12499 4.125L6.12499 5.875H7.87499L7.87499 4.125H6.12499ZM8.12499 4.125V5.875H9.87499V4.125H8.12499ZM10.125 4.125V5.875H11.875V4.125H10.125ZM12.125 4.125V5.875H13.875V4.125H12.125ZM13.875 6.125H12.125V7.875H13.875V6.125ZM13.875 8.125H12.125V9.875H13.875V8.125ZM13.875 10.125H12.125V11.875H13.875V10.125ZM13.875 12.125H12.125V13.875H13.875V12.125ZM13.875 14.125H12.125V15.875H13.875V14.125ZM11.875 15.875V14.125H10.125V15.875H11.875ZM9.87499 15.875V14.125H8.12499V15.875H9.87499ZM7.87499 15.875L7.87499 14.125H6.12499L6.12499 15.875H7.87499ZM5.87499 15.875L5.87499 14.125H4.12499L4.12499 15.875H5.87499ZM4.12499 13.875H5.87499V12.125H4.12499V13.875ZM4.12499 11.875H5.87499V10.125H4.12499V11.875ZM4.12499 9.875H5.87499V8.125H4.12499V9.875ZM4.12499 7.875H5.87499V6.125H4.12499V7.875ZM6.12499 6.125V7.875H7.87499V6.125H6.12499ZM8.12499 6.125V7.875H9.87499V6.125H8.12499ZM10.125 6.125V7.875H11.875V6.125H10.125ZM11.875 8.125H10.125V9.875H11.875V8.125ZM11.875 10.125H10.125V11.875H11.875V10.125ZM11.875 12.125H10.125V13.875H11.875V12.125ZM9.87499 13.875V12.125H8.12499V13.875H9.87499ZM7.87499 13.875V12.125H6.12499V13.875H7.87499ZM6.12499 11.875H7.87499V10.125H6.12499V11.875ZM6.12499 9.875H7.87499V8.125H6.12499V9.875ZM8.12499 8.125V9.875H9.87499V8.125H8.12499ZM9.87499 10.125H8.12499V11.875H9.87499V10.125Z" fill="url(#paint1_linear_6_97)" fill-opacity="0.05"/>
|
||||
<g filter="url(#filter1_di_6_97)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.2306 7.25129C15.0975 7.48357 15.9885 6.96913 16.2208 6.10224C16.2693 5.92123 16.2852 5.73915 16.2722 5.56225C16.828 5.94013 17.1117 6.64112 16.9279 7.32699C16.6956 8.19387 15.8046 8.70832 14.9377 8.47604C14.2518 8.29226 13.7866 7.69611 13.7372 7.02583C13.8839 7.12556 14.0496 7.20279 14.2306 7.25129Z" fill="white"/>
|
||||
</g>
|
||||
<g filter="url(#filter2_di_6_97)">
|
||||
<path d="M5.30613 14.4778L5.18709 14.922L3.12695 14.37L3.24599 13.9257L5.30613 14.4778ZM4.33851 10.2786L3.23448 14.3988L2.68832 14.2525L3.79234 10.1322L4.33851 10.2786ZM7.67167 14.8295L8.30254 12.4751L8.82889 12.6161L8.00846 15.678L7.50757 15.5438L7.67167 14.8295ZM7.9436 14.2109L8.16301 14.2636C8.10842 14.4673 8.03617 14.6502 7.94627 14.8121C7.85877 14.9727 7.75092 15.1035 7.62272 15.2046C7.49452 15.3058 7.34441 15.3717 7.17239 15.4024C7.00087 15.4313 6.80569 15.4164 6.58685 15.3578C6.43781 15.3178 6.30685 15.2595 6.19396 15.1827C6.08296 15.1065 5.99526 15.0112 5.93085 14.8969C5.86645 14.7826 5.82918 14.6462 5.81906 14.4878C5.81081 14.3299 5.83399 14.1491 5.88859 13.9453L6.41785 11.9701L6.94138 12.1104L6.41059 14.0913C6.37369 14.229 6.3582 14.3472 6.36412 14.4458C6.37244 14.5431 6.39593 14.6252 6.4346 14.6922C6.47566 14.7578 6.52642 14.8098 6.58689 14.8482C6.64925 14.8872 6.71627 14.9163 6.78796 14.9355C7.01058 14.9951 7.19835 14.9999 7.35127 14.9499C7.5047 14.898 7.62917 14.807 7.72469 14.6769C7.8226 14.5454 7.89557 14.3901 7.9436 14.2109ZM9.97528 13.6239L9.33 16.0321L8.80648 15.8919L9.62691 12.8299L10.1221 12.9626L9.97528 13.6239ZM9.64679 14.3518L9.43117 14.2849C9.48917 14.076 9.57211 13.891 9.68 13.7298C9.78839 13.5668 9.91499 13.4339 10.0598 13.3311C10.2046 13.2284 10.3621 13.1614 10.5322 13.1301C10.7047 13.0975 10.8834 13.106 11.0683 13.1555C11.2193 13.1959 11.3495 13.2531 11.4591 13.327C11.5693 13.3989 11.6549 13.4906 11.716 13.602C11.7791 13.714 11.8143 13.8467 11.8216 14.0004C11.8295 14.1521 11.8064 14.3289 11.7523 14.5308L11.2147 16.5371L10.6883 16.3961L11.2275 14.3841C11.2704 14.2237 11.2812 14.0891 11.2598 13.9802C11.239 13.8695 11.1891 13.7803 11.1101 13.7127C11.0317 13.6431 10.9265 13.5906 10.7944 13.5553C10.6642 13.5204 10.538 13.5159 10.4158 13.5418C10.2955 13.5682 10.1838 13.6191 10.0807 13.6946C9.97944 13.7706 9.89045 13.8651 9.81369 13.9779C9.73933 14.0895 9.6837 14.2141 9.64679 14.3518ZM13.2653 17.1473C13.0521 17.0901 12.8684 17.0025 12.714 16.8843C12.5621 16.7647 12.4415 16.6222 12.3524 16.4568C12.2651 16.2919 12.2128 16.11 12.1953 15.9112C12.1778 15.7124 12.1978 15.5055 12.2555 15.2904L12.2873 15.1715C12.354 14.9225 12.4502 14.7107 12.5759 14.5361C12.702 14.3596 12.8465 14.2204 13.0093 14.1184C13.1721 14.0164 13.343 13.952 13.5221 13.9252C13.7011 13.8984 13.8765 13.908 14.0482 13.954C14.267 14.0126 14.4456 14.1009 14.5838 14.2188C14.724 14.3372 14.8268 14.478 14.8922 14.6411C14.9582 14.8024 14.9912 14.9811 14.9912 15.1772C14.9918 15.3715 14.9632 15.5761 14.9056 15.7912L14.8426 16.0261L12.5561 15.4134L12.6706 14.9861L14.4336 15.4585L14.4442 15.4189C14.4731 15.281 14.4802 15.1414 14.4655 15C14.4527 14.859 14.4056 14.7332 14.324 14.6223C14.2425 14.5115 14.1121 14.4321 13.9329 14.3841C13.8141 14.3522 13.6978 14.3484 13.5842 14.3725C13.471 14.3948 13.3655 14.4454 13.2675 14.5243C13.1695 14.6032 13.0818 14.708 13.0043 14.8389C12.9267 14.9698 12.8632 15.1277 12.8137 15.3126L12.7818 15.4314C12.7429 15.5767 12.726 15.7188 12.7313 15.8577C12.7389 15.9952 12.7685 16.1224 12.8201 16.2394C12.8735 16.3568 12.9486 16.4578 13.0452 16.5423C13.1438 16.6274 13.2647 16.6891 13.4081 16.7275C13.593 16.7771 13.7597 16.7813 13.9082 16.7402C14.0567 16.6991 14.196 16.6283 14.3261 16.5276L14.5756 16.8644C14.4827 16.9467 14.3732 17.0195 14.2471 17.0828C14.121 17.146 13.9758 17.186 13.8116 17.2026C13.6493 17.2198 13.4672 17.2013 13.2653 17.1473Z" fill="white"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_i_6_97" x="0.202041" y="-0.297958" width="20.0959" height="20.0959" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dx="0.5" dy="-0.5"/>
|
||||
<feGaussianBlur stdDeviation="0.25"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0"/>
|
||||
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_6_97"/>
|
||||
</filter>
|
||||
<filter id="filter1_di_6_97" x="11.7372" y="4.56225" width="6.24645" height="5.96956" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dx="-0.5" dy="0.5"/>
|
||||
<feGaussianBlur stdDeviation="0.75"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_6_97"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_6_97" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dx="0.25" dy="-0.25"/>
|
||||
<feGaussianBlur stdDeviation="0.375"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
|
||||
<feBlend mode="normal" in2="shape" result="effect2_innerShadow_6_97"/>
|
||||
</filter>
|
||||
<filter id="filter2_di_6_97" x="0.673153" y="9.13222" width="15.9027" height="10.3343" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dx="-0.5" dy="0.5"/>
|
||||
<feGaussianBlur stdDeviation="0.75"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_6_97"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_6_97" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dx="0.25" dy="-0.25"/>
|
||||
<feGaussianBlur stdDeviation="0.375"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
|
||||
<feBlend mode="normal" in2="shape" result="effect2_innerShadow_6_97"/>
|
||||
</filter>
|
||||
<linearGradient id="paint0_linear_6_97" x1="12.3431" y1="0.202042" x2="12.3431" y2="16.202" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#E168FF"/>
|
||||
<stop offset="1" stop-color="#C848E9"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_6_97" x1="10" y1="-0.125" x2="10" y2="20.125" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="white" stop-opacity="0.5"/>
|
||||
<stop offset="1" stop-color="white"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_6_97">
|
||||
<rect width="20" height="20" fill="white"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip1_6_97">
|
||||
<rect x="4.34315" y="0.202042" width="16" height="16" rx="1" transform="rotate(15 4.34315 0.202042)" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 12 KiB |
BIN
assets/logo/tilt.ico
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
assets/logo/tilt.png
Normal file
After Width: | Height: | Size: 172 KiB |
69
assets/logo/tilt_svg.svg
Normal file
|
@ -0,0 +1,69 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_6_77)">
|
||||
<g filter="url(#filter0_i_6_77)">
|
||||
<g clip-path="url(#clip1_6_77)">
|
||||
<rect x="4.34315" y="0.202042" width="16" height="16" rx="1" transform="rotate(15 4.34315 0.202042)" fill="url(#paint0_linear_6_77)"/>
|
||||
<g filter="url(#filter1_di_6_77)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.2306 7.25129C15.0975 7.48357 15.9885 6.96913 16.2208 6.10224C16.2693 5.92123 16.2852 5.73915 16.2722 5.56225C16.828 5.94013 17.1117 6.64112 16.9279 7.32699C16.6956 8.19387 15.8046 8.70832 14.9377 8.47604C14.2518 8.29226 13.7866 7.69611 13.7372 7.02583C13.8839 7.12556 14.0496 7.20279 14.2306 7.25129Z" fill="white"/>
|
||||
</g>
|
||||
<g filter="url(#filter2_di_6_77)">
|
||||
<path d="M5.30613 14.4778L5.18709 14.922L3.12695 14.37L3.24599 13.9257L5.30613 14.4778ZM4.33851 10.2786L3.23448 14.3988L2.68832 14.2525L3.79234 10.1322L4.33851 10.2786ZM7.67167 14.8295L8.30254 12.4751L8.82889 12.6161L8.00846 15.678L7.50757 15.5438L7.67167 14.8295ZM7.9436 14.2109L8.16301 14.2636C8.10842 14.4673 8.03617 14.6502 7.94627 14.8121C7.85877 14.9727 7.75092 15.1035 7.62272 15.2046C7.49452 15.3058 7.34441 15.3717 7.17239 15.4024C7.00087 15.4313 6.80569 15.4164 6.58685 15.3578C6.43781 15.3178 6.30685 15.2595 6.19396 15.1827C6.08296 15.1065 5.99526 15.0112 5.93085 14.8969C5.86645 14.7826 5.82918 14.6462 5.81906 14.4878C5.81081 14.3299 5.83399 14.1491 5.88859 13.9453L6.41785 11.9701L6.94138 12.1104L6.41059 14.0913C6.37369 14.229 6.3582 14.3472 6.36412 14.4458C6.37244 14.5431 6.39593 14.6252 6.4346 14.6922C6.47566 14.7578 6.52642 14.8098 6.58689 14.8482C6.64925 14.8872 6.71627 14.9163 6.78796 14.9355C7.01058 14.9951 7.19835 14.9999 7.35127 14.9499C7.5047 14.898 7.62917 14.807 7.72469 14.6769C7.8226 14.5454 7.89557 14.3901 7.9436 14.2109ZM9.97528 13.6239L9.33 16.0321L8.80648 15.8919L9.62691 12.8299L10.1221 12.9626L9.97528 13.6239ZM9.64679 14.3518L9.43117 14.2849C9.48917 14.076 9.57211 13.891 9.68 13.7298C9.78839 13.5668 9.91499 13.4339 10.0598 13.3311C10.2046 13.2284 10.3621 13.1614 10.5322 13.1301C10.7047 13.0975 10.8834 13.106 11.0683 13.1555C11.2193 13.1959 11.3495 13.2531 11.4591 13.327C11.5693 13.3989 11.6549 13.4906 11.716 13.602C11.7791 13.714 11.8143 13.8467 11.8216 14.0004C11.8295 14.1521 11.8064 14.3289 11.7523 14.5308L11.2147 16.5371L10.6883 16.3961L11.2275 14.3841C11.2704 14.2237 11.2812 14.0891 11.2598 13.9802C11.239 13.8695 11.1891 13.7803 11.1101 13.7127C11.0317 13.6431 10.9265 13.5906 10.7944 13.5553C10.6642 13.5204 10.538 13.5159 10.4158 13.5418C10.2955 13.5682 10.1838 13.6191 10.0807 13.6946C9.97944 13.7706 9.89045 13.8651 9.81369 13.9779C9.73933 14.0895 9.6837 14.2141 9.64679 14.3518ZM13.2653 17.1473C13.0521 17.0901 12.8684 17.0025 12.714 16.8843C12.5621 16.7647 12.4415 16.6222 12.3524 16.4568C12.2651 16.2919 12.2128 16.11 12.1953 15.9112C12.1778 15.7124 12.1978 15.5055 12.2555 15.2904L12.2873 15.1715C12.354 14.9225 12.4502 14.7107 12.5759 14.5361C12.702 14.3596 12.8465 14.2204 13.0093 14.1184C13.1721 14.0164 13.343 13.952 13.5221 13.9252C13.7011 13.8984 13.8765 13.908 14.0482 13.954C14.267 14.0126 14.4456 14.1009 14.5838 14.2188C14.724 14.3372 14.8268 14.478 14.8922 14.6411C14.9582 14.8024 14.9912 14.9811 14.9912 15.1772C14.9918 15.3715 14.9632 15.5761 14.9056 15.7912L14.8426 16.0261L12.5561 15.4134L12.6706 14.9861L14.4336 15.4585L14.4442 15.4189C14.4731 15.281 14.4802 15.1414 14.4655 15C14.4527 14.859 14.4056 14.7332 14.324 14.6223C14.2425 14.5115 14.1121 14.4321 13.9329 14.3841C13.8141 14.3522 13.6978 14.3484 13.5842 14.3725C13.471 14.3948 13.3655 14.4454 13.2675 14.5243C13.1695 14.6032 13.0818 14.708 13.0043 14.8389C12.9267 14.9698 12.8632 15.1277 12.8137 15.3126L12.7818 15.4314C12.7429 15.5767 12.726 15.7188 12.7313 15.8577C12.7389 15.9952 12.7685 16.1224 12.8201 16.2394C12.8735 16.3568 12.9486 16.4578 13.0452 16.5423C13.1438 16.6274 13.2647 16.6891 13.4081 16.7275C13.593 16.7771 13.7597 16.7813 13.9082 16.7402C14.0567 16.6991 14.196 16.6283 14.3261 16.5276L14.5756 16.8644C14.4827 16.9467 14.3732 17.0195 14.2471 17.0828C14.121 17.146 13.9758 17.186 13.8116 17.2026C13.6493 17.2198 13.4672 17.2013 13.2653 17.1473Z" fill="white"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_i_6_77" x="0.202041" y="-0.297958" width="20.0959" height="20.0959" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dx="0.5" dy="-0.5"/>
|
||||
<feGaussianBlur stdDeviation="0.25"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.05 0"/>
|
||||
<feBlend mode="normal" in2="shape" result="effect1_innerShadow_6_77"/>
|
||||
</filter>
|
||||
<filter id="filter1_di_6_77" x="11.7372" y="4.56224" width="6.24645" height="5.96957" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dx="-0.5" dy="0.5"/>
|
||||
<feGaussianBlur stdDeviation="0.75"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_6_77"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_6_77" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dx="0.25" dy="-0.25"/>
|
||||
<feGaussianBlur stdDeviation="0.375"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
|
||||
<feBlend mode="normal" in2="shape" result="effect2_innerShadow_6_77"/>
|
||||
</filter>
|
||||
<filter id="filter2_di_6_77" x="0.673153" y="9.13222" width="15.9027" height="10.3343" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dx="-0.5" dy="0.5"/>
|
||||
<feGaussianBlur stdDeviation="0.75"/>
|
||||
<feComposite in2="hardAlpha" operator="out"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_6_77"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_6_77" result="shape"/>
|
||||
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
|
||||
<feOffset dx="0.25" dy="-0.25"/>
|
||||
<feGaussianBlur stdDeviation="0.375"/>
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1"/>
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0"/>
|
||||
<feBlend mode="normal" in2="shape" result="effect2_innerShadow_6_77"/>
|
||||
</filter>
|
||||
<linearGradient id="paint0_linear_6_77" x1="12.3431" y1="0.202042" x2="12.3431" y2="16.202" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#E168FF"/>
|
||||
<stop offset="1" stop-color="#C848E9"/>
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_6_77">
|
||||
<rect width="20" height="20" fill="white"/>
|
||||
</clipPath>
|
||||
<clipPath id="clip1_6_77">
|
||||
<rect x="4.34315" y="0.202042" width="16" height="16" rx="1" transform="rotate(15 4.34315 0.202042)" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 7.5 KiB |
29
crates/lune-roblox/Cargo.toml
Normal file
|
@ -0,0 +1,29 @@
|
|||
[package]
|
||||
name = "lune-roblox"
|
||||
version = "0.1.4"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/lune-org/lune"
|
||||
description = "Roblox library for Lune"
|
||||
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
mlua = { version = "0.9.9", features = ["luau"] }
|
||||
|
||||
glam = "0.27"
|
||||
rand = "0.8"
|
||||
thiserror = "1.0"
|
||||
once_cell = "1.17"
|
||||
|
||||
rbx_binary = "1.0.0"
|
||||
rbx_dom_weak = "3.0.0"
|
||||
rbx_reflection = "5.0.0"
|
||||
rbx_reflection_database = "1.0.0"
|
||||
rbx_xml = "1.0.0"
|
||||
|
||||
lune-utils = { version = "0.1.3", path = "../lune-utils" }
|
|
@ -4,6 +4,15 @@ use rbx_dom_weak::types::{Variant as DomValue, VariantType as DomType};
|
|||
|
||||
use super::extension::DomValueExt;
|
||||
|
||||
/**
|
||||
Checks if the given name is a valid attribute name.
|
||||
|
||||
# Errors
|
||||
|
||||
- If the name starts with the prefix "RBX".
|
||||
- If the name contains any characters other than alphanumeric characters and underscore.
|
||||
- If the name is longer than 100 characters.
|
||||
*/
|
||||
pub fn ensure_valid_attribute_name(name: impl AsRef<str>) -> LuaResult<()> {
|
||||
let name = name.as_ref();
|
||||
if name.to_ascii_uppercase().starts_with("RBX") {
|
||||
|
@ -23,6 +32,13 @@ pub fn ensure_valid_attribute_name(name: impl AsRef<str>) -> LuaResult<()> {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Checks if the given value is a valid attribute value.
|
||||
|
||||
# Errors
|
||||
|
||||
- If the value is not a valid attribute type.
|
||||
*/
|
||||
pub fn ensure_valid_attribute_value(value: &DomValue) -> LuaResult<()> {
|
||||
let is_valid = matches!(
|
||||
value.ty(),
|
||||
|
@ -31,6 +47,7 @@ pub fn ensure_valid_attribute_value(value: &DomValue) -> LuaResult<()> {
|
|||
| DomType::CFrame
|
||||
| DomType::Color3
|
||||
| DomType::ColorSequence
|
||||
| DomType::EnumItem
|
||||
| DomType::Float32
|
||||
| DomType::Float64
|
||||
| DomType::Font
|
|
@ -51,7 +51,7 @@ impl<'lua> DomValueToLua<'lua> for LuaValue<'lua> {
|
|||
DomValue::Float32(n) => Ok(LuaValue::Number(*n as f64)),
|
||||
DomValue::String(s) => Ok(LuaValue::String(lua.create_string(s)?)),
|
||||
DomValue::BinaryString(s) => Ok(LuaValue::String(lua.create_string(s)?)),
|
||||
DomValue::Content(s) => Ok(LuaValue::String(
|
||||
DomValue::ContentId(s) => Ok(LuaValue::String(
|
||||
lua.create_string(AsRef::<str>::as_ref(s))?,
|
||||
)),
|
||||
|
||||
|
@ -65,8 +65,10 @@ impl<'lua> DomValueToLua<'lua> for LuaValue<'lua> {
|
|||
|
||||
// NOTE: Some values are either optional or default and we should handle
|
||||
// that properly here since the userdata conversion above will always fail
|
||||
DomValue::OptionalCFrame(None) => Ok(LuaValue::Nil),
|
||||
DomValue::PhysicalProperties(dom::PhysicalProperties::Default) => Ok(LuaValue::Nil),
|
||||
DomValue::OptionalCFrame(None)
|
||||
| DomValue::PhysicalProperties(dom::PhysicalProperties::Default) => {
|
||||
Ok(LuaValue::Nil)
|
||||
}
|
||||
|
||||
_ => Err(e),
|
||||
},
|
||||
|
@ -102,8 +104,8 @@ impl<'lua> LuaToDomValue<'lua> for LuaValue<'lua> {
|
|||
(LuaValue::String(s), DomType::BinaryString) => {
|
||||
Ok(DomValue::BinaryString(s.as_ref().into()))
|
||||
}
|
||||
(LuaValue::String(s), DomType::Content) => {
|
||||
Ok(DomValue::Content(s.to_str()?.to_string().into()))
|
||||
(LuaValue::String(s), DomType::ContentId) => {
|
||||
Ok(DomValue::ContentId(s.to_str()?.to_string().into()))
|
||||
}
|
||||
|
||||
// NOTE: Some values are either optional or default and we
|
||||
|
@ -198,6 +200,8 @@ impl<'lua> DomValueToLua<'lua> for LuaAnyUserData<'lua> {
|
|||
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),
|
||||
|
@ -254,7 +258,8 @@ impl<'lua> LuaToDomValue<'lua> for LuaAnyUserData<'lua> {
|
|||
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::Enum => userdata_to_dom!(self as EnumItem => dom::Enum),
|
||||
DomType::Content => userdata_to_dom!(self as Content => dom::Content),
|
||||
DomType::EnumItem => userdata_to_dom!(self as EnumItem => dom::EnumItem),
|
||||
DomType::Faces => userdata_to_dom!(self as Faces => dom::Faces),
|
||||
DomType::Font => userdata_to_dom!(self as Font => dom::Font),
|
||||
DomType::NumberRange => userdata_to_dom!(self as NumberRange => dom::NumberRange),
|
||||
|
@ -291,7 +296,7 @@ impl<'lua> LuaToDomValue<'lua> for LuaAnyUserData<'lua> {
|
|||
}
|
||||
|
||||
ty => {
|
||||
return Err(DomConversionError::ToDomValue {
|
||||
Err(DomConversionError::ToDomValue {
|
||||
to: ty.variant_name().unwrap_or("???"),
|
||||
from: "userdata",
|
||||
detail: Some("Type not supported".to_string()),
|
||||
|
@ -312,7 +317,7 @@ impl<'lua> LuaToDomValue<'lua> for LuaAnyUserData<'lua> {
|
|||
value if value.is::<CFrame>() => userdata_to_dom!(value as CFrame => dom::CFrame),
|
||||
value if value.is::<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::<Enum>() => userdata_to_dom!(value as EnumItem => dom::Enum),
|
||||
value if value.is::<EnumItem>() => userdata_to_dom!(value as EnumItem => dom::EnumItem),
|
||||
value if value.is::<Faces>() => userdata_to_dom!(value as Faces => dom::Faces),
|
||||
value if value.is::<Font>() => userdata_to_dom!(value as Font => dom::Font),
|
||||
value if value.is::<Instance>() => userdata_to_dom!(value as Instance => dom::Ref),
|
61
crates/lune-roblox/src/datatypes/extension.rs
Normal file
|
@ -0,0 +1,61 @@
|
|||
use super::*;
|
||||
|
||||
pub(crate) trait DomValueExt {
|
||||
fn variant_name(&self) -> Option<&'static str>;
|
||||
}
|
||||
|
||||
impl DomValueExt for DomType {
|
||||
fn variant_name(&self) -> Option<&'static str> {
|
||||
#[allow(clippy::enum_glob_use)]
|
||||
use DomType::*;
|
||||
Some(match self {
|
||||
Attributes => "Attributes",
|
||||
Axes => "Axes",
|
||||
BinaryString => "BinaryString",
|
||||
Bool => "Bool",
|
||||
BrickColor => "BrickColor",
|
||||
CFrame => "CFrame",
|
||||
Color3 => "Color3",
|
||||
Color3uint8 => "Color3uint8",
|
||||
ColorSequence => "ColorSequence",
|
||||
Content => "Content",
|
||||
ContentId => "ContentId",
|
||||
Enum => "Enum",
|
||||
EnumItem => "EnumItem",
|
||||
Faces => "Faces",
|
||||
Float32 => "Float32",
|
||||
Float64 => "Float64",
|
||||
Font => "Font",
|
||||
Int32 => "Int32",
|
||||
Int64 => "Int64",
|
||||
MaterialColors => "MaterialColors",
|
||||
NumberRange => "NumberRange",
|
||||
NumberSequence => "NumberSequence",
|
||||
PhysicalProperties => "PhysicalProperties",
|
||||
Ray => "Ray",
|
||||
Rect => "Rect",
|
||||
Ref => "Ref",
|
||||
Region3 => "Region3",
|
||||
Region3int16 => "Region3int16",
|
||||
SharedString => "SharedString",
|
||||
String => "String",
|
||||
Tags => "Tags",
|
||||
UDim => "UDim",
|
||||
UDim2 => "UDim2",
|
||||
UniqueId => "UniqueId",
|
||||
Vector2 => "Vector2",
|
||||
Vector2int16 => "Vector2int16",
|
||||
Vector3 => "Vector3",
|
||||
Vector3int16 => "Vector3int16",
|
||||
OptionalCFrame => "OptionalCFrame",
|
||||
SecurityCapabilities => "SecurityCapabilities",
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl DomValueExt for DomValue {
|
||||
fn variant_name(&self) -> Option<&'static str> {
|
||||
self.ty().variant_name()
|
||||
}
|
||||
}
|
|
@ -6,6 +6,8 @@ pub mod extension;
|
|||
pub mod result;
|
||||
pub mod types;
|
||||
|
||||
mod util;
|
||||
|
||||
use result::*;
|
||||
|
||||
pub use crate::shared::userdata::*;
|
|
@ -3,6 +3,10 @@ use core::fmt;
|
|||
use mlua::prelude::*;
|
||||
use rbx_dom_weak::types::Axes as DomAxes;
|
||||
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use crate::exports::LuaExportsTable;
|
||||
|
||||
use super::{super::*, EnumItem};
|
||||
|
||||
/**
|
||||
|
@ -17,53 +21,57 @@ pub struct Axes {
|
|||
pub(crate) z: bool,
|
||||
}
|
||||
|
||||
impl Axes {
|
||||
pub(crate) fn make_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> {
|
||||
datatype_table.set(
|
||||
"new",
|
||||
lua.create_function(|_, args: LuaMultiValue| {
|
||||
let mut x = false;
|
||||
let mut y = false;
|
||||
let mut z = false;
|
||||
let mut check = |e: &EnumItem| {
|
||||
if e.parent.desc.name == "Axis" {
|
||||
match &e.name {
|
||||
name if name == "X" => x = true,
|
||||
name if name == "Y" => y = true,
|
||||
name if name == "Z" => z = true,
|
||||
_ => {}
|
||||
}
|
||||
} else if e.parent.desc.name == "NormalId" {
|
||||
match &e.name {
|
||||
name if name == "Left" || name == "Right" => x = true,
|
||||
name if name == "Top" || name == "Bottom" => y = true,
|
||||
name if name == "Front" || name == "Back" => z = true,
|
||||
_ => {}
|
||||
}
|
||||
impl LuaExportsTable<'_> for Axes {
|
||||
const EXPORT_NAME: &'static str = "Axes";
|
||||
|
||||
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;
|
||||
|
||||
let mut check = |e: &EnumItem| {
|
||||
if e.parent.desc.name == "Axis" {
|
||||
match &e.name {
|
||||
name if name == "X" => x = true,
|
||||
name if name == "Y" => y = true,
|
||||
name if name == "Z" => z = true,
|
||||
_ => {}
|
||||
}
|
||||
};
|
||||
for (index, arg) in args.into_iter().enumerate() {
|
||||
if let LuaValue::UserData(u) = arg {
|
||||
if let Ok(e) = u.borrow::<EnumItem>() {
|
||||
check(&e);
|
||||
} else {
|
||||
return Err(LuaError::RuntimeError(format!(
|
||||
"Expected argument #{} to be an EnumItem, got userdata",
|
||||
index
|
||||
)));
|
||||
}
|
||||
} else {
|
||||
return Err(LuaError::RuntimeError(format!(
|
||||
"Expected argument #{} to be an EnumItem, got {}",
|
||||
index,
|
||||
arg.type_name()
|
||||
)));
|
||||
} else if e.parent.desc.name == "NormalId" {
|
||||
match &e.name {
|
||||
name if name == "Left" || name == "Right" => x = true,
|
||||
name if name == "Top" || name == "Bottom" => y = true,
|
||||
name if name == "Front" || name == "Back" => z = true,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(Axes { x, y, z })
|
||||
})?,
|
||||
)?;
|
||||
Ok(())
|
||||
};
|
||||
|
||||
for (index, arg) in args.into_iter().enumerate() {
|
||||
if let LuaValue::UserData(u) = arg {
|
||||
if let Ok(e) = u.borrow::<EnumItem>() {
|
||||
check(&e);
|
||||
} else {
|
||||
return Err(LuaError::RuntimeError(format!(
|
||||
"Expected argument #{index} to be an EnumItem, got userdata",
|
||||
)));
|
||||
}
|
||||
} else {
|
||||
return Err(LuaError::RuntimeError(format!(
|
||||
"Expected argument #{} to be an EnumItem, got {}",
|
||||
index,
|
||||
arg.type_name()
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Axes { x, y, z })
|
||||
};
|
||||
|
||||
TableBuilder::new(lua)?
|
||||
.with_function("new", axes_new)?
|
||||
.build_readonly()
|
||||
}
|
||||
}
|
||||
|
|
@ -4,12 +4,16 @@ use mlua::prelude::*;
|
|||
use rand::seq::SliceRandom;
|
||||
use rbx_dom_weak::types::BrickColor as DomBrickColor;
|
||||
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use crate::exports::LuaExportsTable;
|
||||
|
||||
use super::{super::*, Color3};
|
||||
|
||||
/**
|
||||
An implementation of the [BrickColor](https://create.roblox.com/docs/reference/engine/datatypes/BrickColor) Roblox datatype.
|
||||
|
||||
This implements all documented properties, methods & constructors of the BrickColor class as of March 2023.
|
||||
This implements all documented properties, methods & constructors of the `BrickColor` class as of March 2023.
|
||||
*/
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct BrickColor {
|
||||
|
@ -20,57 +24,58 @@ pub struct BrickColor {
|
|||
pub(crate) rgb: (u8, u8, u8),
|
||||
}
|
||||
|
||||
impl BrickColor {
|
||||
pub(crate) fn make_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> {
|
||||
impl LuaExportsTable<'_> for BrickColor {
|
||||
const EXPORT_NAME: &'static str = "BrickColor";
|
||||
|
||||
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
|
||||
type ArgsNumber = u16;
|
||||
type ArgsName = String;
|
||||
type ArgsRgb = (u8, u8, u8);
|
||||
type ArgsColor3<'lua> = LuaUserDataRef<'lua, Color3>;
|
||||
datatype_table.set(
|
||||
"new",
|
||||
lua.create_function(|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) {
|
||||
Ok(color_from_name(name))
|
||||
} else if let Ok((r, g, b)) = ArgsRgb::from_lua_multi(args.clone(), lua) {
|
||||
Ok(color_from_rgb(r, g, b))
|
||||
} else if let Ok(color) = ArgsColor3::from_lua_multi(args.clone(), lua) {
|
||||
Ok(Self::from(*color))
|
||||
} else {
|
||||
// FUTURE: Better error message here using given arg types
|
||||
Err(LuaError::RuntimeError(
|
||||
"Invalid arguments to constructor".to_string(),
|
||||
))
|
||||
}
|
||||
})?,
|
||||
)?;
|
||||
datatype_table.set(
|
||||
"palette",
|
||||
lua.create_function(|_, 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) {
|
||||
Ok(color_from_number(*number))
|
||||
} else {
|
||||
Err(LuaError::RuntimeError("Invalid index".to_string()))
|
||||
}
|
||||
})?,
|
||||
)?;
|
||||
datatype_table.set(
|
||||
"random",
|
||||
lua.create_function(|_, ()| {
|
||||
let number = BRICK_COLOR_PALETTE.choose(&mut rand::thread_rng());
|
||||
Ok(color_from_number(*number.unwrap()))
|
||||
})?,
|
||||
)?;
|
||||
|
||||
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) {
|
||||
Ok(color_from_name(name))
|
||||
} else if let Ok((r, g, b)) = ArgsRgb::from_lua_multi(args.clone(), lua) {
|
||||
Ok(color_from_rgb(r, g, b))
|
||||
} else if let Ok(color) = ArgsColor3::from_lua_multi(args.clone(), lua) {
|
||||
Ok(Self::from(*color))
|
||||
} else {
|
||||
// FUTURE: Better error message here using given arg types
|
||||
Err(LuaError::RuntimeError(
|
||||
"Invalid arguments to constructor".to_string(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
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) {
|
||||
Ok(color_from_number(*number))
|
||||
} else {
|
||||
Err(LuaError::RuntimeError("Invalid index".to_string()))
|
||||
}
|
||||
};
|
||||
|
||||
let brick_color_random = |_, ()| {
|
||||
let number = BRICK_COLOR_PALETTE.choose(&mut rand::thread_rng());
|
||||
Ok(color_from_number(*number.unwrap()))
|
||||
};
|
||||
|
||||
let mut builder = TableBuilder::new(lua)?
|
||||
.with_function("new", brick_color_new)?
|
||||
.with_function("palette", brick_color_palette)?
|
||||
.with_function("random", brick_color_random)?;
|
||||
|
||||
for (name, number) in BRICK_COLOR_CONSTRUCTORS {
|
||||
datatype_table.set(
|
||||
*name,
|
||||
lua.create_function(|_, ()| Ok(color_from_number(*number)))?,
|
||||
)?;
|
||||
let f = |_, ()| Ok(color_from_number(*number));
|
||||
builder = builder.with_function(*name, f)?;
|
||||
}
|
||||
Ok(())
|
||||
|
||||
builder.build_readonly()
|
||||
}
|
||||
}
|
||||
|
493
crates/lune-roblox/src/datatypes/types/cframe.rs
Normal file
|
@ -0,0 +1,493 @@
|
|||
#![allow(clippy::items_after_statements)]
|
||||
|
||||
use core::fmt;
|
||||
use std::ops;
|
||||
|
||||
use glam::{EulerRot, Mat3, Mat4, Quat, Vec3};
|
||||
use mlua::{prelude::*, Variadic};
|
||||
use rbx_dom_weak::types::{CFrame as DomCFrame, Matrix3 as DomMatrix3, Vector3 as DomVector3};
|
||||
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use crate::exports::LuaExportsTable;
|
||||
|
||||
use super::{super::*, Vector3};
|
||||
|
||||
/**
|
||||
An implementation of the [CFrame](https://create.roblox.com/docs/reference/engine/datatypes/CFrame)
|
||||
Roblox datatype, backed by [`glam::Mat4`].
|
||||
|
||||
This implements all documented properties, methods &
|
||||
constructors of the `CFrame` class as of March 2023.
|
||||
*/
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct CFrame(pub Mat4);
|
||||
|
||||
impl CFrame {
|
||||
pub const IDENTITY: Self = Self(Mat4::IDENTITY);
|
||||
|
||||
fn position(&self) -> Vec3 {
|
||||
self.0.w_axis.truncate()
|
||||
}
|
||||
|
||||
fn orientation(&self) -> Mat3 {
|
||||
Mat3::from_cols(
|
||||
self.0.x_axis.truncate(),
|
||||
self.0.y_axis.truncate(),
|
||||
self.0.z_axis.truncate(),
|
||||
)
|
||||
}
|
||||
|
||||
fn inverse(&self) -> Self {
|
||||
Self(self.0.inverse())
|
||||
}
|
||||
}
|
||||
|
||||
impl LuaExportsTable<'_> for CFrame {
|
||||
const EXPORT_NAME: &'static str = "CFrame";
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
|
||||
let cframe_angles = |_, (rx, ry, rz): (f32, f32, f32)| {
|
||||
Ok(CFrame(Mat4::from_euler(EulerRot::XYZ, rx, ry, rz)))
|
||||
};
|
||||
|
||||
let cframe_from_axis_angle =
|
||||
|_, (v, r): (LuaUserDataRef<Vector3>, f32)| Ok(CFrame(Mat4::from_axis_angle(v.0, r)));
|
||||
|
||||
let cframe_from_euler_angles_xyz = |_, (rx, ry, rz): (f32, f32, f32)| {
|
||||
Ok(CFrame(Mat4::from_euler(EulerRot::XYZ, rx, ry, rz)))
|
||||
};
|
||||
|
||||
let cframe_from_euler_angles_yxz = |_, (rx, ry, rz): (f32, f32, f32)| {
|
||||
Ok(CFrame(Mat4::from_euler(EulerRot::YXZ, ry, rx, rz)))
|
||||
};
|
||||
|
||||
let cframe_from_matrix = |_,
|
||||
(pos, rx, ry, rz): (
|
||||
LuaUserDataRef<Vector3>,
|
||||
LuaUserDataRef<Vector3>,
|
||||
LuaUserDataRef<Vector3>,
|
||||
Option<LuaUserDataRef<Vector3>>,
|
||||
)| {
|
||||
Ok(CFrame(Mat4::from_cols(
|
||||
rx.0.extend(0.0),
|
||||
ry.0.extend(0.0),
|
||||
rz.map_or_else(|| rx.0.cross(ry.0).normalize(), |r| r.0)
|
||||
.extend(0.0),
|
||||
pos.0.extend(1.0),
|
||||
)))
|
||||
};
|
||||
|
||||
let cframe_from_orientation = |_, (rx, ry, rz): (f32, f32, f32)| {
|
||||
Ok(CFrame(Mat4::from_euler(EulerRot::YXZ, ry, rx, rz)))
|
||||
};
|
||||
|
||||
let cframe_look_at = |_,
|
||||
(from, to, up): (
|
||||
LuaUserDataRef<Vector3>,
|
||||
LuaUserDataRef<Vector3>,
|
||||
Option<LuaUserDataRef<Vector3>>,
|
||||
)| {
|
||||
Ok(CFrame(look_at(
|
||||
from.0,
|
||||
to.0,
|
||||
up.as_deref().unwrap_or(&Vector3(Vec3::Y)).0,
|
||||
)))
|
||||
};
|
||||
|
||||
// Dynamic args constructor
|
||||
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, args: LuaMultiValue| match args.len() {
|
||||
0 => Ok(CFrame(Mat4::IDENTITY)),
|
||||
|
||||
1 => match ArgsPos::from_lua_multi(args, lua) {
|
||||
Ok(pos) => Ok(CFrame(Mat4::from_translation(pos.0))),
|
||||
Err(err) => Err(err),
|
||||
},
|
||||
|
||||
3 => {
|
||||
if let Ok((from, to, up)) = ArgsLook::from_lua_multi(args.clone(), lua) {
|
||||
Ok(CFrame(look_at(
|
||||
from.0,
|
||||
to.0,
|
||||
up.as_deref().unwrap_or(&Vector3(Vec3::Y)).0,
|
||||
)))
|
||||
} else if let Ok((x, y, z)) = ArgsPosXYZ::from_lua_multi(args, lua) {
|
||||
Ok(CFrame(Mat4::from_translation(Vec3::new(x, y, z))))
|
||||
} else {
|
||||
// TODO: Make this error message better
|
||||
Err(LuaError::RuntimeError(
|
||||
"Invalid arguments to constructor".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
7 => match ArgsPosXYZQuat::from_lua_multi(args, lua) {
|
||||
Ok((x, y, z, qx, qy, qz, qw)) => Ok(CFrame(Mat4::from_rotation_translation(
|
||||
Quat::from_array([qx, qy, qz, qw]),
|
||||
Vec3::new(x, y, z),
|
||||
))),
|
||||
Err(err) => Err(err),
|
||||
},
|
||||
|
||||
12 => match ArgsMatrix::from_lua_multi(args, lua) {
|
||||
Ok((x, y, z, r00, r01, r02, r10, r11, r12, r20, r21, r22)) => {
|
||||
Ok(CFrame(Mat4::from_cols_array_2d(&[
|
||||
[r00, r10, r20, 0.0],
|
||||
[r01, r11, r21, 0.0],
|
||||
[r02, r12, r22, 0.0],
|
||||
[x, y, z, 1.0],
|
||||
])))
|
||||
}
|
||||
Err(err) => Err(err),
|
||||
},
|
||||
|
||||
_ => Err(LuaError::RuntimeError(format!(
|
||||
"Invalid number of arguments: expected 0, 1, 3, 7, or 12, got {}",
|
||||
args.len()
|
||||
))),
|
||||
};
|
||||
|
||||
TableBuilder::new(lua)?
|
||||
.with_function("Angles", cframe_angles)?
|
||||
.with_value("identity", CFrame(Mat4::IDENTITY))?
|
||||
.with_function("fromAxisAngle", cframe_from_axis_angle)?
|
||||
.with_function("fromEulerAnglesXYZ", cframe_from_euler_angles_xyz)?
|
||||
.with_function("fromEulerAnglesYXZ", cframe_from_euler_angles_yxz)?
|
||||
.with_function("fromMatrix", cframe_from_matrix)?
|
||||
.with_function("fromOrientation", cframe_from_orientation)?
|
||||
.with_function("lookAt", cframe_look_at)?
|
||||
.with_function("new", cframe_new)?
|
||||
.build_readonly()
|
||||
}
|
||||
}
|
||||
|
||||
impl LuaUserData for CFrame {
|
||||
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(
|
||||
this.0.x_axis,
|
||||
this.0.y_axis,
|
||||
this.0.z_axis,
|
||||
Vec3::ZERO.extend(1.0),
|
||||
)))
|
||||
});
|
||||
fields.add_field_method_get("X", |_, this| Ok(this.position().x));
|
||||
fields.add_field_method_get("Y", |_, this| Ok(this.position().y));
|
||||
fields.add_field_method_get("Z", |_, this| Ok(this.position().z));
|
||||
fields.add_field_method_get("XVector", |_, this| Ok(Vector3(this.orientation().x_axis)));
|
||||
fields.add_field_method_get("YVector", |_, this| Ok(Vector3(this.orientation().y_axis)));
|
||||
fields.add_field_method_get("ZVector", |_, this| Ok(Vector3(this.orientation().z_axis)));
|
||||
fields.add_field_method_get("RightVector", |_, this| {
|
||||
Ok(Vector3(this.orientation().x_axis))
|
||||
});
|
||||
fields.add_field_method_get("UpVector", |_, this| Ok(Vector3(this.orientation().y_axis)));
|
||||
fields.add_field_method_get("LookVector", |_, this| {
|
||||
Ok(Vector3(-this.orientation().z_axis))
|
||||
});
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
// Methods
|
||||
methods.add_method("Inverse", |_, this, ()| Ok(this.inverse()));
|
||||
methods.add_method(
|
||||
"Lerp",
|
||||
|_, this, (goal, alpha): (LuaUserDataRef<CFrame>, f32)| {
|
||||
let quat_this = Quat::from_mat4(&this.0);
|
||||
let quat_goal = Quat::from_mat4(&goal.0);
|
||||
let translation = this
|
||||
.0
|
||||
.w_axis
|
||||
.truncate()
|
||||
.lerp(goal.0.w_axis.truncate(), alpha);
|
||||
let rotation = quat_this.slerp(quat_goal, alpha);
|
||||
Ok(CFrame(Mat4::from_rotation_translation(
|
||||
rotation,
|
||||
translation,
|
||||
)))
|
||||
},
|
||||
);
|
||||
methods.add_method("Orthonormalize", |_, this, ()| {
|
||||
let rotation = Quat::from_mat4(&this.0);
|
||||
let translation = this.0.w_axis.truncate();
|
||||
Ok(CFrame(Mat4::from_rotation_translation(
|
||||
rotation.normalize(),
|
||||
translation,
|
||||
)))
|
||||
});
|
||||
methods.add_method(
|
||||
"ToWorldSpace",
|
||||
|_, this, rhs: Variadic<LuaUserDataRef<CFrame>>| {
|
||||
Ok(rhs
|
||||
.into_iter()
|
||||
.map(|cf| *this * *cf)
|
||||
.collect::<Variadic<_>>())
|
||||
},
|
||||
);
|
||||
methods.add_method(
|
||||
"ToObjectSpace",
|
||||
|_, this, rhs: Variadic<LuaUserDataRef<CFrame>>| {
|
||||
let inverse = this.inverse();
|
||||
Ok(rhs
|
||||
.into_iter()
|
||||
.map(|cf| inverse * *cf)
|
||||
.collect::<Variadic<_>>())
|
||||
},
|
||||
);
|
||||
methods.add_method(
|
||||
"PointToWorldSpace",
|
||||
|_, this, rhs: Variadic<LuaUserDataRef<Vector3>>| {
|
||||
Ok(rhs
|
||||
.into_iter()
|
||||
.map(|v3| *this * *v3)
|
||||
.collect::<Variadic<_>>())
|
||||
},
|
||||
);
|
||||
methods.add_method(
|
||||
"PointToObjectSpace",
|
||||
|_, this, rhs: Variadic<LuaUserDataRef<Vector3>>| {
|
||||
let inverse = this.inverse();
|
||||
Ok(rhs
|
||||
.into_iter()
|
||||
.map(|v3| inverse * *v3)
|
||||
.collect::<Variadic<_>>())
|
||||
},
|
||||
);
|
||||
methods.add_method(
|
||||
"VectorToWorldSpace",
|
||||
|_, this, rhs: Variadic<LuaUserDataRef<Vector3>>| {
|
||||
let result = *this - Vector3(this.position());
|
||||
Ok(rhs
|
||||
.into_iter()
|
||||
.map(|v3| result * *v3)
|
||||
.collect::<Variadic<_>>())
|
||||
},
|
||||
);
|
||||
methods.add_method(
|
||||
"VectorToObjectSpace",
|
||||
|_, this, rhs: Variadic<LuaUserDataRef<Vector3>>| {
|
||||
let inverse = this.inverse();
|
||||
let result = inverse - Vector3(inverse.position());
|
||||
Ok(rhs
|
||||
.into_iter()
|
||||
.map(|v3| result * *v3)
|
||||
.collect::<Variadic<_>>())
|
||||
},
|
||||
);
|
||||
#[rustfmt::skip]
|
||||
methods.add_method("GetComponents", |_, this, ()| {
|
||||
let pos = this.position();
|
||||
let transposed = this.orientation().transpose();
|
||||
Ok((
|
||||
pos.x, pos.y, pos.z,
|
||||
transposed.x_axis.x, transposed.x_axis.y, transposed.x_axis.z,
|
||||
transposed.y_axis.x, transposed.y_axis.y, transposed.y_axis.z,
|
||||
transposed.z_axis.x, transposed.z_axis.y, transposed.z_axis.z,
|
||||
))
|
||||
});
|
||||
methods.add_method("ToEulerAnglesXYZ", |_, this, ()| {
|
||||
Ok(Quat::from_mat4(&this.0).to_euler(EulerRot::XYZ))
|
||||
});
|
||||
methods.add_method("ToEulerAnglesYXZ", |_, this, ()| {
|
||||
let (ry, rx, rz) = Quat::from_mat4(&this.0).to_euler(EulerRot::YXZ);
|
||||
Ok((rx, ry, rz))
|
||||
});
|
||||
methods.add_method("ToOrientation", |_, this, ()| {
|
||||
let (ry, rx, rz) = Quat::from_mat4(&this.0).to_euler(EulerRot::YXZ);
|
||||
Ok((rx, ry, rz))
|
||||
});
|
||||
methods.add_method("ToAxisAngle", |_, this, ()| {
|
||||
let (axis, angle) = Quat::from_mat4(&this.0).to_axis_angle();
|
||||
Ok((Vector3(axis), angle))
|
||||
});
|
||||
// Metamethods
|
||||
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
||||
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
||||
methods.add_meta_method(LuaMetaMethod::Mul, |lua, this, rhs: LuaValue| {
|
||||
if let LuaValue::UserData(ud) = &rhs {
|
||||
if let Ok(cf) = ud.borrow::<CFrame>() {
|
||||
return lua.create_userdata(*this * *cf);
|
||||
} else if let Ok(vec) = ud.borrow::<Vector3>() {
|
||||
return lua.create_userdata(*this * *vec);
|
||||
}
|
||||
};
|
||||
Err(LuaError::FromLuaConversionError {
|
||||
from: rhs.type_name(),
|
||||
to: "userdata",
|
||||
message: Some(format!(
|
||||
"Expected CFrame or Vector3, got {}",
|
||||
rhs.type_name()
|
||||
)),
|
||||
})
|
||||
});
|
||||
methods.add_meta_method(
|
||||
LuaMetaMethod::Add,
|
||||
|_, this, vec: LuaUserDataRef<Vector3>| Ok(*this + *vec),
|
||||
);
|
||||
methods.add_meta_method(
|
||||
LuaMetaMethod::Sub,
|
||||
|_, this, vec: LuaUserDataRef<Vector3>| Ok(*this - *vec),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for CFrame {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let pos = self.position();
|
||||
let transposed = self.orientation().transpose();
|
||||
write!(
|
||||
f,
|
||||
"{}, {}, {}, {}",
|
||||
Vector3(pos),
|
||||
Vector3(transposed.x_axis),
|
||||
Vector3(transposed.y_axis),
|
||||
Vector3(transposed.z_axis)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Mul for CFrame {
|
||||
type Output = Self;
|
||||
fn mul(self, rhs: Self) -> Self::Output {
|
||||
CFrame(self.0 * rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Mul<Vector3> for CFrame {
|
||||
type Output = Vector3;
|
||||
fn mul(self, rhs: Vector3) -> Self::Output {
|
||||
Vector3(self.0.project_point3(rhs.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Add<Vector3> for CFrame {
|
||||
type Output = Self;
|
||||
fn add(self, rhs: Vector3) -> Self::Output {
|
||||
CFrame(Mat4::from_cols(
|
||||
self.0.x_axis,
|
||||
self.0.y_axis,
|
||||
self.0.z_axis,
|
||||
self.0.w_axis + rhs.0.extend(0.0),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Sub<Vector3> for CFrame {
|
||||
type Output = Self;
|
||||
fn sub(self, rhs: Vector3) -> Self::Output {
|
||||
CFrame(Mat4::from_cols(
|
||||
self.0.x_axis,
|
||||
self.0.y_axis,
|
||||
self.0.z_axis,
|
||||
self.0.w_axis - rhs.0.extend(0.0),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DomCFrame> for CFrame {
|
||||
fn from(v: DomCFrame) -> Self {
|
||||
let transposed = v.orientation.transpose();
|
||||
CFrame(Mat4::from_cols(
|
||||
Vector3::from(transposed.x).0.extend(0.0),
|
||||
Vector3::from(transposed.y).0.extend(0.0),
|
||||
Vector3::from(transposed.z).0.extend(0.0),
|
||||
Vector3::from(v.position).0.extend(1.0),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CFrame> for DomCFrame {
|
||||
fn from(v: CFrame) -> Self {
|
||||
let transposed = v.orientation().transpose();
|
||||
DomCFrame {
|
||||
position: DomVector3::from(Vector3(v.position())),
|
||||
orientation: DomMatrix3::new(
|
||||
DomVector3::from(Vector3(transposed.x_axis)),
|
||||
DomVector3::from(Vector3(transposed.y_axis)),
|
||||
DomVector3::from(Vector3(transposed.z_axis)),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a matrix at the position `from`, looking towards `to`.
|
||||
|
||||
[`glam`] does provide functions such as [`look_at_lh`], [`look_at_rh`] and more but
|
||||
they all create view matrices for camera transforms which is not what we want here.
|
||||
*/
|
||||
fn look_at(from: Vec3, to: Vec3, up: Vec3) -> Mat4 {
|
||||
let dir = (to - from).normalize();
|
||||
let xaxis = up.cross(dir).normalize();
|
||||
let yaxis = dir.cross(xaxis).normalize();
|
||||
|
||||
Mat4::from_cols(
|
||||
Vec3::new(xaxis.x, yaxis.x, dir.x).extend(0.0),
|
||||
Vec3::new(xaxis.y, yaxis.y, dir.y).extend(0.0),
|
||||
Vec3::new(xaxis.z, yaxis.z, dir.z).extend(0.0),
|
||||
from.extend(1.0),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod cframe_test {
|
||||
use glam::{Mat4, Vec3};
|
||||
use rbx_dom_weak::types::{CFrame as DomCFrame, Matrix3 as DomMatrix3, Vector3 as DomVector3};
|
||||
|
||||
use super::CFrame;
|
||||
|
||||
#[test]
|
||||
fn dom_cframe_from_cframe() {
|
||||
let dom_cframe = DomCFrame::new(
|
||||
DomVector3::new(1.0, 2.0, 3.0),
|
||||
DomMatrix3::new(
|
||||
DomVector3::new(1.0, 2.0, 3.0),
|
||||
DomVector3::new(1.0, 2.0, 3.0),
|
||||
DomVector3::new(1.0, 2.0, 3.0),
|
||||
),
|
||||
);
|
||||
|
||||
let cframe = CFrame(Mat4::from_cols(
|
||||
Vec3::new(1.0, 1.0, 1.0).extend(0.0),
|
||||
Vec3::new(2.0, 2.0, 2.0).extend(0.0),
|
||||
Vec3::new(3.0, 3.0, 3.0).extend(0.0),
|
||||
Vec3::new(1.0, 2.0, 3.0).extend(1.0),
|
||||
));
|
||||
|
||||
assert_eq!(CFrame::from(dom_cframe), cframe);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cframe_from_dom_cframe() {
|
||||
let cframe = CFrame(Mat4::from_cols(
|
||||
Vec3::new(1.0, 2.0, 3.0).extend(0.0),
|
||||
Vec3::new(1.0, 2.0, 3.0).extend(0.0),
|
||||
Vec3::new(1.0, 2.0, 3.0).extend(0.0),
|
||||
Vec3::new(1.0, 2.0, 3.0).extend(1.0),
|
||||
));
|
||||
|
||||
let dom_cframe = DomCFrame::new(
|
||||
DomVector3::new(1.0, 2.0, 3.0),
|
||||
DomMatrix3::new(
|
||||
DomVector3::new(1.0, 1.0, 1.0),
|
||||
DomVector3::new(2.0, 2.0, 2.0),
|
||||
DomVector3::new(3.0, 3.0, 3.0),
|
||||
),
|
||||
);
|
||||
|
||||
assert_eq!(DomCFrame::from(cframe), dom_cframe);
|
||||
}
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
#![allow(clippy::many_single_char_names)]
|
||||
|
||||
use core::fmt;
|
||||
use std::ops;
|
||||
|
||||
|
@ -5,6 +7,10 @@ use glam::Vec3;
|
|||
use mlua::prelude::*;
|
||||
use rbx_dom_weak::types::{Color3 as DomColor3, Color3uint8 as DomColor3uint8};
|
||||
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use crate::exports::LuaExportsTable;
|
||||
|
||||
use super::super::*;
|
||||
|
||||
/**
|
||||
|
@ -22,88 +28,100 @@ pub struct Color3 {
|
|||
pub(crate) b: f32,
|
||||
}
|
||||
|
||||
impl Color3 {
|
||||
pub(crate) fn make_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> {
|
||||
datatype_table.set(
|
||||
"new",
|
||||
lua.create_function(|_, (r, g, b): (Option<f32>, Option<f32>, Option<f32>)| {
|
||||
Ok(Color3 {
|
||||
r: r.unwrap_or_default(),
|
||||
g: g.unwrap_or_default(),
|
||||
b: b.unwrap_or_default(),
|
||||
})
|
||||
})?,
|
||||
)?;
|
||||
datatype_table.set(
|
||||
"fromRGB",
|
||||
lua.create_function(|_, (r, g, b): (Option<u8>, Option<u8>, Option<u8>)| {
|
||||
Ok(Color3 {
|
||||
r: (r.unwrap_or_default() as f32) / 255f32,
|
||||
g: (g.unwrap_or_default() as f32) / 255f32,
|
||||
b: (b.unwrap_or_default() as f32) / 255f32,
|
||||
})
|
||||
})?,
|
||||
)?;
|
||||
datatype_table.set(
|
||||
"fromHSV",
|
||||
lua.create_function(|_, (h, s, v): (f32, f32, f32)| {
|
||||
// https://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c
|
||||
let i = (h * 6.0).floor();
|
||||
let f = h * 6.0 - i;
|
||||
let p = v * (1.0 - s);
|
||||
let q = v * (1.0 - f * s);
|
||||
let t = v * (1.0 - (1.0 - f) * s);
|
||||
impl LuaExportsTable<'_> for Color3 {
|
||||
const EXPORT_NAME: &'static str = "Color3";
|
||||
|
||||
let (r, g, b) = match (i % 6.0) as u8 {
|
||||
0 => (v, t, p),
|
||||
1 => (q, v, p),
|
||||
2 => (p, v, t),
|
||||
3 => (p, q, v),
|
||||
4 => (t, p, v),
|
||||
5 => (v, p, q),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
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,
|
||||
b: (b.unwrap_or_default() as f32) / 255f32,
|
||||
})
|
||||
};
|
||||
|
||||
Ok(Color3 { r, g, b })
|
||||
})?,
|
||||
)?;
|
||||
datatype_table.set(
|
||||
"fromHex",
|
||||
lua.create_function(|_, hex: String| {
|
||||
let trimmed = hex.trim_start_matches('#').to_ascii_uppercase();
|
||||
let chars = if trimmed.len() == 3 {
|
||||
(
|
||||
u8::from_str_radix(&trimmed[..1].repeat(2), 16),
|
||||
u8::from_str_radix(&trimmed[1..2].repeat(2), 16),
|
||||
u8::from_str_radix(&trimmed[2..3].repeat(2), 16),
|
||||
)
|
||||
} else if trimmed.len() == 6 {
|
||||
(
|
||||
u8::from_str_radix(&trimmed[..2], 16),
|
||||
u8::from_str_radix(&trimmed[2..4], 16),
|
||||
u8::from_str_radix(&trimmed[4..6], 16),
|
||||
)
|
||||
} else {
|
||||
return Err(LuaError::RuntimeError(format!(
|
||||
"Hex color string must be 3 or 6 characters long, got {} character{}",
|
||||
trimmed.len(),
|
||||
if trimmed.len() == 1 { "" } else { "s" }
|
||||
)));
|
||||
};
|
||||
match chars {
|
||||
(Ok(r), Ok(g), Ok(b)) => Ok(Color3 {
|
||||
r: (r as f32) / 255f32,
|
||||
g: (g as f32) / 255f32,
|
||||
b: (b as f32) / 255f32,
|
||||
}),
|
||||
_ => Err(LuaError::RuntimeError(format!(
|
||||
"Hex color string '{}' contains invalid character",
|
||||
trimmed
|
||||
))),
|
||||
}
|
||||
})?,
|
||||
)?;
|
||||
Ok(())
|
||||
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;
|
||||
let p = v * (1.0 - s);
|
||||
let q = v * (1.0 - f * s);
|
||||
let t = v * (1.0 - (1.0 - f) * s);
|
||||
|
||||
let (r, g, b) = match (i % 6.0) as u8 {
|
||||
0 => (v, t, p),
|
||||
1 => (q, v, p),
|
||||
2 => (p, v, t),
|
||||
3 => (p, q, v),
|
||||
4 => (t, p, v),
|
||||
5 => (v, p, q),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
Ok(Color3 { r, g, b })
|
||||
};
|
||||
|
||||
let color3_from_hex = |_, hex: String| {
|
||||
let trimmed = hex.trim_start_matches('#').to_ascii_uppercase();
|
||||
let chars = if trimmed.len() == 3 {
|
||||
(
|
||||
u8::from_str_radix(&trimmed[..1].repeat(2), 16),
|
||||
u8::from_str_radix(&trimmed[1..2].repeat(2), 16),
|
||||
u8::from_str_radix(&trimmed[2..3].repeat(2), 16),
|
||||
)
|
||||
} else if trimmed.len() == 6 {
|
||||
(
|
||||
u8::from_str_radix(&trimmed[..2], 16),
|
||||
u8::from_str_radix(&trimmed[2..4], 16),
|
||||
u8::from_str_radix(&trimmed[4..6], 16),
|
||||
)
|
||||
} else {
|
||||
return Err(LuaError::RuntimeError(format!(
|
||||
"Hex color string must be 3 or 6 characters long, got {} character{}",
|
||||
trimmed.len(),
|
||||
if trimmed.len() == 1 { "" } else { "s" }
|
||||
)));
|
||||
};
|
||||
match chars {
|
||||
(Ok(r), Ok(g), Ok(b)) => Ok(Color3 {
|
||||
r: (r as f32) / 255f32,
|
||||
g: (g as f32) / 255f32,
|
||||
b: (b as f32) / 255f32,
|
||||
}),
|
||||
_ => Err(LuaError::RuntimeError(format!(
|
||||
"Hex color string '{trimmed}' contains invalid character",
|
||||
))),
|
||||
}
|
||||
};
|
||||
|
||||
let color3_new = |_, (r, g, b): (Option<f32>, Option<f32>, Option<f32>)| {
|
||||
Ok(Color3 {
|
||||
r: r.unwrap_or_default(),
|
||||
g: g.unwrap_or_default(),
|
||||
b: b.unwrap_or_default(),
|
||||
})
|
||||
};
|
||||
|
||||
TableBuilder::new(lua)?
|
||||
.with_function("fromRGB", color3_from_rgb)?
|
||||
.with_function("fromHSV", color3_from_hsv)?
|
||||
.with_function("fromHex", color3_from_hex)?
|
||||
.with_function("new", color3_new)?
|
||||
.build_readonly()
|
||||
}
|
||||
}
|
||||
|
||||
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",
|
||||
message: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -136,6 +154,7 @@ impl LuaUserData for Color3 {
|
|||
let max = r.max(g).max(b);
|
||||
let diff = max - min;
|
||||
|
||||
#[allow(clippy::float_cmp)]
|
||||
let hue = (match max {
|
||||
max if max == min => 0.0,
|
||||
max if max == r => (g - b) / diff + (if g < b { 6.0 } else { 0.0 }),
|
||||
|
@ -286,20 +305,12 @@ impl From<Color3> for DomColor3 {
|
|||
|
||||
impl From<DomColor3uint8> for Color3 {
|
||||
fn from(v: DomColor3uint8) -> Self {
|
||||
Self {
|
||||
r: (v.r as f32) / 255f32,
|
||||
g: (v.g as f32) / 255f32,
|
||||
b: (v.b as f32) / 255f32,
|
||||
}
|
||||
Color3::from(DomColor3::from(v))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Color3> for DomColor3uint8 {
|
||||
fn from(v: Color3) -> Self {
|
||||
Self {
|
||||
r: v.r.clamp(u8::MIN as f32, u8::MAX as f32) as u8,
|
||||
g: v.g.clamp(u8::MIN as f32, u8::MAX as f32) as u8,
|
||||
b: v.b.clamp(u8::MIN as f32, u8::MAX as f32) as u8,
|
||||
}
|
||||
DomColor3uint8::from(DomColor3::from(v))
|
||||
}
|
||||
}
|
125
crates/lune-roblox/src/datatypes/types/color_sequence.rs
Normal file
|
@ -0,0 +1,125 @@
|
|||
use core::fmt;
|
||||
|
||||
use mlua::prelude::*;
|
||||
use rbx_dom_weak::types::{
|
||||
ColorSequence as DomColorSequence, ColorSequenceKeypoint as DomColorSequenceKeypoint,
|
||||
};
|
||||
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use crate::exports::LuaExportsTable;
|
||||
|
||||
use super::{super::*, Color3, ColorSequenceKeypoint};
|
||||
|
||||
/**
|
||||
An implementation of the [ColorSequence](https://create.roblox.com/docs/reference/engine/datatypes/ColorSequence) Roblox datatype.
|
||||
|
||||
This implements all documented properties, methods & constructors of the `ColorSequence` class as of March 2023.
|
||||
*/
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct ColorSequence {
|
||||
pub(crate) keypoints: Vec<ColorSequenceKeypoint>,
|
||||
}
|
||||
|
||||
impl LuaExportsTable<'_> for ColorSequence {
|
||||
const EXPORT_NAME: &'static str = "ColorSequence";
|
||||
|
||||
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, args: LuaMultiValue| {
|
||||
if let Ok(color) = ArgsColor::from_lua_multi(args.clone(), lua) {
|
||||
Ok(ColorSequence {
|
||||
keypoints: vec![
|
||||
ColorSequenceKeypoint {
|
||||
time: 0.0,
|
||||
color: *color,
|
||||
},
|
||||
ColorSequenceKeypoint {
|
||||
time: 1.0,
|
||||
color: *color,
|
||||
},
|
||||
],
|
||||
})
|
||||
} else if let Ok((c0, c1)) = ArgsColors::from_lua_multi(args.clone(), lua) {
|
||||
Ok(ColorSequence {
|
||||
keypoints: vec![
|
||||
ColorSequenceKeypoint {
|
||||
time: 0.0,
|
||||
color: *c0,
|
||||
},
|
||||
ColorSequenceKeypoint {
|
||||
time: 1.0,
|
||||
color: *c1,
|
||||
},
|
||||
],
|
||||
})
|
||||
} else if let Ok(keypoints) = ArgsKeypoints::from_lua_multi(args, lua) {
|
||||
Ok(ColorSequence {
|
||||
keypoints: keypoints.iter().map(|k| **k).collect(),
|
||||
})
|
||||
} else {
|
||||
// FUTURE: Better error message here using given arg types
|
||||
Err(LuaError::RuntimeError(
|
||||
"Invalid arguments to constructor".to_string(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
TableBuilder::new(lua)?
|
||||
.with_function("new", color_sequence_new)?
|
||||
.build_readonly()
|
||||
}
|
||||
}
|
||||
|
||||
impl LuaUserData for ColorSequence {
|
||||
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<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
||||
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ColorSequence {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
for (index, keypoint) in self.keypoints.iter().enumerate() {
|
||||
if index < self.keypoints.len() - 1 {
|
||||
write!(f, "{keypoint}, ")?;
|
||||
} else {
|
||||
write!(f, "{keypoint}")?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DomColorSequence> for ColorSequence {
|
||||
fn from(v: DomColorSequence) -> Self {
|
||||
Self {
|
||||
keypoints: v
|
||||
.keypoints
|
||||
.iter()
|
||||
.copied()
|
||||
.map(ColorSequenceKeypoint::from)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ColorSequence> for DomColorSequence {
|
||||
fn from(v: ColorSequence) -> Self {
|
||||
Self {
|
||||
keypoints: v
|
||||
.keypoints
|
||||
.iter()
|
||||
.copied()
|
||||
.map(DomColorSequenceKeypoint::from)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,12 +3,16 @@ use core::fmt;
|
|||
use mlua::prelude::*;
|
||||
use rbx_dom_weak::types::ColorSequenceKeypoint as DomColorSequenceKeypoint;
|
||||
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use crate::exports::LuaExportsTable;
|
||||
|
||||
use super::{super::*, Color3};
|
||||
|
||||
/**
|
||||
An implementation of the [ColorSequenceKeypoint](https://create.roblox.com/docs/reference/engine/datatypes/ColorSequenceKeypoint) Roblox datatype.
|
||||
|
||||
This implements all documented properties, methods & constructors of the ColorSequenceKeypoint class as of March 2023.
|
||||
This implements all documented properties, methods & constructors of the `ColorSequenceKeypoint` class as of March 2023.
|
||||
*/
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct ColorSequenceKeypoint {
|
||||
|
@ -16,18 +20,20 @@ pub struct ColorSequenceKeypoint {
|
|||
pub(crate) color: Color3,
|
||||
}
|
||||
|
||||
impl ColorSequenceKeypoint {
|
||||
pub(crate) fn make_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> {
|
||||
datatype_table.set(
|
||||
"new",
|
||||
lua.create_function(|_, (time, color): (f32, LuaUserDataRef<Color3>)| {
|
||||
Ok(ColorSequenceKeypoint {
|
||||
time,
|
||||
color: *color,
|
||||
})
|
||||
})?,
|
||||
)?;
|
||||
Ok(())
|
||||
impl LuaExportsTable<'_> for ColorSequenceKeypoint {
|
||||
const EXPORT_NAME: &'static str = "ColorSequenceKeypoint";
|
||||
|
||||
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)?
|
||||
.build_readonly()
|
||||
}
|
||||
}
|
||||
|
120
crates/lune-roblox/src/datatypes/types/content.rs
Normal file
|
@ -0,0 +1,120 @@
|
|||
use core::fmt;
|
||||
|
||||
use mlua::prelude::*;
|
||||
use rbx_dom_weak::types::{Content as DomContent, ContentType};
|
||||
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use crate::{exports::LuaExportsTable, instance::Instance};
|
||||
|
||||
use super::{super::*, EnumItem};
|
||||
|
||||
/**
|
||||
An implementation of the [Content](https://create.roblox.com/docs/reference/engine/datatypes/Content) Roblox datatype.
|
||||
|
||||
This implements all documented properties, methods & constructors of the Content type as of April 2025.
|
||||
*/
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Content(ContentType);
|
||||
|
||||
impl LuaExportsTable<'_> for Content {
|
||||
const EXPORT_NAME: &'static str = "Content";
|
||||
|
||||
fn create_exports_table(lua: &'_ Lua) -> LuaResult<LuaTable<'_>> {
|
||||
let from_uri = |_, uri: String| Ok(Self(ContentType::Uri(uri)));
|
||||
|
||||
let from_object = |_, obj: LuaUserDataRef<Instance>| {
|
||||
let database = rbx_reflection_database::get();
|
||||
let instance_descriptor = database
|
||||
.classes
|
||||
.get("Instance")
|
||||
.expect("the reflection database should always have Instance in it");
|
||||
let param_descriptor = database.classes.get(obj.get_class_name()).expect(
|
||||
"you should not be able to construct an Instance that is not known to Lune",
|
||||
);
|
||||
if database.has_superclass(param_descriptor, instance_descriptor) {
|
||||
Err(LuaError::runtime("the provided object is a descendant class of 'Instance', expected one that was only an 'Object'"))
|
||||
} else {
|
||||
Ok(Content(ContentType::Object(obj.dom_ref)))
|
||||
}
|
||||
};
|
||||
|
||||
TableBuilder::new(lua)?
|
||||
.with_value("none", Content(ContentType::None))?
|
||||
.with_function("fromUri", from_uri)?
|
||||
.with_function("fromObject", from_object)?
|
||||
.build_readonly()
|
||||
}
|
||||
}
|
||||
|
||||
impl LuaUserData for Content {
|
||||
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("SourceType", |_, this| {
|
||||
let variant_name = match &this.0 {
|
||||
ContentType::None => "None",
|
||||
ContentType::Uri(_) => "Uri",
|
||||
ContentType::Object(_) => "Object",
|
||||
other => {
|
||||
return Err(LuaError::runtime(format!(
|
||||
"cannot get SourceType: unknown ContentType variant '{other:?}'"
|
||||
)))
|
||||
}
|
||||
};
|
||||
Ok(EnumItem::from_enum_name_and_name(
|
||||
"ContentSourceType",
|
||||
variant_name,
|
||||
))
|
||||
});
|
||||
fields.add_field_method_get("Uri", |_, this| {
|
||||
if let ContentType::Uri(uri) = &this.0 {
|
||||
Ok(Some(uri.to_owned()))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
});
|
||||
fields.add_field_method_get("Object", |_, this| {
|
||||
if let ContentType::Object(referent) = &this.0 {
|
||||
Ok(Instance::new_opt(*referent))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
||||
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Content {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
// Regardless of the actual content of the Content, Roblox just emits
|
||||
// `Content` when casting it to a string. We do not do that.
|
||||
write!(f, "Content(")?;
|
||||
match &self.0 {
|
||||
ContentType::None => write!(f, "None")?,
|
||||
ContentType::Uri(uri) => write!(f, "Uri={uri}")?,
|
||||
ContentType::Object(_) => write!(f, "Object")?,
|
||||
other => write!(f, "UnknownType({other:?})")?,
|
||||
}
|
||||
write!(f, ")")
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DomContent> for Content {
|
||||
fn from(value: DomContent) -> Self {
|
||||
Self(value.value().clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Content> for DomContent {
|
||||
fn from(value: Content) -> Self {
|
||||
match value.0 {
|
||||
ContentType::None => Self::none(),
|
||||
ContentType::Uri(uri) => Self::from_uri(uri),
|
||||
ContentType::Object(referent) => Self::from_referent(referent),
|
||||
other => unimplemented!("unknown variant of ContentType: {other:?}"),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,14 +1,14 @@
|
|||
use core::fmt;
|
||||
|
||||
use mlua::prelude::*;
|
||||
use rbx_dom_weak::types::Enum as DomEnum;
|
||||
use rbx_dom_weak::types::EnumItem as DomEnumItem;
|
||||
|
||||
use super::{super::*, Enum};
|
||||
|
||||
/**
|
||||
An implementation of the [EnumItem](https://create.roblox.com/docs/reference/engine/datatypes/EnumItem) Roblox datatype.
|
||||
|
||||
This implements all documented properties, methods & constructors of the EnumItem class as of March 2023.
|
||||
This implements all documented properties, methods & constructors of the `EnumItem` class as of March 2023.
|
||||
*/
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EnumItem {
|
||||
|
@ -74,6 +74,20 @@ impl LuaUserData for EnumItem {
|
|||
}
|
||||
}
|
||||
|
||||
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",
|
||||
message: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for EnumItem {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}.{}", self.parent, self.name)
|
||||
|
@ -86,8 +100,18 @@ impl PartialEq for EnumItem {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<EnumItem> for DomEnum {
|
||||
impl From<EnumItem> for DomEnumItem {
|
||||
fn from(v: EnumItem) -> Self {
|
||||
DomEnum::from_u32(v.value)
|
||||
DomEnumItem {
|
||||
ty: v.parent.desc.name.to_string(),
|
||||
value: v.value,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DomEnumItem> for EnumItem {
|
||||
fn from(value: DomEnumItem) -> Self {
|
||||
EnumItem::from_enum_name_and_value(value.ty, value.value)
|
||||
.expect("cannot convert rbx_type::EnumItem with unknown type into EnumItem")
|
||||
}
|
||||
}
|
|
@ -24,8 +24,7 @@ impl LuaUserData for Enums {
|
|||
|_, _, name: String| match Enum::from_name(&name) {
|
||||
Some(e) => Ok(e),
|
||||
None => Err(LuaError::RuntimeError(format!(
|
||||
"The enum '{}' does not exist",
|
||||
name
|
||||
"The enum '{name}' does not exist",
|
||||
))),
|
||||
},
|
||||
);
|
|
@ -1,8 +1,14 @@
|
|||
#![allow(clippy::struct_excessive_bools)]
|
||||
|
||||
use core::fmt;
|
||||
|
||||
use mlua::prelude::*;
|
||||
use rbx_dom_weak::types::Faces as DomFaces;
|
||||
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use crate::exports::LuaExportsTable;
|
||||
|
||||
use super::{super::*, EnumItem};
|
||||
|
||||
/**
|
||||
|
@ -20,59 +26,63 @@ pub struct Faces {
|
|||
pub(crate) front: bool,
|
||||
}
|
||||
|
||||
impl Faces {
|
||||
pub(crate) fn make_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> {
|
||||
datatype_table.set(
|
||||
"new",
|
||||
lua.create_function(|_, args: LuaMultiValue| {
|
||||
let mut right = false;
|
||||
let mut top = false;
|
||||
let mut back = false;
|
||||
let mut left = false;
|
||||
let mut bottom = false;
|
||||
let mut front = false;
|
||||
let mut check = |e: &EnumItem| {
|
||||
if e.parent.desc.name == "NormalId" {
|
||||
match &e.name {
|
||||
name if name == "Right" => right = true,
|
||||
name if name == "Top" => top = true,
|
||||
name if name == "Back" => back = true,
|
||||
name if name == "Left" => left = true,
|
||||
name if name == "Bottom" => bottom = true,
|
||||
name if name == "Front" => front = true,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
};
|
||||
for (index, arg) in args.into_iter().enumerate() {
|
||||
if let LuaValue::UserData(u) = arg {
|
||||
if let Ok(e) = u.borrow::<EnumItem>() {
|
||||
check(&e);
|
||||
} else {
|
||||
return Err(LuaError::RuntimeError(format!(
|
||||
"Expected argument #{} to be an EnumItem, got userdata",
|
||||
index
|
||||
)));
|
||||
}
|
||||
} else {
|
||||
return Err(LuaError::RuntimeError(format!(
|
||||
"Expected argument #{} to be an EnumItem, got {}",
|
||||
index,
|
||||
arg.type_name()
|
||||
)));
|
||||
impl LuaExportsTable<'_> for Faces {
|
||||
const EXPORT_NAME: &'static str = "Faces";
|
||||
|
||||
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;
|
||||
let mut left = false;
|
||||
let mut bottom = false;
|
||||
let mut front = false;
|
||||
|
||||
let mut check = |e: &EnumItem| {
|
||||
if e.parent.desc.name == "NormalId" {
|
||||
match &e.name {
|
||||
name if name == "Right" => right = true,
|
||||
name if name == "Top" => top = true,
|
||||
name if name == "Back" => back = true,
|
||||
name if name == "Left" => left = true,
|
||||
name if name == "Bottom" => bottom = true,
|
||||
name if name == "Front" => front = true,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(Faces {
|
||||
right,
|
||||
top,
|
||||
back,
|
||||
left,
|
||||
bottom,
|
||||
front,
|
||||
})
|
||||
})?,
|
||||
)?;
|
||||
Ok(())
|
||||
};
|
||||
|
||||
for (index, arg) in args.into_iter().enumerate() {
|
||||
if let LuaValue::UserData(u) = arg {
|
||||
if let Ok(e) = u.borrow::<EnumItem>() {
|
||||
check(&e);
|
||||
} else {
|
||||
return Err(LuaError::RuntimeError(format!(
|
||||
"Expected argument #{index} to be an EnumItem, got userdata",
|
||||
)));
|
||||
}
|
||||
} else {
|
||||
return Err(LuaError::RuntimeError(format!(
|
||||
"Expected argument #{} to be an EnumItem, got {}",
|
||||
index,
|
||||
arg.type_name()
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Faces {
|
||||
right,
|
||||
top,
|
||||
back,
|
||||
left,
|
||||
bottom,
|
||||
front,
|
||||
})
|
||||
};
|
||||
|
||||
TableBuilder::new(lua)?
|
||||
.with_function("new", faces_new)?
|
||||
.build_readonly()
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,10 @@ use rbx_dom_weak::types::{
|
|||
Font as DomFont, FontStyle as DomFontStyle, FontWeight as DomFontWeight,
|
||||
};
|
||||
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use crate::exports::LuaExportsTable;
|
||||
|
||||
use super::{super::*, EnumItem};
|
||||
|
||||
/**
|
||||
|
@ -34,66 +38,65 @@ impl Font {
|
|||
cached_id: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn make_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> {
|
||||
datatype_table.set(
|
||||
"new",
|
||||
lua.create_function(
|
||||
|_, (family, weight, style): (String, Option<FontWeight>, Option<FontStyle>)| {
|
||||
Ok(Font {
|
||||
family,
|
||||
weight: weight.unwrap_or_default(),
|
||||
style: style.unwrap_or_default(),
|
||||
cached_id: None,
|
||||
})
|
||||
},
|
||||
)?,
|
||||
)?;
|
||||
datatype_table.set(
|
||||
"fromEnum",
|
||||
lua.create_function(|_, value: LuaUserDataRef<EnumItem>| {
|
||||
if value.parent.desc.name == "Font" {
|
||||
match Font::from_enum_item(&value) {
|
||||
Some(props) => Ok(props),
|
||||
None => Err(LuaError::RuntimeError(format!(
|
||||
"Found unknown Font '{}'",
|
||||
value.name
|
||||
))),
|
||||
}
|
||||
} else {
|
||||
Err(LuaError::RuntimeError(format!(
|
||||
"Expected argument #1 to be a Font, got {}",
|
||||
value.parent.desc.name
|
||||
)))
|
||||
impl LuaExportsTable<'_> for Font {
|
||||
const EXPORT_NAME: &'static str = "Font";
|
||||
|
||||
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),
|
||||
None => Err(LuaError::RuntimeError(format!(
|
||||
"Found unknown Font '{}'",
|
||||
value.name
|
||||
))),
|
||||
}
|
||||
})?,
|
||||
)?;
|
||||
datatype_table.set(
|
||||
"fromName",
|
||||
lua.create_function(
|
||||
|_, (file, weight, style): (String, Option<FontWeight>, Option<FontStyle>)| {
|
||||
Ok(Font {
|
||||
family: format!("rbxasset://fonts/families/{}.json", file),
|
||||
weight: weight.unwrap_or_default(),
|
||||
style: style.unwrap_or_default(),
|
||||
cached_id: None,
|
||||
})
|
||||
},
|
||||
)?,
|
||||
)?;
|
||||
datatype_table.set(
|
||||
"fromId",
|
||||
lua.create_function(
|
||||
|_, (id, weight, style): (i32, Option<FontWeight>, Option<FontStyle>)| {
|
||||
Ok(Font {
|
||||
family: format!("rbxassetid://{}", id),
|
||||
weight: weight.unwrap_or_default(),
|
||||
style: style.unwrap_or_default(),
|
||||
cached_id: None,
|
||||
})
|
||||
},
|
||||
)?,
|
||||
)
|
||||
} else {
|
||||
Err(LuaError::RuntimeError(format!(
|
||||
"Expected argument #1 to be a Font, got {}",
|
||||
value.parent.desc.name
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
let font_from_name =
|
||||
|_, (file, weight, style): (String, Option<FontWeight>, Option<FontStyle>)| {
|
||||
Ok(Font {
|
||||
family: format!("rbxasset://fonts/families/{file}.json"),
|
||||
weight: weight.unwrap_or_default(),
|
||||
style: style.unwrap_or_default(),
|
||||
cached_id: None,
|
||||
})
|
||||
};
|
||||
|
||||
let font_from_id =
|
||||
|_, (id, weight, style): (i32, Option<FontWeight>, Option<FontStyle>)| {
|
||||
Ok(Font {
|
||||
family: format!("rbxassetid://{id}"),
|
||||
weight: weight.unwrap_or_default(),
|
||||
style: style.unwrap_or_default(),
|
||||
cached_id: None,
|
||||
})
|
||||
};
|
||||
|
||||
let font_new =
|
||||
|_, (family, weight, style): (String, Option<FontWeight>, Option<FontStyle>)| {
|
||||
Ok(Font {
|
||||
family,
|
||||
weight: weight.unwrap_or_default(),
|
||||
style: style.unwrap_or_default(),
|
||||
cached_id: None,
|
||||
})
|
||||
};
|
||||
|
||||
TableBuilder::new(lua)?
|
||||
.with_function("fromEnum", font_from_enum)?
|
||||
.with_function("fromName", font_from_name)?
|
||||
.with_function("fromId", font_from_id)?
|
||||
.with_function("new", font_new)?
|
||||
.build_readonly()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -205,7 +208,7 @@ pub(crate) enum FontWeight {
|
|||
}
|
||||
|
||||
impl FontWeight {
|
||||
pub(crate) fn as_u16(&self) -> u16 {
|
||||
pub(crate) fn as_u16(self) -> u16 {
|
||||
match self {
|
||||
Self::Thin => 100,
|
||||
Self::ExtraLight => 200,
|
||||
|
@ -287,12 +290,11 @@ impl<'lua> FromLua<'lua> for FontWeight {
|
|||
if value.parent.desc.name == "FontWeight" {
|
||||
if let Ok(value) = FontWeight::from_str(&value.name) {
|
||||
return Ok(value);
|
||||
} else {
|
||||
message = Some(format!(
|
||||
"Found unknown Enum.FontWeight value '{}'",
|
||||
value.name
|
||||
));
|
||||
}
|
||||
message = Some(format!(
|
||||
"Found unknown Enum.FontWeight value '{}'",
|
||||
value.name
|
||||
));
|
||||
} else {
|
||||
message = Some(format!(
|
||||
"Expected Enum.FontWeight, got Enum.{}",
|
||||
|
@ -315,7 +317,7 @@ impl<'lua> IntoLua<'lua> for FontWeight {
|
|||
None => Err(LuaError::ToLuaConversionError {
|
||||
from: "FontWeight",
|
||||
to: "EnumItem",
|
||||
message: Some(format!("Found unknown Enum.FontWeight value '{}'", self)),
|
||||
message: Some(format!("Found unknown Enum.FontWeight value '{self}'")),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
@ -328,7 +330,7 @@ pub(crate) enum FontStyle {
|
|||
}
|
||||
|
||||
impl FontStyle {
|
||||
pub(crate) fn as_u8(&self) -> u8 {
|
||||
pub(crate) fn as_u8(self) -> u8 {
|
||||
match self {
|
||||
Self::Normal => 0,
|
||||
Self::Italic => 1,
|
||||
|
@ -382,12 +384,11 @@ impl<'lua> FromLua<'lua> for FontStyle {
|
|||
if value.parent.desc.name == "FontStyle" {
|
||||
if let Ok(value) = FontStyle::from_str(&value.name) {
|
||||
return Ok(value);
|
||||
} else {
|
||||
message = Some(format!(
|
||||
"Found unknown Enum.FontStyle value '{}'",
|
||||
value.name
|
||||
));
|
||||
}
|
||||
message = Some(format!(
|
||||
"Found unknown Enum.FontStyle value '{}'",
|
||||
value.name
|
||||
));
|
||||
} else {
|
||||
message = Some(format!(
|
||||
"Expected Enum.FontStyle, got Enum.{}",
|
||||
|
@ -410,7 +411,7 @@ impl<'lua> IntoLua<'lua> for FontStyle {
|
|||
None => Err(LuaError::ToLuaConversionError {
|
||||
from: "FontStyle",
|
||||
to: "EnumItem",
|
||||
message: Some(format!("Found unknown Enum.FontStyle value '{}'", self)),
|
||||
message: Some(format!("Found unknown Enum.FontStyle value '{self}'")),
|
||||
}),
|
||||
}
|
||||
}
|
|
@ -4,6 +4,7 @@ mod cframe;
|
|||
mod color3;
|
||||
mod color_sequence;
|
||||
mod color_sequence_keypoint;
|
||||
mod content;
|
||||
mod r#enum;
|
||||
mod r#enum_item;
|
||||
mod r#enums;
|
||||
|
@ -30,6 +31,7 @@ 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;
|
|
@ -3,12 +3,16 @@ use core::fmt;
|
|||
use mlua::prelude::*;
|
||||
use rbx_dom_weak::types::NumberRange as DomNumberRange;
|
||||
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use crate::exports::LuaExportsTable;
|
||||
|
||||
use super::super::*;
|
||||
|
||||
/**
|
||||
An implementation of the [NumberRange](https://create.roblox.com/docs/reference/engine/datatypes/NumberRange) Roblox datatype.
|
||||
|
||||
This implements all documented properties, methods & constructors of the NumberRange class as of March 2023.
|
||||
This implements all documented properties, methods & constructors of the `NumberRange` class as of March 2023.
|
||||
*/
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct NumberRange {
|
||||
|
@ -16,20 +20,23 @@ pub struct NumberRange {
|
|||
pub(crate) max: f32,
|
||||
}
|
||||
|
||||
impl NumberRange {
|
||||
pub(crate) fn make_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> {
|
||||
datatype_table.set(
|
||||
"new",
|
||||
lua.create_function(|_, (min, max): (f32, Option<f32>)| {
|
||||
Ok(match max {
|
||||
Some(max) => NumberRange {
|
||||
min: min.min(max),
|
||||
max: min.max(max),
|
||||
},
|
||||
None => NumberRange { min, max: min },
|
||||
})
|
||||
})?,
|
||||
)
|
||||
impl LuaExportsTable<'_> for NumberRange {
|
||||
const EXPORT_NAME: &'static str = "NumberRange";
|
||||
|
||||
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),
|
||||
max: min.max(max),
|
||||
},
|
||||
None => NumberRange { min, max: min },
|
||||
})
|
||||
};
|
||||
|
||||
TableBuilder::new(lua)?
|
||||
.with_function("new", number_range_new)?
|
||||
.build_readonly()
|
||||
}
|
||||
}
|
||||
|
129
crates/lune-roblox/src/datatypes/types/number_sequence.rs
Normal file
|
@ -0,0 +1,129 @@
|
|||
use core::fmt;
|
||||
|
||||
use mlua::prelude::*;
|
||||
use rbx_dom_weak::types::{
|
||||
NumberSequence as DomNumberSequence, NumberSequenceKeypoint as DomNumberSequenceKeypoint,
|
||||
};
|
||||
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use crate::exports::LuaExportsTable;
|
||||
|
||||
use super::{super::*, NumberSequenceKeypoint};
|
||||
|
||||
/**
|
||||
An implementation of the [NumberSequence](https://create.roblox.com/docs/reference/engine/datatypes/NumberSequence) Roblox datatype.
|
||||
|
||||
This implements all documented properties, methods & constructors of the `NumberSequence` class as of March 2023.
|
||||
*/
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct NumberSequence {
|
||||
pub(crate) keypoints: Vec<NumberSequenceKeypoint>,
|
||||
}
|
||||
|
||||
impl LuaExportsTable<'_> for NumberSequence {
|
||||
const EXPORT_NAME: &'static str = "NumberSequence";
|
||||
|
||||
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
|
||||
type ArgsColor = f32;
|
||||
type ArgsColors = (f32, f32);
|
||||
type ArgsKeypoints<'lua> = Vec<LuaUserDataRef<'lua, NumberSequenceKeypoint>>;
|
||||
|
||||
let number_sequence_new = |lua, args: LuaMultiValue| {
|
||||
if let Ok(value) = ArgsColor::from_lua_multi(args.clone(), lua) {
|
||||
Ok(NumberSequence {
|
||||
keypoints: vec![
|
||||
NumberSequenceKeypoint {
|
||||
time: 0.0,
|
||||
value,
|
||||
envelope: 0.0,
|
||||
},
|
||||
NumberSequenceKeypoint {
|
||||
time: 1.0,
|
||||
value,
|
||||
envelope: 0.0,
|
||||
},
|
||||
],
|
||||
})
|
||||
} else if let Ok((v0, v1)) = ArgsColors::from_lua_multi(args.clone(), lua) {
|
||||
Ok(NumberSequence {
|
||||
keypoints: vec![
|
||||
NumberSequenceKeypoint {
|
||||
time: 0.0,
|
||||
value: v0,
|
||||
envelope: 0.0,
|
||||
},
|
||||
NumberSequenceKeypoint {
|
||||
time: 1.0,
|
||||
value: v1,
|
||||
envelope: 0.0,
|
||||
},
|
||||
],
|
||||
})
|
||||
} else if let Ok(keypoints) = ArgsKeypoints::from_lua_multi(args, lua) {
|
||||
Ok(NumberSequence {
|
||||
keypoints: keypoints.iter().map(|k| **k).collect(),
|
||||
})
|
||||
} else {
|
||||
// FUTURE: Better error message here using given arg types
|
||||
Err(LuaError::RuntimeError(
|
||||
"Invalid arguments to constructor".to_string(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
TableBuilder::new(lua)?
|
||||
.with_function("new", number_sequence_new)?
|
||||
.build_readonly()
|
||||
}
|
||||
}
|
||||
|
||||
impl LuaUserData for NumberSequence {
|
||||
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<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
||||
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for NumberSequence {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
for (index, keypoint) in self.keypoints.iter().enumerate() {
|
||||
if index < self.keypoints.len() - 1 {
|
||||
write!(f, "{keypoint}, ")?;
|
||||
} else {
|
||||
write!(f, "{keypoint}")?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DomNumberSequence> for NumberSequence {
|
||||
fn from(v: DomNumberSequence) -> Self {
|
||||
Self {
|
||||
keypoints: v
|
||||
.keypoints
|
||||
.iter()
|
||||
.copied()
|
||||
.map(NumberSequenceKeypoint::from)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NumberSequence> for DomNumberSequence {
|
||||
fn from(v: NumberSequence) -> Self {
|
||||
Self {
|
||||
keypoints: v
|
||||
.keypoints
|
||||
.iter()
|
||||
.copied()
|
||||
.map(DomNumberSequenceKeypoint::from)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,12 +3,16 @@ use core::fmt;
|
|||
use mlua::prelude::*;
|
||||
use rbx_dom_weak::types::NumberSequenceKeypoint as DomNumberSequenceKeypoint;
|
||||
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use crate::exports::LuaExportsTable;
|
||||
|
||||
use super::super::*;
|
||||
|
||||
/**
|
||||
An implementation of the [NumberSequenceKeypoint](https://create.roblox.com/docs/reference/engine/datatypes/NumberSequenceKeypoint) Roblox datatype.
|
||||
|
||||
This implements all documented properties, methods & constructors of the NumberSequenceKeypoint class as of March 2023.
|
||||
This implements all documented properties, methods & constructors of the `NumberSequenceKeypoint` class as of March 2023.
|
||||
*/
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct NumberSequenceKeypoint {
|
||||
|
@ -17,19 +21,21 @@ pub struct NumberSequenceKeypoint {
|
|||
pub(crate) envelope: f32,
|
||||
}
|
||||
|
||||
impl NumberSequenceKeypoint {
|
||||
pub(crate) fn make_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> {
|
||||
datatype_table.set(
|
||||
"new",
|
||||
lua.create_function(|_, (time, value, envelope): (f32, f32, Option<f32>)| {
|
||||
Ok(NumberSequenceKeypoint {
|
||||
time,
|
||||
value,
|
||||
envelope: envelope.unwrap_or_default(),
|
||||
})
|
||||
})?,
|
||||
)?;
|
||||
Ok(())
|
||||
impl LuaExportsTable<'_> for NumberSequenceKeypoint {
|
||||
const EXPORT_NAME: &'static str = "NumberSequenceKeypoint";
|
||||
|
||||
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)?
|
||||
.build_readonly()
|
||||
}
|
||||
}
|
||||
|
|
@ -3,12 +3,16 @@ use core::fmt;
|
|||
use mlua::prelude::*;
|
||||
use rbx_dom_weak::types::CustomPhysicalProperties as DomCustomPhysicalProperties;
|
||||
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use crate::exports::LuaExportsTable;
|
||||
|
||||
use super::{super::*, EnumItem};
|
||||
|
||||
/**
|
||||
An implementation of the [PhysicalProperties](https://create.roblox.com/docs/reference/engine/datatypes/PhysicalProperties) Roblox datatype.
|
||||
|
||||
This implements all documented properties, methods & constructors of the PhysicalProperties class as of March 2023.
|
||||
This implements all documented properties, methods & constructors of the `PhysicalProperties` class as of March 2023.
|
||||
*/
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct PhysicalProperties {
|
||||
|
@ -32,51 +36,52 @@ impl PhysicalProperties {
|
|||
elasticity_weight: props.5,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn make_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> {
|
||||
impl LuaExportsTable<'_> for PhysicalProperties {
|
||||
const EXPORT_NAME: &'static str = "PhysicalProperties";
|
||||
|
||||
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
|
||||
type ArgsMaterial<'lua> = LuaUserDataRef<'lua, EnumItem>;
|
||||
type ArgsNumbers = (f32, f32, f32, Option<f32>, Option<f32>);
|
||||
datatype_table.set(
|
||||
"new",
|
||||
lua.create_function(|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) {
|
||||
Some(props) => Ok(props),
|
||||
None => Err(LuaError::RuntimeError(format!(
|
||||
"Found unknown Material '{}'",
|
||||
value.name
|
||||
))),
|
||||
}
|
||||
} else {
|
||||
Err(LuaError::RuntimeError(format!(
|
||||
"Expected argument #1 to be a Material, got {}",
|
||||
value.parent.desc.name
|
||||
)))
|
||||
|
||||
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) {
|
||||
Some(props) => Ok(props),
|
||||
None => Err(LuaError::RuntimeError(format!(
|
||||
"Found unknown Material '{}'",
|
||||
value.name
|
||||
))),
|
||||
}
|
||||
} else if let Ok((
|
||||
} else {
|
||||
Err(LuaError::RuntimeError(format!(
|
||||
"Expected argument #1 to be a Material, got {}",
|
||||
value.parent.desc.name
|
||||
)))
|
||||
}
|
||||
} else if let Ok((density, friction, elasticity, friction_weight, elasticity_weight)) =
|
||||
ArgsNumbers::from_lua_multi(args, lua)
|
||||
{
|
||||
Ok(PhysicalProperties {
|
||||
density,
|
||||
friction,
|
||||
friction_weight: friction_weight.unwrap_or(1.0),
|
||||
elasticity,
|
||||
friction_weight,
|
||||
elasticity_weight,
|
||||
)) = ArgsNumbers::from_lua_multi(args, lua)
|
||||
{
|
||||
Ok(PhysicalProperties {
|
||||
density,
|
||||
friction,
|
||||
friction_weight: friction_weight.unwrap_or(1.0),
|
||||
elasticity,
|
||||
elasticity_weight: elasticity_weight.unwrap_or(1.0),
|
||||
})
|
||||
} else {
|
||||
// FUTURE: Better error message here using given arg types
|
||||
Err(LuaError::RuntimeError(
|
||||
"Invalid arguments to constructor".to_string(),
|
||||
))
|
||||
}
|
||||
})?,
|
||||
)
|
||||
elasticity_weight: elasticity_weight.unwrap_or(1.0),
|
||||
})
|
||||
} else {
|
||||
// FUTURE: Better error message here using given arg types
|
||||
Err(LuaError::RuntimeError(
|
||||
"Invalid arguments to constructor".to_string(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
TableBuilder::new(lua)?
|
||||
.with_function("new", physical_properties_new)?
|
||||
.build_readonly()
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,10 @@ use glam::Vec3;
|
|||
use mlua::prelude::*;
|
||||
use rbx_dom_weak::types::Ray as DomRay;
|
||||
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use crate::exports::LuaExportsTable;
|
||||
|
||||
use super::{super::*, Vector3};
|
||||
|
||||
/**
|
||||
|
@ -26,19 +30,23 @@ impl Ray {
|
|||
let dot_product = lhs.dot(norm).max(0.0);
|
||||
self.origin + norm * dot_product
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn make_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> {
|
||||
datatype_table.set(
|
||||
"new",
|
||||
lua.create_function(
|
||||
|_, (origin, direction): (LuaUserDataRef<Vector3>, LuaUserDataRef<Vector3>)| {
|
||||
Ok(Ray {
|
||||
origin: origin.0,
|
||||
direction: direction.0,
|
||||
})
|
||||
},
|
||||
)?,
|
||||
)
|
||||
impl LuaExportsTable<'_> for Ray {
|
||||
const EXPORT_NAME: &'static str = "Ray";
|
||||
|
||||
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
|
||||
let ray_new =
|
||||
|_, (origin, direction): (LuaUserDataRef<Vector3>, LuaUserDataRef<Vector3>)| {
|
||||
Ok(Ray {
|
||||
origin: origin.0,
|
||||
direction: direction.0,
|
||||
})
|
||||
};
|
||||
|
||||
TableBuilder::new(lua)?
|
||||
.with_function("new", ray_new)?
|
||||
.build_readonly()
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,10 @@ use glam::Vec2;
|
|||
use mlua::prelude::*;
|
||||
use rbx_dom_weak::types::Rect as DomRect;
|
||||
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use crate::exports::LuaExportsTable;
|
||||
|
||||
use super::{super::*, Vector2};
|
||||
|
||||
/**
|
||||
|
@ -28,33 +32,37 @@ impl Rect {
|
|||
}
|
||||
}
|
||||
|
||||
impl Rect {
|
||||
pub(crate) fn make_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> {
|
||||
impl LuaExportsTable<'_> for Rect {
|
||||
const EXPORT_NAME: &'static str = "Rect";
|
||||
|
||||
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>);
|
||||
datatype_table.set(
|
||||
"new",
|
||||
lua.create_function(|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,
|
||||
max.map(|m| *m).unwrap_or_default().0,
|
||||
))
|
||||
} else if let Ok((x0, y0, x1, y1)) = ArgsNums::from_lua_multi(args, lua) {
|
||||
let min = Vec2::new(x0.unwrap_or_default(), y0.unwrap_or_default());
|
||||
let max = Vec2::new(x1.unwrap_or_default(), y1.unwrap_or_default());
|
||||
Ok(Rect::new(min, max))
|
||||
} else {
|
||||
// FUTURE: Better error message here using given arg types
|
||||
Err(LuaError::RuntimeError(
|
||||
"Invalid arguments to constructor".to_string(),
|
||||
))
|
||||
}
|
||||
})?,
|
||||
)
|
||||
|
||||
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,
|
||||
max.map(|m| *m).unwrap_or_default().0,
|
||||
))
|
||||
} else if let Ok((x0, y0, x1, y1)) = ArgsNums::from_lua_multi(args, lua) {
|
||||
let min = Vec2::new(x0.unwrap_or_default(), y0.unwrap_or_default());
|
||||
let max = Vec2::new(x1.unwrap_or_default(), y1.unwrap_or_default());
|
||||
Ok(Rect::new(min, max))
|
||||
} else {
|
||||
// FUTURE: Better error message here using given arg types
|
||||
Err(LuaError::RuntimeError(
|
||||
"Invalid arguments to constructor".to_string(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
TableBuilder::new(lua)?
|
||||
.with_function("new", rect_new)?
|
||||
.build_readonly()
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,10 @@ use glam::{Mat4, Vec3};
|
|||
use mlua::prelude::*;
|
||||
use rbx_dom_weak::types::Region3 as DomRegion3;
|
||||
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use crate::exports::LuaExportsTable;
|
||||
|
||||
use super::{super::*, CFrame, Vector3};
|
||||
|
||||
/**
|
||||
|
@ -18,19 +22,20 @@ pub struct Region3 {
|
|||
pub(crate) max: Vec3,
|
||||
}
|
||||
|
||||
impl Region3 {
|
||||
pub(crate) fn make_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> {
|
||||
datatype_table.set(
|
||||
"new",
|
||||
lua.create_function(
|
||||
|_, (min, max): (LuaUserDataRef<Vector3>, LuaUserDataRef<Vector3>)| {
|
||||
Ok(Region3 {
|
||||
min: min.0,
|
||||
max: max.0,
|
||||
})
|
||||
},
|
||||
)?,
|
||||
)
|
||||
impl LuaExportsTable<'_> for Region3 {
|
||||
const EXPORT_NAME: &'static str = "Region3";
|
||||
|
||||
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)?
|
||||
.build_readonly()
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,10 @@ use glam::IVec3;
|
|||
use mlua::prelude::*;
|
||||
use rbx_dom_weak::types::Region3int16 as DomRegion3int16;
|
||||
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use crate::exports::LuaExportsTable;
|
||||
|
||||
use super::{super::*, Vector3int16};
|
||||
|
||||
/**
|
||||
|
@ -18,19 +22,21 @@ pub struct Region3int16 {
|
|||
pub(crate) max: IVec3,
|
||||
}
|
||||
|
||||
impl Region3int16 {
|
||||
pub(crate) fn make_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> {
|
||||
datatype_table.set(
|
||||
"new",
|
||||
lua.create_function(
|
||||
|_, (min, max): (LuaUserDataRef<Vector3int16>, LuaUserDataRef<Vector3int16>)| {
|
||||
Ok(Region3int16 {
|
||||
min: min.0,
|
||||
max: max.0,
|
||||
})
|
||||
},
|
||||
)?,
|
||||
)
|
||||
impl LuaExportsTable<'_> for Region3int16 {
|
||||
const EXPORT_NAME: &'static str = "Region3int16";
|
||||
|
||||
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
|
||||
let region3int16_new =
|
||||
|_, (min, max): (LuaUserDataRef<Vector3int16>, LuaUserDataRef<Vector3int16>)| {
|
||||
Ok(Region3int16 {
|
||||
min: min.0,
|
||||
max: max.0,
|
||||
})
|
||||
};
|
||||
|
||||
TableBuilder::new(lua)?
|
||||
.with_function("new", region3int16_new)?
|
||||
.build_readonly()
|
||||
}
|
||||
}
|
||||
|
|
@ -4,12 +4,16 @@ use std::ops;
|
|||
use mlua::prelude::*;
|
||||
use rbx_dom_weak::types::UDim as DomUDim;
|
||||
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use crate::exports::LuaExportsTable;
|
||||
|
||||
use super::super::*;
|
||||
|
||||
/**
|
||||
An implementation of the [UDim](https://create.roblox.com/docs/reference/engine/datatypes/UDim) Roblox datatype.
|
||||
|
||||
This implements all documented properties, methods & constructors of the UDim class as of March 2023.
|
||||
This implements all documented properties, methods & constructors of the `UDim` class as of March 2023.
|
||||
*/
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct UDim {
|
||||
|
@ -21,17 +25,22 @@ impl UDim {
|
|||
pub(super) fn new(scale: f32, offset: i32) -> Self {
|
||||
Self { scale, offset }
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn make_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> {
|
||||
datatype_table.set(
|
||||
"new",
|
||||
lua.create_function(|_, (scale, offset): (Option<f32>, Option<i32>)| {
|
||||
Ok(UDim {
|
||||
scale: scale.unwrap_or_default(),
|
||||
offset: offset.unwrap_or_default(),
|
||||
})
|
||||
})?,
|
||||
)
|
||||
impl LuaExportsTable<'_> for UDim {
|
||||
const EXPORT_NAME: &'static str = "UDim";
|
||||
|
||||
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(),
|
||||
})
|
||||
};
|
||||
|
||||
TableBuilder::new(lua)?
|
||||
.with_function("new", udim_new)?
|
||||
.build_readonly()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
#![allow(clippy::items_after_statements)]
|
||||
|
||||
use core::fmt;
|
||||
use std::ops;
|
||||
|
||||
|
@ -5,12 +7,16 @@ use glam::Vec2;
|
|||
use mlua::prelude::*;
|
||||
use rbx_dom_weak::types::UDim2 as DomUDim2;
|
||||
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use crate::exports::LuaExportsTable;
|
||||
|
||||
use super::{super::*, UDim};
|
||||
|
||||
/**
|
||||
An implementation of the [UDim2](https://create.roblox.com/docs/reference/engine/datatypes/UDim2) Roblox datatype.
|
||||
|
||||
This implements all documented properties, methods & constructors of the UDim2 class as of March 2023.
|
||||
This implements all documented properties, methods & constructors of the `UDim2` class as of March 2023.
|
||||
*/
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct UDim2 {
|
||||
|
@ -18,52 +24,53 @@ pub struct UDim2 {
|
|||
pub(crate) y: UDim,
|
||||
}
|
||||
|
||||
impl UDim2 {
|
||||
pub(crate) fn make_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> {
|
||||
datatype_table.set(
|
||||
"fromScale",
|
||||
lua.create_function(|_, (x, y): (Option<f32>, Option<f32>)| {
|
||||
Ok(UDim2 {
|
||||
x: UDim::new(x.unwrap_or_default(), 0),
|
||||
y: UDim::new(y.unwrap_or_default(), 0),
|
||||
})
|
||||
})?,
|
||||
)?;
|
||||
datatype_table.set(
|
||||
"fromOffset",
|
||||
lua.create_function(|_, (x, y): (Option<i32>, Option<i32>)| {
|
||||
Ok(UDim2 {
|
||||
x: UDim::new(0f32, x.unwrap_or_default()),
|
||||
y: UDim::new(0f32, y.unwrap_or_default()),
|
||||
})
|
||||
})?,
|
||||
)?;
|
||||
impl LuaExportsTable<'_> for UDim2 {
|
||||
const EXPORT_NAME: &'static str = "UDim2";
|
||||
|
||||
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 = |_, (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<'lua> = (
|
||||
Option<LuaUserDataRef<'lua, UDim>>,
|
||||
Option<LuaUserDataRef<'lua, UDim>>,
|
||||
);
|
||||
type ArgsNums = (Option<f32>, Option<i32>, Option<f32>, Option<i32>);
|
||||
datatype_table.set(
|
||||
"new",
|
||||
lua.create_function(|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(),
|
||||
y: y.map(|y| *y).unwrap_or_default(),
|
||||
})
|
||||
} else if let Ok((sx, ox, sy, oy)) = ArgsNums::from_lua_multi(args, lua) {
|
||||
Ok(UDim2 {
|
||||
x: UDim::new(sx.unwrap_or_default(), ox.unwrap_or_default()),
|
||||
y: UDim::new(sy.unwrap_or_default(), oy.unwrap_or_default()),
|
||||
})
|
||||
} else {
|
||||
// FUTURE: Better error message here using given arg types
|
||||
Err(LuaError::RuntimeError(
|
||||
"Invalid arguments to constructor".to_string(),
|
||||
))
|
||||
}
|
||||
})?,
|
||||
)
|
||||
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(),
|
||||
y: y.map(|y| *y).unwrap_or_default(),
|
||||
})
|
||||
} else if let Ok((sx, ox, sy, oy)) = ArgsNums::from_lua_multi(args, lua) {
|
||||
Ok(UDim2 {
|
||||
x: UDim::new(sx.unwrap_or_default(), ox.unwrap_or_default()),
|
||||
y: UDim::new(sy.unwrap_or_default(), oy.unwrap_or_default()),
|
||||
})
|
||||
} else {
|
||||
// FUTURE: Better error message here using given arg types
|
||||
Err(LuaError::RuntimeError(
|
||||
"Invalid arguments to constructor".to_string(),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
TableBuilder::new(lua)?
|
||||
.with_function("fromOffset", udim2_from_offset)?
|
||||
.with_function("fromScale", udim2_from_scale)?
|
||||
.with_function("new", udim2_new)?
|
||||
.build_readonly()
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,10 @@ use glam::{Vec2, Vec3};
|
|||
use mlua::prelude::*;
|
||||
use rbx_dom_weak::types::Vector2 as DomVector2;
|
||||
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use crate::exports::LuaExportsTable;
|
||||
|
||||
use super::super::*;
|
||||
|
||||
/**
|
||||
|
@ -17,23 +21,24 @@ use super::super::*;
|
|||
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
||||
pub struct Vector2(pub Vec2);
|
||||
|
||||
impl Vector2 {
|
||||
pub(crate) fn make_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> {
|
||||
// Constants
|
||||
datatype_table.set("xAxis", Vector2(Vec2::X))?;
|
||||
datatype_table.set("yAxis", Vector2(Vec2::Y))?;
|
||||
datatype_table.set("zero", Vector2(Vec2::ZERO))?;
|
||||
datatype_table.set("one", Vector2(Vec2::ONE))?;
|
||||
// Constructors
|
||||
datatype_table.set(
|
||||
"new",
|
||||
lua.create_function(|_, (x, y): (Option<f32>, Option<f32>)| {
|
||||
Ok(Vector2(Vec2 {
|
||||
x: x.unwrap_or_default(),
|
||||
y: y.unwrap_or_default(),
|
||||
}))
|
||||
})?,
|
||||
)
|
||||
impl LuaExportsTable<'_> for Vector2 {
|
||||
const EXPORT_NAME: &'static str = "Vector2";
|
||||
|
||||
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(),
|
||||
}))
|
||||
};
|
||||
|
||||
TableBuilder::new(lua)?
|
||||
.with_value("xAxis", Vector2(Vec2::X))?
|
||||
.with_value("yAxis", Vector2(Vec2::Y))?
|
||||
.with_value("zero", Vector2(Vec2::ZERO))?
|
||||
.with_value("one", Vector2(Vec2::ONE))?
|
||||
.with_function("new", vector2_new)?
|
||||
.build_readonly()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,6 +52,9 @@ impl LuaUserData for Vector2 {
|
|||
|
||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
// Methods
|
||||
methods.add_method("Angle", |_, this, rhs: LuaUserDataRef<Vector2>| {
|
||||
Ok(this.0.angle_between(rhs.0))
|
||||
});
|
||||
methods.add_method("Cross", |_, this, rhs: LuaUserDataRef<Vector2>| {
|
||||
let this_v3 = Vec3::new(this.0.x, this.0.y, 0f32);
|
||||
let rhs_v3 = Vec3::new(rhs.0.x, rhs.0.y, 0f32);
|
||||
|
@ -55,6 +63,14 @@ impl LuaUserData for Vector2 {
|
|||
methods.add_method("Dot", |_, this, rhs: LuaUserDataRef<Vector2>| {
|
||||
Ok(this.0.dot(rhs.0))
|
||||
});
|
||||
methods.add_method(
|
||||
"FuzzyEq",
|
||||
|_, this, (rhs, epsilon): (LuaUserDataRef<Vector2>, f32)| {
|
||||
let eq_x = (rhs.0.x - this.0.x).abs() <= epsilon;
|
||||
let eq_y = (rhs.0.y - this.0.y).abs() <= epsilon;
|
||||
Ok(eq_x && eq_y)
|
||||
},
|
||||
);
|
||||
methods.add_method(
|
||||
"Lerp",
|
||||
|_, this, (rhs, alpha): (LuaUserDataRef<Vector2>, f32)| {
|
||||
|
@ -67,6 +83,10 @@ impl LuaUserData for Vector2 {
|
|||
methods.add_method("Min", |_, this, rhs: LuaUserDataRef<Vector2>| {
|
||||
Ok(Vector2(this.0.min(rhs.0)))
|
||||
});
|
||||
methods.add_method("Abs", |_, this, ()| Ok(Vector2(this.0.abs())));
|
||||
methods.add_method("Ceil", |_, this, ()| Ok(Vector2(this.0.ceil())));
|
||||
methods.add_method("Floor", |_, this, ()| Ok(Vector2(this.0.floor())));
|
||||
methods.add_method("Sign", |_, this, ()| Ok(Vector2(this.0.signum())));
|
||||
// Metamethods
|
||||
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
||||
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
||||
|
@ -75,6 +95,7 @@ impl LuaUserData for Vector2 {
|
|||
methods.add_meta_method(LuaMetaMethod::Sub, userdata_impl_sub);
|
||||
methods.add_meta_method(LuaMetaMethod::Mul, userdata_impl_mul_f32);
|
||||
methods.add_meta_method(LuaMetaMethod::Div, userdata_impl_div_f32);
|
||||
methods.add_meta_method(LuaMetaMethod::IDiv, userdata_impl_idiv_f32);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -133,6 +154,20 @@ impl ops::Div<f32> for Vector2 {
|
|||
}
|
||||
}
|
||||
|
||||
impl IDiv for Vector2 {
|
||||
type Output = Vector2;
|
||||
fn idiv(self, rhs: Self) -> Self::Output {
|
||||
Self((self.0 / rhs.0).floor())
|
||||
}
|
||||
}
|
||||
|
||||
impl IDiv<f32> for Vector2 {
|
||||
type Output = Vector2;
|
||||
fn idiv(self, rhs: f32) -> Self::Output {
|
||||
Self((self.0 / rhs).floor())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DomVector2> for Vector2 {
|
||||
fn from(v: DomVector2) -> Self {
|
||||
Vector2(Vec2 { x: v.x, y: v.y })
|
|
@ -5,6 +5,10 @@ use glam::IVec2;
|
|||
use mlua::prelude::*;
|
||||
use rbx_dom_weak::types::Vector2int16 as DomVector2int16;
|
||||
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use crate::exports::LuaExportsTable;
|
||||
|
||||
use super::super::*;
|
||||
|
||||
/**
|
||||
|
@ -17,17 +21,20 @@ use super::super::*;
|
|||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Vector2int16(pub IVec2);
|
||||
|
||||
impl Vector2int16 {
|
||||
pub(crate) fn make_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> {
|
||||
datatype_table.set(
|
||||
"new",
|
||||
lua.create_function(|_, (x, y): (Option<i16>, Option<i16>)| {
|
||||
Ok(Vector2int16(IVec2 {
|
||||
x: x.unwrap_or_default() as i32,
|
||||
y: y.unwrap_or_default() as i32,
|
||||
}))
|
||||
})?,
|
||||
)
|
||||
impl LuaExportsTable<'_> for Vector2int16 {
|
||||
const EXPORT_NAME: &'static str = "Vector2int16";
|
||||
|
||||
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,
|
||||
}))
|
||||
};
|
||||
|
||||
TableBuilder::new(lua)?
|
||||
.with_function("new", vector2int16_new)?
|
||||
.build_readonly()
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,10 @@ use glam::Vec3;
|
|||
use mlua::prelude::*;
|
||||
use rbx_dom_weak::types::Vector3 as DomVector3;
|
||||
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use crate::{datatypes::util::round_float_decimal, exports::LuaExportsTable};
|
||||
|
||||
use super::{super::*, EnumItem};
|
||||
|
||||
/**
|
||||
|
@ -20,74 +24,71 @@ use super::{super::*, EnumItem};
|
|||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Vector3(pub Vec3);
|
||||
|
||||
impl Vector3 {
|
||||
pub(crate) fn make_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> {
|
||||
// Constants
|
||||
datatype_table.set("xAxis", Vector3(Vec3::X))?;
|
||||
datatype_table.set("yAxis", Vector3(Vec3::Y))?;
|
||||
datatype_table.set("zAxis", Vector3(Vec3::Z))?;
|
||||
datatype_table.set("zero", Vector3(Vec3::ZERO))?;
|
||||
datatype_table.set("one", Vector3(Vec3::ONE))?;
|
||||
// Constructors
|
||||
datatype_table.set(
|
||||
"fromAxis",
|
||||
lua.create_function(|_, normal_id: LuaUserDataRef<EnumItem>| {
|
||||
if normal_id.parent.desc.name == "Axis" {
|
||||
Ok(match normal_id.name.as_str() {
|
||||
"X" => Vector3(Vec3::X),
|
||||
"Y" => Vector3(Vec3::Y),
|
||||
"Z" => Vector3(Vec3::Z),
|
||||
name => {
|
||||
return Err(LuaError::RuntimeError(format!(
|
||||
"Axis '{}' is not known",
|
||||
name
|
||||
)))
|
||||
}
|
||||
})
|
||||
} else {
|
||||
Err(LuaError::RuntimeError(format!(
|
||||
"EnumItem must be a Axis, got {}",
|
||||
normal_id.parent.desc.name
|
||||
)))
|
||||
}
|
||||
})?,
|
||||
)?;
|
||||
datatype_table.set(
|
||||
"fromNormalId",
|
||||
lua.create_function(|_, normal_id: LuaUserDataRef<EnumItem>| {
|
||||
if normal_id.parent.desc.name == "NormalId" {
|
||||
Ok(match normal_id.name.as_str() {
|
||||
"Left" => Vector3(Vec3::X),
|
||||
"Top" => Vector3(Vec3::Y),
|
||||
"Front" => Vector3(-Vec3::Z),
|
||||
"Right" => Vector3(-Vec3::X),
|
||||
"Bottom" => Vector3(-Vec3::Y),
|
||||
"Back" => Vector3(Vec3::Z),
|
||||
name => {
|
||||
return Err(LuaError::RuntimeError(format!(
|
||||
"NormalId '{}' is not known",
|
||||
name
|
||||
)))
|
||||
}
|
||||
})
|
||||
} else {
|
||||
Err(LuaError::RuntimeError(format!(
|
||||
"EnumItem must be a NormalId, got {}",
|
||||
normal_id.parent.desc.name
|
||||
)))
|
||||
}
|
||||
})?,
|
||||
)?;
|
||||
datatype_table.set(
|
||||
"new",
|
||||
lua.create_function(|_, (x, y, z): (Option<f32>, Option<f32>, Option<f32>)| {
|
||||
Ok(Vector3(Vec3 {
|
||||
x: x.unwrap_or_default(),
|
||||
y: y.unwrap_or_default(),
|
||||
z: z.unwrap_or_default(),
|
||||
}))
|
||||
})?,
|
||||
)
|
||||
impl LuaExportsTable<'_> for Vector3 {
|
||||
const EXPORT_NAME: &'static str = "Vector3";
|
||||
|
||||
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),
|
||||
"Y" => Vector3(Vec3::Y),
|
||||
"Z" => Vector3(Vec3::Z),
|
||||
name => {
|
||||
return Err(LuaError::RuntimeError(format!(
|
||||
"Axis '{name}' is not known",
|
||||
)))
|
||||
}
|
||||
})
|
||||
} else {
|
||||
Err(LuaError::RuntimeError(format!(
|
||||
"EnumItem must be a Axis, got {}",
|
||||
normal_id.parent.desc.name
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
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),
|
||||
"Top" => Vector3(Vec3::Y),
|
||||
"Front" => Vector3(-Vec3::Z),
|
||||
"Right" => Vector3(-Vec3::X),
|
||||
"Bottom" => Vector3(-Vec3::Y),
|
||||
"Back" => Vector3(Vec3::Z),
|
||||
name => {
|
||||
return Err(LuaError::RuntimeError(format!(
|
||||
"NormalId '{name}' is not known",
|
||||
)))
|
||||
}
|
||||
})
|
||||
} else {
|
||||
Err(LuaError::RuntimeError(format!(
|
||||
"EnumItem must be a NormalId, got {}",
|
||||
normal_id.parent.desc.name
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
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(),
|
||||
z: z.unwrap_or_default(),
|
||||
}))
|
||||
};
|
||||
|
||||
TableBuilder::new(lua)?
|
||||
.with_value("xAxis", Vector3(Vec3::X))?
|
||||
.with_value("yAxis", Vector3(Vec3::Y))?
|
||||
.with_value("zAxis", Vector3(Vec3::Z))?
|
||||
.with_value("zero", Vector3(Vec3::ZERO))?
|
||||
.with_value("one", Vector3(Vec3::ONE))?
|
||||
.with_function("fromAxis", vector3_from_axis)?
|
||||
.with_function("fromNormalId", vector3_from_normal_id)?
|
||||
.with_function("new", vector3_new)?
|
||||
.build_readonly()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -132,6 +133,10 @@ impl LuaUserData for Vector3 {
|
|||
methods.add_method("Min", |_, this, rhs: LuaUserDataRef<Vector3>| {
|
||||
Ok(Vector3(this.0.min(rhs.0)))
|
||||
});
|
||||
methods.add_method("Abs", |_, this, ()| Ok(Vector3(this.0.abs())));
|
||||
methods.add_method("Ceil", |_, this, ()| Ok(Vector3(this.0.ceil())));
|
||||
methods.add_method("Floor", |_, this, ()| Ok(Vector3(this.0.floor())));
|
||||
methods.add_method("Sign", |_, this, ()| Ok(Vector3(this.0.signum())));
|
||||
// Metamethods
|
||||
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
||||
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
||||
|
@ -140,6 +145,7 @@ impl LuaUserData for Vector3 {
|
|||
methods.add_meta_method(LuaMetaMethod::Sub, userdata_impl_sub);
|
||||
methods.add_meta_method(LuaMetaMethod::Mul, userdata_impl_mul_f32);
|
||||
methods.add_meta_method(LuaMetaMethod::Div, userdata_impl_div_f32);
|
||||
methods.add_meta_method(LuaMetaMethod::IDiv, userdata_impl_idiv_f32);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -198,6 +204,20 @@ impl ops::Div<f32> for Vector3 {
|
|||
}
|
||||
}
|
||||
|
||||
impl IDiv for Vector3 {
|
||||
type Output = Vector3;
|
||||
fn idiv(self, rhs: Self) -> Self::Output {
|
||||
Self((self.0 / rhs.0).floor())
|
||||
}
|
||||
}
|
||||
|
||||
impl IDiv<f32> for Vector3 {
|
||||
type Output = Vector3;
|
||||
fn idiv(self, rhs: f32) -> Self::Output {
|
||||
Self((self.0 / rhs).floor())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DomVector3> for Vector3 {
|
||||
fn from(v: DomVector3) -> Self {
|
||||
Vector3(Vec3 {
|
||||
|
@ -211,9 +231,9 @@ impl From<DomVector3> for Vector3 {
|
|||
impl From<Vector3> for DomVector3 {
|
||||
fn from(v: Vector3) -> Self {
|
||||
DomVector3 {
|
||||
x: v.0.x,
|
||||
y: v.0.y,
|
||||
z: v.0.z,
|
||||
x: round_float_decimal(v.0.x),
|
||||
y: round_float_decimal(v.0.y),
|
||||
z: round_float_decimal(v.0.z),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,6 +5,10 @@ use glam::IVec3;
|
|||
use mlua::prelude::*;
|
||||
use rbx_dom_weak::types::Vector3int16 as DomVector3int16;
|
||||
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use crate::exports::LuaExportsTable;
|
||||
|
||||
use super::super::*;
|
||||
|
||||
/**
|
||||
|
@ -17,18 +21,21 @@ use super::super::*;
|
|||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub struct Vector3int16(pub IVec3);
|
||||
|
||||
impl Vector3int16 {
|
||||
pub(crate) fn make_table(lua: &Lua, datatype_table: &LuaTable) -> LuaResult<()> {
|
||||
datatype_table.set(
|
||||
"new",
|
||||
lua.create_function(|_, (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,
|
||||
z: z.unwrap_or_default() as i32,
|
||||
}))
|
||||
})?,
|
||||
)
|
||||
impl LuaExportsTable<'_> for Vector3int16 {
|
||||
const EXPORT_NAME: &'static str = "Vector3int16";
|
||||
|
||||
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,
|
||||
z: z.unwrap_or_default() as i32,
|
||||
}))
|
||||
};
|
||||
|
||||
TableBuilder::new(lua)?
|
||||
.with_function("new", vector3int16_new)?
|
||||
.build_readonly()
|
||||
}
|
||||
}
|
||||
|
16
crates/lune-roblox/src/datatypes/util.rs
Normal file
|
@ -0,0 +1,16 @@
|
|||
// HACK: We round to the nearest Very Small Decimal
|
||||
// to reduce writing out floating point accumulation
|
||||
// errors to files (mostly relevant for xml formats)
|
||||
const ROUNDING: usize = 65_536; // 2 ^ 16
|
||||
|
||||
pub fn round_float_decimal(value: f32) -> f32 {
|
||||
let place = ROUNDING as f32;
|
||||
|
||||
// Round only the fractional part, we do not want to
|
||||
// lose any float precision in case a user for some
|
||||
// reason has very very large float numbers in files
|
||||
let whole = value.trunc();
|
||||
let fract = (value.fract() * place).round() / place;
|
||||
|
||||
whole + fract
|
||||
}
|
|
@ -58,13 +58,14 @@ impl DocumentKind {
|
|||
|
||||
Returns `None` if the given dom is empty and as such can not have its kind inferred.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn from_weak_dom(dom: &WeakDom) -> Option<Self> {
|
||||
let mut has_top_level_child = false;
|
||||
let mut has_top_level_service = false;
|
||||
for child_ref in dom.root().children() {
|
||||
if let Some(child_inst) = dom.get_by_ref(*child_ref) {
|
||||
has_top_level_child = true;
|
||||
if class_is_a_service(&child_inst.class).unwrap_or(false) {
|
||||
if class_is_a_service(child_inst.class).unwrap_or(false) {
|
||||
has_top_level_service = true;
|
||||
break;
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
use rbx_dom_weak::{InstanceBuilder as DomInstanceBuilder, WeakDom};
|
||||
use rbx_dom_weak::{types::Ref as DomRef, InstanceBuilder as DomInstanceBuilder, WeakDom};
|
||||
use rbx_xml::{
|
||||
DecodeOptions as XmlDecodeOptions, DecodePropertyBehavior as XmlDecodePropertyBehavior,
|
||||
EncodeOptions as XmlEncodeOptions, EncodePropertyBehavior as XmlEncodePropertyBehavior,
|
||||
|
@ -78,6 +78,7 @@ impl Document {
|
|||
| Model | Binary | `rbxm` |
|
||||
| Model | Xml | `rbxmx` |
|
||||
*/
|
||||
#[must_use]
|
||||
#[rustfmt::skip]
|
||||
pub fn canonical_extension(kind: DocumentKind, format: DocumentFormat) -> &'static str {
|
||||
match (kind, format) {
|
||||
|
@ -113,6 +114,10 @@ impl Document {
|
|||
Note that detection of model vs place file is heavily dependent on the structure
|
||||
of the file, and a model file with services in it will detect as a place file, so
|
||||
if possible using [`Document::from_bytes`] with an explicit kind should be preferred.
|
||||
|
||||
# Errors
|
||||
|
||||
Errors if the given bytes are not a valid roblox file.
|
||||
*/
|
||||
pub fn from_bytes_auto(bytes: impl AsRef<[u8]>) -> DocumentResult<Self> {
|
||||
let (format, dom) = Self::from_bytes_inner(bytes)?;
|
||||
|
@ -125,6 +130,10 @@ impl Document {
|
|||
|
||||
This will automatically handle and detect if the document
|
||||
should be decoded using a roblox binary or roblox xml format.
|
||||
|
||||
# Errors
|
||||
|
||||
Errors if the given bytes are not a valid roblox file or not of the given kind.
|
||||
*/
|
||||
pub fn from_bytes(bytes: impl AsRef<[u8]>, kind: DocumentKind) -> DocumentResult<Self> {
|
||||
let (format, dom) = Self::from_bytes_inner(bytes)?;
|
||||
|
@ -138,6 +147,10 @@ impl Document {
|
|||
This will use the same format that the document was created
|
||||
with, meaning if the document is a binary document the output
|
||||
will be binary, and vice versa for xml and other future formats.
|
||||
|
||||
# Errors
|
||||
|
||||
Errors if the document can not be encoded.
|
||||
*/
|
||||
pub fn to_bytes(&self) -> DocumentResult<Vec<u8>> {
|
||||
self.to_bytes_with_format(self.format)
|
||||
|
@ -146,6 +159,10 @@ impl Document {
|
|||
/**
|
||||
Encodes the document as a vector of bytes, to
|
||||
be written to a file or sent over the network.
|
||||
|
||||
# Errors
|
||||
|
||||
Errors if the document can not be encoded.
|
||||
*/
|
||||
pub fn to_bytes_with_format(&self, format: DocumentFormat) -> DocumentResult<Vec<u8>> {
|
||||
let mut bytes = Vec::new();
|
||||
|
@ -172,6 +189,7 @@ impl Document {
|
|||
/**
|
||||
Gets the kind this document was created with.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn kind(&self) -> DocumentKind {
|
||||
self.kind
|
||||
}
|
||||
|
@ -179,6 +197,7 @@ impl Document {
|
|||
/**
|
||||
Gets the format this document was created with.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn format(&self) -> DocumentFormat {
|
||||
self.format
|
||||
}
|
||||
|
@ -186,14 +205,17 @@ impl Document {
|
|||
/**
|
||||
Gets the file extension for this document.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn extension(&self) -> &'static str {
|
||||
Self::canonical_extension(self.kind, self.format)
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a DataModel instance out of this place document.
|
||||
Creates a `DataModel` instance out of this place document.
|
||||
|
||||
Will error if the document is not a place.
|
||||
# Errors
|
||||
|
||||
Errors if the document is not a place.
|
||||
*/
|
||||
pub fn into_data_model_instance(mut self) -> DocumentResult<Instance> {
|
||||
if self.kind != DocumentKind::Place {
|
||||
|
@ -219,7 +241,9 @@ impl Document {
|
|||
/**
|
||||
Creates an array of instances out of this model document.
|
||||
|
||||
Will error if the document is not a model.
|
||||
# Errors
|
||||
|
||||
Errors if the document is not a model.
|
||||
*/
|
||||
pub fn into_instance_array(mut self) -> DocumentResult<Vec<Instance>> {
|
||||
if self.kind != DocumentKind::Model {
|
||||
|
@ -237,9 +261,11 @@ impl Document {
|
|||
}
|
||||
|
||||
/**
|
||||
Creates a place document out of a DataModel instance.
|
||||
Creates a place document out of a `DataModel` instance.
|
||||
|
||||
Will error if the instance is not a DataModel.
|
||||
# Errors
|
||||
|
||||
Errors if the instance is not a `DataModel`.
|
||||
*/
|
||||
pub fn from_data_model_instance(i: Instance) -> DocumentResult<Self> {
|
||||
if i.get_class_name() != data_model::CLASS_NAME {
|
||||
|
@ -247,11 +273,13 @@ impl Document {
|
|||
}
|
||||
|
||||
let mut dom = WeakDom::new(DomInstanceBuilder::new("ROOT"));
|
||||
let children: Vec<DomRef> = i
|
||||
.get_children()
|
||||
.iter()
|
||||
.map(|instance| instance.dom_ref)
|
||||
.collect();
|
||||
|
||||
for data_model_child in i.get_children() {
|
||||
data_model_child.clone_into_external_dom(&mut dom);
|
||||
}
|
||||
|
||||
Instance::clone_multiple_into_external_dom(&children, &mut dom);
|
||||
postprocess_dom_for_place(&mut dom);
|
||||
|
||||
Ok(Self {
|
||||
|
@ -264,7 +292,9 @@ impl Document {
|
|||
/**
|
||||
Creates a model document out of an array of instances.
|
||||
|
||||
Will error if any of the instances is a DataModel.
|
||||
# Errors
|
||||
|
||||
Errors if any of the instances is a `DataModel`.
|
||||
*/
|
||||
pub fn from_instance_array(v: Vec<Instance>) -> DocumentResult<Self> {
|
||||
for i in &v {
|
||||
|
@ -274,11 +304,9 @@ impl Document {
|
|||
}
|
||||
|
||||
let mut dom = WeakDom::new(DomInstanceBuilder::new("ROOT"));
|
||||
let instances: Vec<DomRef> = v.iter().map(|instance| instance.dom_ref).collect();
|
||||
|
||||
for instance in v {
|
||||
instance.clone_into_external_dom(&mut dom);
|
||||
}
|
||||
|
||||
Instance::clone_multiple_into_external_dom(&instances, &mut dom);
|
||||
postprocess_dom_for_model(&mut dom);
|
||||
|
||||
Ok(Self {
|
|
@ -1,6 +1,6 @@
|
|||
use rbx_dom_weak::{
|
||||
types::{Ref as DomRef, VariantType as DomType},
|
||||
Instance as DomInstance, WeakDom,
|
||||
ustr, 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("ScriptGuid");
|
||||
if class_is_a(inst.class, "LuaSourceContainer").unwrap_or(false) {
|
||||
inst.properties.remove(&ustr("ScriptGuid"));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -41,7 +41,8 @@ where
|
|||
}
|
||||
|
||||
fn remove_matching_prop(inst: &mut DomInstance, ty: DomType, name: &'static str) {
|
||||
if inst.properties.get(name).map_or(false, |u| u.ty() == ty) {
|
||||
let name = &ustr(name);
|
||||
if inst.properties.get(name).is_some_and(|u| u.ty() == ty) {
|
||||
inst.properties.remove(name);
|
||||
}
|
||||
}
|
68
crates/lune-roblox/src/exports.rs
Normal file
|
@ -0,0 +1,68 @@
|
|||
use mlua::prelude::*;
|
||||
|
||||
/**
|
||||
Trait for any item that should be exported as part of the `roblox` built-in library.
|
||||
|
||||
This may be an enum or a struct that should export constants and/or constructs.
|
||||
|
||||
### Example usage
|
||||
|
||||
```rs
|
||||
use mlua::prelude::*;
|
||||
|
||||
struct MyType(usize);
|
||||
|
||||
impl MyType {
|
||||
pub fn new(n: usize) -> Self {
|
||||
Self(n)
|
||||
}
|
||||
}
|
||||
|
||||
impl LuaExportsTable<'_> for MyType {
|
||||
const EXPORT_NAME: &'static str = "MyType";
|
||||
|
||||
fn create_exports_table(lua: &Lua) -> LuaResult<LuaTable> {
|
||||
let my_type_new = |lua, n: Option<usize>| {
|
||||
Self::new(n.unwrap_or_default())
|
||||
};
|
||||
|
||||
TableBuilder::new(lua)?
|
||||
.with_function("new", my_type_new)?
|
||||
.build_readonly()
|
||||
}
|
||||
}
|
||||
|
||||
impl LuaUserData for MyType {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
*/
|
||||
pub trait LuaExportsTable<'lua> {
|
||||
const EXPORT_NAME: &'static str;
|
||||
|
||||
fn create_exports_table(lua: &'lua Lua) -> LuaResult<LuaTable<'lua>>;
|
||||
}
|
||||
|
||||
/**
|
||||
Exports a single item that implements the [`LuaExportsTable`] trait.
|
||||
|
||||
Returns the name of the export, as well as the export table.
|
||||
|
||||
### Example usage
|
||||
|
||||
```rs
|
||||
let lua: mlua::Lua::new();
|
||||
|
||||
let (name1, table1) = export::<Type1>(lua)?;
|
||||
let (name2, table2) = export::<Type2>(lua)?;
|
||||
```
|
||||
*/
|
||||
pub fn export<'lua, T>(lua: &'lua Lua) -> LuaResult<(&'static str, LuaValue<'lua>)>
|
||||
where
|
||||
T: LuaExportsTable<'lua>,
|
||||
{
|
||||
Ok((
|
||||
T::EXPORT_NAME,
|
||||
<T as LuaExportsTable>::create_exports_table(lua)?.into_lua(lua)?,
|
||||
))
|
||||
}
|
363
crates/lune-roblox/src/instance/base.rs
Normal file
|
@ -0,0 +1,363 @@
|
|||
#![allow(clippy::items_after_statements)]
|
||||
|
||||
use mlua::prelude::*;
|
||||
|
||||
use rbx_dom_weak::{
|
||||
types::{Variant as DomValue, VariantType as DomType},
|
||||
Instance as DomInstance,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
datatypes::{
|
||||
attributes::{ensure_valid_attribute_name, ensure_valid_attribute_value},
|
||||
conversion::{DomValueToLua, LuaToDomValue},
|
||||
types::EnumItem,
|
||||
userdata_impl_eq, userdata_impl_to_string,
|
||||
},
|
||||
shared::instance::{class_is_a, find_property_info},
|
||||
};
|
||||
|
||||
use super::{data_model, registry::InstanceRegistry, Instance};
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub fn add_methods<'lua, M: LuaUserDataMethods<'lua, Instance>>(m: &mut M) {
|
||||
m.add_meta_method(LuaMetaMethod::ToString, |lua, this, ()| {
|
||||
ensure_not_destroyed(this)?;
|
||||
userdata_impl_to_string(lua, this, ())
|
||||
});
|
||||
m.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
||||
m.add_meta_method(LuaMetaMethod::Index, instance_property_get);
|
||||
m.add_meta_method_mut(LuaMetaMethod::NewIndex, instance_property_set);
|
||||
m.add_method("Clone", |lua, this, ()| {
|
||||
ensure_not_destroyed(this)?;
|
||||
this.clone_instance().into_lua(lua)
|
||||
});
|
||||
m.add_method_mut("Destroy", |_, this, ()| {
|
||||
this.destroy();
|
||||
Ok(())
|
||||
});
|
||||
m.add_method_mut("ClearAllChildren", |_, this, ()| {
|
||||
this.clear_all_children();
|
||||
Ok(())
|
||||
});
|
||||
m.add_method("GetChildren", |lua, this, ()| {
|
||||
ensure_not_destroyed(this)?;
|
||||
this.get_children().into_lua(lua)
|
||||
});
|
||||
m.add_method("GetDescendants", |lua, this, ()| {
|
||||
ensure_not_destroyed(this)?;
|
||||
this.get_descendants().into_lua(lua)
|
||||
});
|
||||
m.add_method("GetFullName", |lua, this, ()| {
|
||||
ensure_not_destroyed(this)?;
|
||||
this.get_full_name().into_lua(lua)
|
||||
});
|
||||
m.add_method("GetDebugId", |lua, this, ()| {
|
||||
this.dom_ref.to_string().into_lua(lua)
|
||||
});
|
||||
m.add_method("FindFirstAncestor", |lua, this, name: String| {
|
||||
ensure_not_destroyed(this)?;
|
||||
this.find_ancestor(|child| child.name == name).into_lua(lua)
|
||||
});
|
||||
m.add_method(
|
||||
"FindFirstAncestorOfClass",
|
||||
|lua, this, class_name: String| {
|
||||
ensure_not_destroyed(this)?;
|
||||
this.find_ancestor(|child| child.class == class_name)
|
||||
.into_lua(lua)
|
||||
},
|
||||
);
|
||||
m.add_method(
|
||||
"FindFirstAncestorWhichIsA",
|
||||
|lua, this, class_name: String| {
|
||||
ensure_not_destroyed(this)?;
|
||||
this.find_ancestor(|child| class_is_a(child.class, &class_name).unwrap_or(false))
|
||||
.into_lua(lua)
|
||||
},
|
||||
);
|
||||
m.add_method(
|
||||
"FindFirstChild",
|
||||
|lua, this, (name, recursive): (String, Option<bool>)| {
|
||||
ensure_not_destroyed(this)?;
|
||||
let predicate = |child: &DomInstance| child.name == name;
|
||||
if matches!(recursive, Some(true)) {
|
||||
this.find_descendant(predicate).into_lua(lua)
|
||||
} else {
|
||||
this.find_child(predicate).into_lua(lua)
|
||||
}
|
||||
},
|
||||
);
|
||||
m.add_method(
|
||||
"FindFirstChildOfClass",
|
||||
|lua, this, (class_name, recursive): (String, Option<bool>)| {
|
||||
ensure_not_destroyed(this)?;
|
||||
let predicate = |child: &DomInstance| child.class == class_name;
|
||||
if matches!(recursive, Some(true)) {
|
||||
this.find_descendant(predicate).into_lua(lua)
|
||||
} else {
|
||||
this.find_child(predicate).into_lua(lua)
|
||||
}
|
||||
},
|
||||
);
|
||||
m.add_method(
|
||||
"FindFirstChildWhichIsA",
|
||||
|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);
|
||||
if matches!(recursive, Some(true)) {
|
||||
this.find_descendant(predicate).into_lua(lua)
|
||||
} else {
|
||||
this.find_child(predicate).into_lua(lua)
|
||||
}
|
||||
},
|
||||
);
|
||||
m.add_method("IsA", |_, this, class_name: String| {
|
||||
Ok(class_is_a(this.class_name, class_name).unwrap_or(false))
|
||||
});
|
||||
m.add_method(
|
||||
"IsAncestorOf",
|
||||
|_, this, instance: LuaUserDataRef<Instance>| {
|
||||
ensure_not_destroyed(this)?;
|
||||
Ok(instance
|
||||
.find_ancestor(|ancestor| ancestor.referent() == this.dom_ref)
|
||||
.is_some())
|
||||
},
|
||||
);
|
||||
m.add_method(
|
||||
"IsDescendantOf",
|
||||
|_, this, instance: LuaUserDataRef<Instance>| {
|
||||
ensure_not_destroyed(this)?;
|
||||
Ok(this
|
||||
.find_ancestor(|ancestor| ancestor.referent() == instance.dom_ref)
|
||||
.is_some())
|
||||
},
|
||||
);
|
||||
m.add_method("GetAttribute", |lua, this, name: String| {
|
||||
ensure_not_destroyed(this)?;
|
||||
match this.get_attribute(name) {
|
||||
Some(attribute) => Ok(LuaValue::dom_value_to_lua(lua, &attribute)?),
|
||||
None => Ok(LuaValue::Nil),
|
||||
}
|
||||
});
|
||||
m.add_method("GetAttributes", |lua, this, ()| {
|
||||
ensure_not_destroyed(this)?;
|
||||
let attributes = this.get_attributes();
|
||||
let tab = lua.create_table_with_capacity(0, attributes.len())?;
|
||||
for (key, value) in attributes {
|
||||
tab.set(key, LuaValue::dom_value_to_lua(lua, &value)?)?;
|
||||
}
|
||||
Ok(tab)
|
||||
});
|
||||
m.add_method(
|
||||
"SetAttribute",
|
||||
|lua, this, (attribute_name, lua_value): (String, LuaValue)| {
|
||||
ensure_not_destroyed(this)?;
|
||||
ensure_valid_attribute_name(&attribute_name)?;
|
||||
if lua_value.is_nil() || lua_value.is_null() {
|
||||
this.remove_attribute(attribute_name);
|
||||
Ok(())
|
||||
} else {
|
||||
match lua_value.lua_to_dom_value(lua, None) {
|
||||
Ok(dom_value) => {
|
||||
ensure_valid_attribute_value(&dom_value)?;
|
||||
this.set_attribute(attribute_name, dom_value);
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
m.add_method("GetTags", |_, this, ()| {
|
||||
ensure_not_destroyed(this)?;
|
||||
Ok(this.get_tags())
|
||||
});
|
||||
m.add_method("HasTag", |_, this, tag: String| {
|
||||
ensure_not_destroyed(this)?;
|
||||
Ok(this.has_tag(tag))
|
||||
});
|
||||
m.add_method("AddTag", |_, this, tag: String| {
|
||||
ensure_not_destroyed(this)?;
|
||||
this.add_tag(tag);
|
||||
Ok(())
|
||||
});
|
||||
m.add_method("RemoveTag", |_, this, tag: String| {
|
||||
ensure_not_destroyed(this)?;
|
||||
this.remove_tag(tag);
|
||||
Ok(())
|
||||
});
|
||||
}
|
||||
|
||||
fn ensure_not_destroyed(inst: &Instance) -> LuaResult<()> {
|
||||
if inst.is_destroyed() {
|
||||
Err(LuaError::RuntimeError(
|
||||
"Instance has been destroyed".to_string(),
|
||||
))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Gets a property value for an instance.
|
||||
|
||||
Getting a value does the following:
|
||||
|
||||
1. Check if it is a special property like "ClassName", "Name" or "Parent"
|
||||
2. Check if a property exists for the wanted name
|
||||
2a. Get an existing instance property OR
|
||||
2b. Get a property from a known default value
|
||||
3. Get a current child of the instance
|
||||
4. No valid property or instance found, throw error
|
||||
*/
|
||||
fn instance_property_get<'lua>(
|
||||
lua: &'lua Lua,
|
||||
this: &Instance,
|
||||
prop_name: String,
|
||||
) -> LuaResult<LuaValue<'lua>> {
|
||||
match prop_name.as_str() {
|
||||
"ClassName" => return this.get_class_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(prop) = this.get_property(&prop_name) {
|
||||
if let DomValue::Enum(enum_value) = prop {
|
||||
let enum_name = info.enum_name.ok_or_else(|| {
|
||||
LuaError::RuntimeError(format!(
|
||||
"Failed to get property '{prop_name}' - encountered unknown enum",
|
||||
))
|
||||
})?;
|
||||
EnumItem::from_enum_name_and_value(&enum_name, enum_value.to_u32())
|
||||
.ok_or_else(|| {
|
||||
LuaError::RuntimeError(format!(
|
||||
"Failed to get property '{}' - Enum.{} does not contain numeric value {}",
|
||||
prop_name, enum_name, enum_value.to_u32()
|
||||
))
|
||||
})?
|
||||
.into_lua(lua)
|
||||
} else {
|
||||
Ok(LuaValue::dom_value_to_lua(lua, &prop)?)
|
||||
}
|
||||
} else if let (Some(enum_name), Some(enum_value)) = (info.enum_name, info.enum_default) {
|
||||
EnumItem::from_enum_name_and_value(&enum_name, enum_value)
|
||||
.ok_or_else(|| {
|
||||
LuaError::RuntimeError(format!(
|
||||
"Failed to get property '{prop_name}' - Enum.{enum_name} does not contain numeric value {enum_value}",
|
||||
))
|
||||
})?
|
||||
.into_lua(lua)
|
||||
} else if let Some(prop_default) = info.value_default {
|
||||
Ok(LuaValue::dom_value_to_lua(lua, prop_default)?)
|
||||
} else if info.value_type.is_some() {
|
||||
if info.value_type == Some(DomType::Ref) {
|
||||
Ok(LuaValue::Nil)
|
||||
} else {
|
||||
Err(LuaError::RuntimeError(format!(
|
||||
"Failed to get property '{prop_name}' - missing default value",
|
||||
)))
|
||||
}
|
||||
} else {
|
||||
Err(LuaError::RuntimeError(format!(
|
||||
"Failed to get property '{prop_name}' - malformed property info",
|
||||
)))
|
||||
}
|
||||
} else if let Some(inst) = this.find_child(|inst| inst.name == prop_name) {
|
||||
Ok(LuaValue::UserData(lua.create_userdata(inst)?))
|
||||
} else if let Some(getter) = InstanceRegistry::find_property_getter(lua, this, &prop_name) {
|
||||
getter.call(*this)
|
||||
} else if let Some(method) = InstanceRegistry::find_method(lua, this, &prop_name) {
|
||||
Ok(LuaValue::Function(method))
|
||||
} else {
|
||||
Err(LuaError::RuntimeError(format!(
|
||||
"{prop_name} is not a valid member of {this}",
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Sets a property value for an instance.
|
||||
|
||||
Setting a value does the following:
|
||||
|
||||
1. Check if it is a special property like "ClassName", "Name" or "Parent"
|
||||
2. Check if a property exists for the wanted name
|
||||
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: &'lua Lua,
|
||||
this: &mut Instance,
|
||||
(prop_name, prop_value): (String, LuaValue<'lua>),
|
||||
) -> LuaResult<()> {
|
||||
ensure_not_destroyed(this)?;
|
||||
|
||||
match prop_name.as_str() {
|
||||
"ClassName" => {
|
||||
return Err(LuaError::RuntimeError(
|
||||
"Failed to set ClassName - property is read-only".to_string(),
|
||||
));
|
||||
}
|
||||
"Name" => {
|
||||
let name = String::from_lua(prop_value, lua)?;
|
||||
this.set_name(name);
|
||||
return Ok(());
|
||||
}
|
||||
"Parent" => {
|
||||
if this.get_class_name() == data_model::CLASS_NAME {
|
||||
return Err(LuaError::RuntimeError(
|
||||
"Failed to set Parent - DataModel can not be reparented".to_string(),
|
||||
));
|
||||
}
|
||||
type Parent<'lua> = Option<LuaUserDataRef<'lua, Instance>>;
|
||||
let parent = Parent::from_lua(prop_value, lua)?;
|
||||
this.set_parent(parent.map(|p| *p));
|
||||
return Ok(());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
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()));
|
||||
Ok(())
|
||||
}
|
||||
Ok(given_enum) => Err(LuaError::RuntimeError(format!(
|
||||
"Failed to set property '{}' - expected Enum.{}, got Enum.{}",
|
||||
prop_name, enum_name, given_enum.parent.desc.name
|
||||
))),
|
||||
Err(e) => Err(e),
|
||||
}
|
||||
} else if let Some(dom_type) = info.value_type {
|
||||
match prop_value.lua_to_dom_value(lua, Some(dom_type)) {
|
||||
Ok(dom_value) => {
|
||||
this.set_property(prop_name, dom_value);
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
} else {
|
||||
Err(LuaError::RuntimeError(format!(
|
||||
"Failed to set property '{prop_name}' - malformed property info",
|
||||
)))
|
||||
}
|
||||
} else if let Some(setter) = InstanceRegistry::find_property_setter(lua, this, &prop_name) {
|
||||
setter.call((*this, prop_value))
|
||||
} else {
|
||||
Err(LuaError::RuntimeError(format!(
|
||||
"{prop_name} is not a valid member of {this}",
|
||||
)))
|
||||
}
|
||||
}
|
|
@ -12,8 +12,8 @@ use super::Instance;
|
|||
|
||||
pub const CLASS_NAME: &str = "DataModel";
|
||||
|
||||
pub fn add_fields<'lua, M: LuaUserDataFields<'lua, Instance>>(m: &mut M) {
|
||||
add_class_restricted_getter(m, CLASS_NAME, "Workspace", data_model_get_workspace);
|
||||
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<'lua, M: LuaUserDataMethods<'lua, Instance>>(m: &mut M) {
|
||||
|
@ -26,40 +26,39 @@ pub fn add_methods<'lua, M: LuaUserDataMethods<'lua, Instance>>(m: &mut M) {
|
|||
|
||||
### See Also
|
||||
* [`Terrain`](https://create.roblox.com/docs/reference/engine/classes/Workspace#Terrain)
|
||||
on the Roblox Developer Hub
|
||||
on the Roblox Developer Hub
|
||||
*/
|
||||
fn data_model_get_workspace(_: &Lua, this: &Instance) -> LuaResult<Instance> {
|
||||
get_or_create_property_ref_instance(this, "Workspace", "Workspace")
|
||||
}
|
||||
|
||||
/**
|
||||
Gets or creates a service for this DataModel.
|
||||
Gets or creates a service for this `DataModel`.
|
||||
|
||||
### See Also
|
||||
* [`GetService`](https://create.roblox.com/docs/reference/engine/classes/ServiceProvider#GetService)
|
||||
on the Roblox Developer Hub
|
||||
on the Roblox Developer Hub
|
||||
*/
|
||||
fn data_model_get_service(_: &Lua, this: &Instance, service_name: String) -> LuaResult<Instance> {
|
||||
if matches!(class_is_a_service(&service_name), None | Some(false)) {
|
||||
Err(LuaError::RuntimeError(format!(
|
||||
"'{}' is not a valid service name",
|
||||
service_name
|
||||
"'{service_name}' is not a valid service name",
|
||||
)))
|
||||
} else if let Some(service) = this.find_child(|child| child.class == service_name) {
|
||||
Ok(service)
|
||||
} else {
|
||||
let service = Instance::new_orphaned(service_name);
|
||||
service.set_parent(Some(this.clone()));
|
||||
service.set_parent(Some(*this));
|
||||
Ok(service)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Gets a service for this DataModel, if it exists.
|
||||
Gets a service for this `DataModel`, if it exists.
|
||||
|
||||
### See Also
|
||||
* [`FindService`](https://create.roblox.com/docs/reference/engine/classes/ServiceProvider#FindService)
|
||||
on the Roblox Developer Hub
|
||||
on the Roblox Developer Hub
|
||||
*/
|
||||
fn data_model_find_service(
|
||||
_: &Lua,
|
||||
|
@ -68,8 +67,7 @@ fn data_model_find_service(
|
|||
) -> LuaResult<Option<Instance>> {
|
||||
if matches!(class_is_a_service(&service_name), None | Some(false)) {
|
||||
Err(LuaError::RuntimeError(format!(
|
||||
"'{}' is not a valid service name",
|
||||
service_name
|
||||
"'{service_name}' is not a valid service name",
|
||||
)))
|
||||
} else if let Some(service) = this.find_child(|child| child.class == service_name) {
|
||||
Ok(Some(service))
|
803
crates/lune-roblox/src/instance/mod.rs
Normal file
|
@ -0,0 +1,803 @@
|
|||
#![allow(clippy::missing_panics_doc)]
|
||||
|
||||
use std::{
|
||||
collections::{BTreeMap, VecDeque},
|
||||
fmt,
|
||||
hash::{Hash, Hasher},
|
||||
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,
|
||||
};
|
||||
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use crate::{
|
||||
exports::LuaExportsTable,
|
||||
shared::instance::{class_exists, class_is_a},
|
||||
};
|
||||
|
||||
pub(crate) mod base;
|
||||
pub(crate) mod data_model;
|
||||
pub(crate) mod terrain;
|
||||
pub(crate) mod workspace;
|
||||
|
||||
pub mod registry;
|
||||
|
||||
const PROPERTY_NAME_ATTRIBUTES: &str = "Attributes";
|
||||
const PROPERTY_NAME_TAGS: &str = "Tags";
|
||||
|
||||
static INTERNAL_DOM: Lazy<Mutex<WeakDom>> =
|
||||
Lazy::new(|| Mutex::new(WeakDom::new(DomInstanceBuilder::new("ROOT"))));
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Instance {
|
||||
pub(crate) dom_ref: DomRef,
|
||||
pub(crate) class_name: Ustr,
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
**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")
|
||||
}
|
||||
|
||||
/**
|
||||
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.
|
||||
|
||||
**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> {
|
||||
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||
|
||||
if let Some(instance) = dom.get_by_ref(dom_ref) {
|
||||
assert!(
|
||||
!(instance.referent() == dom.root_ref()),
|
||||
"Instances can not be created from dom roots"
|
||||
);
|
||||
|
||||
Some(Self {
|
||||
dom_ref,
|
||||
class_name: instance.class,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
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.
|
||||
|
||||
**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 {
|
||||
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 dom_root = dom.root_ref();
|
||||
let dom_ref = dom.insert(dom_root, instance);
|
||||
|
||||
Self {
|
||||
dom_ref,
|
||||
class_name: ustr(class_name),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
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.
|
||||
|
||||
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();
|
||||
|
||||
external_dom.transfer(external_dom_ref, &mut dom, dom_root);
|
||||
|
||||
drop(dom); // Self::new needs mutex handle, drop it first
|
||||
Self::new(external_dom_ref)
|
||||
}
|
||||
|
||||
/**
|
||||
Clones an instance to an external weak dom.
|
||||
|
||||
This will place the instance as a child of the
|
||||
root of the weak dom, and return its referent.
|
||||
*/
|
||||
pub fn clone_into_external_dom(self, external_dom: &mut WeakDom) -> DomRef {
|
||||
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||
|
||||
let cloned = dom.clone_into_external(self.dom_ref, external_dom);
|
||||
external_dom.transfer_within(cloned, external_dom.root_ref());
|
||||
|
||||
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,
|
||||
) -> Vec<DomRef> {
|
||||
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||
|
||||
let cloned = dom.clone_multiple_into_external(referents, external_dom);
|
||||
|
||||
for referent in &cloned {
|
||||
external_dom.transfer_within(*referent, external_dom.root_ref());
|
||||
}
|
||||
|
||||
cloned
|
||||
}
|
||||
|
||||
/**
|
||||
Clones the instance and all of its descendants, and orphans it.
|
||||
|
||||
To then save the new instance it must be re-parented,
|
||||
which matches the exact behavior of Roblox's instances.
|
||||
|
||||
### See Also
|
||||
* [`Clone`](https://create.roblox.com/docs/reference/engine/classes/Instance#Clone)
|
||||
on the Roblox Developer Hub
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn clone_instance(&self) -> Self {
|
||||
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||
let new_ref = dom.clone_within(self.dom_ref);
|
||||
drop(dom); // Self::new needs mutex handle, drop it first
|
||||
|
||||
let new_inst = Self::new(new_ref);
|
||||
new_inst.set_parent(None);
|
||||
new_inst
|
||||
}
|
||||
|
||||
/**
|
||||
Destroys the instance, removing it completely
|
||||
from the weak dom with no way of recovering it.
|
||||
|
||||
All member methods will throw errors when called from lua and panic
|
||||
when called from rust after the instance has been destroyed.
|
||||
|
||||
Returns `true` if destroyed successfully, `false` if already destroyed.
|
||||
|
||||
### See Also
|
||||
* [`Destroy`](https://create.roblox.com/docs/reference/engine/classes/Instance#Destroy)
|
||||
on the Roblox Developer Hub
|
||||
*/
|
||||
pub fn destroy(&mut self) -> bool {
|
||||
if self.is_destroyed() {
|
||||
false
|
||||
} else {
|
||||
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||
|
||||
dom.destroy(self.dom_ref);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
fn is_destroyed(&self) -> bool {
|
||||
// NOTE: This property can not be cached since instance references
|
||||
// other than this one may have destroyed this one, and we don't
|
||||
// keep track of all current instance reference structs
|
||||
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||
dom.get_by_ref(self.dom_ref).is_none()
|
||||
}
|
||||
|
||||
/**
|
||||
Destroys all child instances.
|
||||
|
||||
### 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
|
||||
*/
|
||||
pub fn clear_all_children(&mut self) {
|
||||
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||
|
||||
let instance = dom
|
||||
.get_by_ref(self.dom_ref)
|
||||
.expect("Failed to find instance in document");
|
||||
|
||||
let child_refs = instance.children().to_vec();
|
||||
for child_ref in child_refs {
|
||||
dom.destroy(child_ref);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Checks if the instance matches or inherits a given class name.
|
||||
|
||||
### See Also
|
||||
* [`IsA`](https://create.roblox.com/docs/reference/engine/classes/Instance#IsA)
|
||||
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)
|
||||
}
|
||||
|
||||
/**
|
||||
Gets the class name of the instance.
|
||||
|
||||
This will return the correct class name even if the instance has been destroyed.
|
||||
|
||||
### See Also
|
||||
* [`ClassName`](https://create.roblox.com/docs/reference/engine/classes/Instance#ClassName)
|
||||
on the Roblox Developer Hub
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn get_class_name(&self) -> &str {
|
||||
self.class_name.as_str()
|
||||
}
|
||||
|
||||
/**
|
||||
Gets the name of the instance, if it exists.
|
||||
|
||||
### See Also
|
||||
* [`Name`](https://create.roblox.com/docs/reference/engine/classes/Instance#Name)
|
||||
on the Roblox Developer Hub
|
||||
*/
|
||||
pub fn get_name(&self) -> String {
|
||||
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||
|
||||
dom.get_by_ref(self.dom_ref)
|
||||
.expect("Failed to find instance in document")
|
||||
.name
|
||||
.clone()
|
||||
}
|
||||
|
||||
/**
|
||||
Sets the name of the instance, if it exists.
|
||||
|
||||
### See Also
|
||||
* [`Name`](https://create.roblox.com/docs/reference/engine/classes/Instance#Name)
|
||||
on the Roblox Developer Hub
|
||||
*/
|
||||
pub fn set_name(&self, name: impl Into<String>) {
|
||||
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||
|
||||
dom.get_by_ref_mut(self.dom_ref)
|
||||
.expect("Failed to find instance in document")
|
||||
.name = name.into();
|
||||
}
|
||||
|
||||
/**
|
||||
Gets the parent of the instance, if it exists.
|
||||
|
||||
### See Also
|
||||
* [`Parent`](https://create.roblox.com/docs/reference/engine/classes/Instance#Parent)
|
||||
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();
|
||||
|
||||
if parent_ref == dom.root_ref() {
|
||||
None
|
||||
} else {
|
||||
drop(dom); // Self::new needs mutex handle, drop it first
|
||||
Some(Self::new(parent_ref))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Sets the parent of the instance, if it exists.
|
||||
|
||||
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.
|
||||
|
||||
### See Also
|
||||
* [`Parent`](https://create.roblox.com/docs/reference/engine/classes/Instance#Parent)
|
||||
on the Roblox Developer Hub
|
||||
*/
|
||||
pub fn set_parent(&self, parent: Option<Instance>) {
|
||||
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||
|
||||
let parent_ref = parent.map_or_else(|| dom.root_ref(), |parent| parent.dom_ref);
|
||||
|
||||
dom.transfer_within(self.dom_ref, parent_ref);
|
||||
}
|
||||
|
||||
/**
|
||||
Gets a property for the instance, if it exists.
|
||||
*/
|
||||
pub fn get_property(&self, name: impl AsRef<str>) -> Option<DomValue> {
|
||||
INTERNAL_DOM
|
||||
.lock()
|
||||
.expect("Failed to lock document")
|
||||
.get_by_ref(self.dom_ref)
|
||||
.expect("Failed to find instance in document")
|
||||
.properties
|
||||
.get(&ustr(name.as_ref()))
|
||||
.cloned()
|
||||
}
|
||||
|
||||
/**
|
||||
Sets a property for the instance.
|
||||
|
||||
Note that setting a property here will not fail even if the
|
||||
property does not actually exist for the instance class.
|
||||
*/
|
||||
pub fn set_property(&self, name: impl AsRef<str>, value: DomValue) {
|
||||
INTERNAL_DOM
|
||||
.lock()
|
||||
.expect("Failed to lock document")
|
||||
.get_by_ref_mut(self.dom_ref)
|
||||
.expect("Failed to find instance in document")
|
||||
.properties
|
||||
.insert(ustr(name.as_ref()), value);
|
||||
}
|
||||
|
||||
/**
|
||||
Gets an attribute for the instance, if it exists.
|
||||
|
||||
### See Also
|
||||
* [`GetAttribute`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetAttribute)
|
||||
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");
|
||||
let inst = dom
|
||||
.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))
|
||||
{
|
||||
attributes.get(name.as_ref()).cloned()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Gets all known attributes for the instance.
|
||||
|
||||
### See Also
|
||||
* [`GetAttributes`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetAttributes)
|
||||
on the Roblox Developer Hub
|
||||
*/
|
||||
pub fn get_attributes(&self) -> BTreeMap<String, DomValue> {
|
||||
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::Attributes(attributes)) =
|
||||
inst.properties.get(&ustr(PROPERTY_NAME_ATTRIBUTES))
|
||||
{
|
||||
attributes.clone().into_iter().collect()
|
||||
} else {
|
||||
BTreeMap::new()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Sets an attribute for the instance.
|
||||
|
||||
### See Also
|
||||
* [`SetAttribute`](https://create.roblox.com/docs/reference/engine/classes/Instance#SetAttribute)
|
||||
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");
|
||||
let inst = dom
|
||||
.get_by_ref_mut(self.dom_ref)
|
||||
.expect("Failed to find instance in document");
|
||||
// NOTE: Attributes do not support integers, only floats
|
||||
let value = match value {
|
||||
DomValue::Int32(i) => DomValue::Float32(i as f32),
|
||||
DomValue::Int64(i) => DomValue::Float64(i as f64),
|
||||
value => value,
|
||||
};
|
||||
if let Some(DomValue::Attributes(attributes)) =
|
||||
inst.properties.get_mut(&ustr(PROPERTY_NAME_ATTRIBUTES))
|
||||
{
|
||||
attributes.insert(name.as_ref().to_string(), value);
|
||||
} else {
|
||||
let mut attributes = DomAttributes::new();
|
||||
attributes.insert(name.as_ref().to_string(), value);
|
||||
inst.properties.insert(
|
||||
ustr(PROPERTY_NAME_ATTRIBUTES),
|
||||
DomValue::Attributes(attributes),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Removes an attribute from the instance.
|
||||
|
||||
Note that this does not have an equivalent in the Roblox engine API,
|
||||
but separating this from `set_attribute` lets `set_attribute` be more
|
||||
ergonomic and not require an `Option<DomValue>` for the value argument.
|
||||
The equivalent in the Roblox engine API would be `instance:SetAttribute(name, nil)`.
|
||||
*/
|
||||
pub fn remove_attribute(&self, name: impl AsRef<str>) {
|
||||
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||
let inst = dom
|
||||
.get_by_ref_mut(self.dom_ref)
|
||||
.expect("Failed to find instance in document");
|
||||
if let Some(DomValue::Attributes(attributes)) =
|
||||
inst.properties.get_mut(&ustr(PROPERTY_NAME_ATTRIBUTES))
|
||||
{
|
||||
attributes.remove(name.as_ref());
|
||||
if attributes.is_empty() {
|
||||
inst.properties.remove(&ustr(PROPERTY_NAME_ATTRIBUTES));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Adds a tag to the instance.
|
||||
|
||||
### See Also
|
||||
* [`AddTag`](https://create.roblox.com/docs/reference/engine/classes/CollectionService#AddTag)
|
||||
on the Roblox Developer Hub
|
||||
*/
|
||||
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)) {
|
||||
tags.push(name.as_ref());
|
||||
} else {
|
||||
inst.properties.insert(
|
||||
ustr(PROPERTY_NAME_TAGS),
|
||||
DomValue::Tags(vec![name.as_ref().to_string()].into()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Gets all current tags for the instance.
|
||||
|
||||
### See Also
|
||||
* [`GetTags`](https://create.roblox.com/docs/reference/engine/classes/CollectionService#GetTags)
|
||||
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)) {
|
||||
tags.iter().map(ToString::to_string).collect()
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Checks if the instance has a specific tag.
|
||||
|
||||
### See Also
|
||||
* [`HasTag`](https://create.roblox.com/docs/reference/engine/classes/CollectionService#HasTag)
|
||||
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)) {
|
||||
let name = name.as_ref();
|
||||
tags.iter().any(|tag| tag == name)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Removes a tag from the instance.
|
||||
|
||||
### See Also
|
||||
* [`RemoveTag`](https://create.roblox.com/docs/reference/engine/classes/CollectionService#RemoveTag)
|
||||
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)) {
|
||||
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()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Gets all of the current children of this `Instance`.
|
||||
|
||||
Note that this is a somewhat expensive operation and that other
|
||||
operations using weak dom referents should be preferred if possible.
|
||||
|
||||
### See Also
|
||||
* [`GetChildren`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetChildren)
|
||||
on the Roblox Developer Hub
|
||||
*/
|
||||
pub fn get_children(&self) -> Vec<Instance> {
|
||||
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||
|
||||
let children = dom
|
||||
.get_by_ref(self.dom_ref)
|
||||
.expect("Failed to find instance in document")
|
||||
.children()
|
||||
.to_vec();
|
||||
|
||||
drop(dom); // Self::new needs mutex handle, drop it first
|
||||
children.into_iter().map(Self::new).collect()
|
||||
}
|
||||
|
||||
/**
|
||||
Gets all of the current descendants of this `Instance` using a breadth-first search.
|
||||
|
||||
Note that this is a somewhat expensive operation and that other
|
||||
operations using weak dom referents should be preferred if possible.
|
||||
|
||||
### See Also
|
||||
* [`GetDescendants`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetDescendants)
|
||||
on the Roblox Developer Hub
|
||||
*/
|
||||
pub fn get_descendants(&self) -> Vec<Instance> {
|
||||
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||
|
||||
let mut descendants = Vec::new();
|
||||
let mut queue = VecDeque::from_iter(
|
||||
dom.get_by_ref(self.dom_ref)
|
||||
.expect("Failed to find instance in document")
|
||||
.children(),
|
||||
);
|
||||
|
||||
while let Some(queue_ref) = queue.pop_front() {
|
||||
descendants.push(*queue_ref);
|
||||
let queue_inst = dom.get_by_ref(*queue_ref).unwrap();
|
||||
for queue_ref_inner in queue_inst.children().iter().rev() {
|
||||
queue.push_back(queue_ref_inner);
|
||||
}
|
||||
}
|
||||
|
||||
drop(dom); // Self::new needs mutex handle, drop it first
|
||||
descendants.into_iter().map(Self::new).collect()
|
||||
}
|
||||
|
||||
/**
|
||||
Gets the "full name" of this instance.
|
||||
|
||||
This will be a path composed of instance names from the top-level
|
||||
ancestor of this instance down to itself, in the following format:
|
||||
|
||||
`Ancestor.Child.Descendant.Instance`
|
||||
|
||||
### See Also
|
||||
* [`GetFullName`](https://create.roblox.com/docs/reference/engine/classes/Instance#GetFullName)
|
||||
on the Roblox Developer Hub
|
||||
*/
|
||||
pub fn get_full_name(&self) -> String {
|
||||
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||
let dom_root = dom.root_ref();
|
||||
|
||||
let mut parts = Vec::new();
|
||||
let mut instance_ref = self.dom_ref;
|
||||
|
||||
while let Some(instance) = dom.get_by_ref(instance_ref) {
|
||||
if instance_ref != dom_root && instance.class != data_model::CLASS_NAME {
|
||||
instance_ref = instance.parent();
|
||||
parts.push(instance.name.clone());
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
parts.reverse();
|
||||
parts.join(".")
|
||||
}
|
||||
|
||||
/**
|
||||
Finds a child of the instance using the given predicate callback.
|
||||
|
||||
### See Also
|
||||
* [`FindFirstChild`](https://create.roblox.com/docs/reference/engine/classes/Instance#FindFirstChild) on the Roblox Developer Hub
|
||||
* [`FindFirstChildOfClass`](https://create.roblox.com/docs/reference/engine/classes/Instance#FindFirstChildOfClass) on the Roblox Developer Hub
|
||||
* [`FindFirstChildWhichIsA`](https://create.roblox.com/docs/reference/engine/classes/Instance#FindFirstChildWhichIsA) on the Roblox Developer Hub
|
||||
*/
|
||||
pub fn find_child<F>(&self, predicate: F) -> Option<Instance>
|
||||
where
|
||||
F: Fn(&DomInstance) -> bool,
|
||||
{
|
||||
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||
|
||||
let children = dom
|
||||
.get_by_ref(self.dom_ref)
|
||||
.expect("Failed to find instance in document")
|
||||
.children()
|
||||
.to_vec();
|
||||
|
||||
let found_ref = children.into_iter().find(|child_ref| {
|
||||
if let Some(child_inst) = dom.get_by_ref(*child_ref) {
|
||||
predicate(child_inst)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
|
||||
drop(dom); // Self::new needs mutex handle, drop it first
|
||||
found_ref.map(Self::new)
|
||||
}
|
||||
|
||||
/**
|
||||
Finds an ancestor of the instance using the given predicate callback.
|
||||
|
||||
### See Also
|
||||
* [`FindFirstAncestor`](https://create.roblox.com/docs/reference/engine/classes/Instance#FindFirstAncestor) on the Roblox Developer Hub
|
||||
* [`FindFirstAncestorOfClass`](https://create.roblox.com/docs/reference/engine/classes/Instance#FindFirstAncestorOfClass) on the Roblox Developer Hub
|
||||
* [`FindFirstAncestorWhichIsA`](https://create.roblox.com/docs/reference/engine/classes/Instance#FindFirstAncestorWhichIsA) on the Roblox Developer Hub
|
||||
*/
|
||||
pub fn find_ancestor<F>(&self, predicate: F) -> Option<Instance>
|
||||
where
|
||||
F: Fn(&DomInstance) -> bool,
|
||||
{
|
||||
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||
|
||||
let mut ancestor_ref = dom
|
||||
.get_by_ref(self.dom_ref)
|
||||
.expect("Failed to find instance in document")
|
||||
.parent();
|
||||
|
||||
while let Some(ancestor) = dom.get_by_ref(ancestor_ref) {
|
||||
if predicate(ancestor) {
|
||||
drop(dom); // Self::new needs mutex handle, drop it first
|
||||
return Some(Self::new(ancestor_ref));
|
||||
}
|
||||
ancestor_ref = ancestor.parent();
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/**
|
||||
Finds a descendant of the instance using the given
|
||||
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
|
||||
*/
|
||||
pub fn find_descendant<F>(&self, predicate: F) -> Option<Instance>
|
||||
where
|
||||
F: Fn(&DomInstance) -> bool,
|
||||
{
|
||||
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
|
||||
|
||||
let mut queue = VecDeque::from_iter(
|
||||
dom.get_by_ref(self.dom_ref)
|
||||
.expect("Failed to find instance in document")
|
||||
.children(),
|
||||
);
|
||||
|
||||
while let Some(queue_item) = queue
|
||||
.pop_front()
|
||||
.and_then(|queue_ref| dom.get_by_ref(*queue_ref))
|
||||
{
|
||||
if predicate(queue_item) {
|
||||
let queue_ref = queue_item.referent();
|
||||
drop(dom); // Self::new needs mutex handle, drop it first
|
||||
return Some(Self::new(queue_ref));
|
||||
}
|
||||
queue.extend(queue_item.children());
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
impl LuaExportsTable<'_> for Instance {
|
||||
const EXPORT_NAME: &'static str = "Instance";
|
||||
|
||||
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 {
|
||||
Err(LuaError::RuntimeError(format!(
|
||||
"Failed to create Instance - '{class_name}' is not a valid class name",
|
||||
)))
|
||||
}
|
||||
};
|
||||
|
||||
TableBuilder::new(lua)?
|
||||
.with_function("new", instance_new)?
|
||||
.build_readonly()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Here we add inheritance-like behavior for instances by creating
|
||||
fields that are restricted to specific classnames / base classes
|
||||
|
||||
Note that we should try to be conservative with how many classes
|
||||
and methods we support here - we should only implement methods that
|
||||
are necessary for modifying the dom and / or having ergonomic access
|
||||
to the dom, not try to replicate Roblox engine behavior of instances
|
||||
|
||||
If a user wants to replicate Roblox engine behavior, they can use the
|
||||
instance registry, and register properties + methods from the lua side
|
||||
*/
|
||||
impl LuaUserData for Instance {
|
||||
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
||||
data_model::add_fields(fields);
|
||||
workspace::add_fields(fields);
|
||||
}
|
||||
|
||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
base::add_methods(methods);
|
||||
data_model::add_methods(methods);
|
||||
terrain::add_methods(methods);
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for Instance {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.dom_ref.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Instance {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
if self.is_destroyed() {
|
||||
"<<DESTROYED>>".to_string()
|
||||
} else {
|
||||
self.get_name()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Instance {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.dom_ref == other.dom_ref
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Instance> for DomRef {
|
||||
fn from(value: Instance) -> Self {
|
||||
value.dom_ref
|
||||
}
|
||||
}
|
274
crates/lune-roblox/src/instance/registry.rs
Normal file
|
@ -0,0 +1,274 @@
|
|||
use std::{
|
||||
borrow::Borrow,
|
||||
collections::HashMap,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use mlua::{prelude::*, AppDataRef};
|
||||
use thiserror::Error;
|
||||
|
||||
use super::Instance;
|
||||
|
||||
type InstanceRegistryMap = HashMap<String, HashMap<String, LuaRegistryKey>>;
|
||||
|
||||
#[derive(Debug, Clone, Error)]
|
||||
pub enum InstanceRegistryError {
|
||||
#[error("class name '{0}' is not valid")]
|
||||
InvalidClassName(String),
|
||||
#[error("class '{class_name}' already registered method '{method_name}'")]
|
||||
MethodAlreadyExists {
|
||||
class_name: String,
|
||||
method_name: String,
|
||||
},
|
||||
#[error("class '{class_name}' already registered property '{property_name}'")]
|
||||
PropertyAlreadyExists {
|
||||
class_name: String,
|
||||
property_name: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct InstanceRegistry {
|
||||
getters: Arc<Mutex<InstanceRegistryMap>>,
|
||||
setters: Arc<Mutex<InstanceRegistryMap>>,
|
||||
methods: Arc<Mutex<InstanceRegistryMap>>,
|
||||
}
|
||||
|
||||
impl InstanceRegistry {
|
||||
// NOTE: We lazily create the instance registry instead
|
||||
// of always creating it together with the roblox builtin
|
||||
// since it is less commonly used and it simplifies some app
|
||||
// data borrowing relationship problems we'd otherwise have
|
||||
fn get_or_create(lua: &Lua) -> AppDataRef<'_, Self> {
|
||||
if lua.app_data_ref::<Self>().is_none() {
|
||||
lua.set_app_data(Self {
|
||||
getters: Arc::new(Mutex::new(HashMap::new())),
|
||||
setters: Arc::new(Mutex::new(HashMap::new())),
|
||||
methods: Arc::new(Mutex::new(HashMap::new())),
|
||||
});
|
||||
}
|
||||
lua.app_data_ref::<Self>()
|
||||
.expect("Missing InstanceRegistry in app data")
|
||||
}
|
||||
|
||||
/**
|
||||
Inserts a method into the instance registry.
|
||||
|
||||
# Errors
|
||||
|
||||
- If the method already exists in the registry.
|
||||
*/
|
||||
pub fn insert_method<'lua>(
|
||||
lua: &'lua Lua,
|
||||
class_name: &str,
|
||||
method_name: &str,
|
||||
method: LuaFunction<'lua>,
|
||||
) -> Result<(), InstanceRegistryError> {
|
||||
let registry = Self::get_or_create(lua);
|
||||
|
||||
let mut methods = registry
|
||||
.methods
|
||||
.lock()
|
||||
.expect("Failed to lock instance registry methods");
|
||||
|
||||
let class_methods = methods.entry(class_name.to_string()).or_default();
|
||||
if class_methods.contains_key(method_name) {
|
||||
return Err(InstanceRegistryError::MethodAlreadyExists {
|
||||
class_name: class_name.to_string(),
|
||||
method_name: method_name.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
let key = lua
|
||||
.create_registry_value(method)
|
||||
.expect("Failed to store method in lua registry");
|
||||
class_methods.insert(method_name.to_string(), key);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/**
|
||||
Inserts a property getter into the instance registry.
|
||||
|
||||
# Errors
|
||||
|
||||
- If the property already exists in the registry.
|
||||
*/
|
||||
pub fn insert_property_getter<'lua>(
|
||||
lua: &'lua Lua,
|
||||
class_name: &str,
|
||||
property_name: &str,
|
||||
property_getter: LuaFunction<'lua>,
|
||||
) -> Result<(), InstanceRegistryError> {
|
||||
let registry = Self::get_or_create(lua);
|
||||
|
||||
let mut getters = registry
|
||||
.getters
|
||||
.lock()
|
||||
.expect("Failed to lock instance registry getters");
|
||||
|
||||
let class_getters = getters.entry(class_name.to_string()).or_default();
|
||||
if class_getters.contains_key(property_name) {
|
||||
return Err(InstanceRegistryError::PropertyAlreadyExists {
|
||||
class_name: class_name.to_string(),
|
||||
property_name: property_name.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
let key = lua
|
||||
.create_registry_value(property_getter)
|
||||
.expect("Failed to store getter in lua registry");
|
||||
class_getters.insert(property_name.to_string(), key);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/**
|
||||
Inserts a property setter into the instance registry.
|
||||
|
||||
# Errors
|
||||
|
||||
- If the property already exists in the registry.
|
||||
*/
|
||||
pub fn insert_property_setter<'lua>(
|
||||
lua: &'lua Lua,
|
||||
class_name: &str,
|
||||
property_name: &str,
|
||||
property_setter: LuaFunction<'lua>,
|
||||
) -> Result<(), InstanceRegistryError> {
|
||||
let registry = Self::get_or_create(lua);
|
||||
|
||||
let mut setters = registry
|
||||
.setters
|
||||
.lock()
|
||||
.expect("Failed to lock instance registry getters");
|
||||
|
||||
let class_setters = setters.entry(class_name.to_string()).or_default();
|
||||
if class_setters.contains_key(property_name) {
|
||||
return Err(InstanceRegistryError::PropertyAlreadyExists {
|
||||
class_name: class_name.to_string(),
|
||||
property_name: property_name.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
let key = lua
|
||||
.create_registry_value(property_setter)
|
||||
.expect("Failed to store getter in lua registry");
|
||||
class_setters.insert(property_name.to_string(), key);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/**
|
||||
Finds a method in the instance registry.
|
||||
|
||||
Returns `None` if the method is not found.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn find_method<'lua>(
|
||||
lua: &'lua Lua,
|
||||
instance: &Instance,
|
||||
method_name: &str,
|
||||
) -> Option<LuaFunction<'lua>> {
|
||||
let registry = Self::get_or_create(lua);
|
||||
let methods = registry
|
||||
.methods
|
||||
.lock()
|
||||
.expect("Failed to lock instance registry methods");
|
||||
|
||||
class_name_chain(&instance.class_name)
|
||||
.iter()
|
||||
.find_map(|&class_name| {
|
||||
methods
|
||||
.get(class_name)
|
||||
.and_then(|class_methods| class_methods.get(method_name))
|
||||
.map(|key| lua.registry_value::<LuaFunction>(key).unwrap())
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
Finds a property getter in the instance registry.
|
||||
|
||||
Returns `None` if the property getter is not found.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn find_property_getter<'lua>(
|
||||
lua: &'lua Lua,
|
||||
instance: &Instance,
|
||||
property_name: &str,
|
||||
) -> Option<LuaFunction<'lua>> {
|
||||
let registry = Self::get_or_create(lua);
|
||||
let getters = registry
|
||||
.getters
|
||||
.lock()
|
||||
.expect("Failed to lock instance registry getters");
|
||||
|
||||
class_name_chain(&instance.class_name)
|
||||
.iter()
|
||||
.find_map(|&class_name| {
|
||||
getters
|
||||
.get(class_name)
|
||||
.and_then(|class_getters| class_getters.get(property_name))
|
||||
.map(|key| lua.registry_value::<LuaFunction>(key).unwrap())
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
Finds a property setter in the instance registry.
|
||||
|
||||
Returns `None` if the property setter is not found.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn find_property_setter<'lua>(
|
||||
lua: &'lua Lua,
|
||||
instance: &Instance,
|
||||
property_name: &str,
|
||||
) -> Option<LuaFunction<'lua>> {
|
||||
let registry = Self::get_or_create(lua);
|
||||
let setters = registry
|
||||
.setters
|
||||
.lock()
|
||||
.expect("Failed to lock instance registry setters");
|
||||
|
||||
class_name_chain(&instance.class_name)
|
||||
.iter()
|
||||
.find_map(|&class_name| {
|
||||
setters
|
||||
.get(class_name)
|
||||
.and_then(|class_setters| class_setters.get(property_name))
|
||||
.map(|key| lua.registry_value::<LuaFunction>(key).unwrap())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Gets the class name chain for a given class name.
|
||||
|
||||
The chain starts with the given class name and ends with the root class.
|
||||
|
||||
# Panics
|
||||
|
||||
Panics if the class name is not valid.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn class_name_chain(class_name: &str) -> Vec<&str> {
|
||||
let db = rbx_reflection_database::get();
|
||||
|
||||
let mut list = vec![class_name];
|
||||
let mut current_name = class_name;
|
||||
|
||||
loop {
|
||||
let class_descriptor = db
|
||||
.classes
|
||||
.get(current_name)
|
||||
.expect("Got invalid class name");
|
||||
if let Some(sup) = &class_descriptor.superclass {
|
||||
current_name = sup.borrow();
|
||||
list.push(current_name);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
list
|
||||
}
|
93
crates/lune-roblox/src/instance/terrain.rs
Normal file
|
@ -0,0 +1,93 @@
|
|||
use mlua::prelude::*;
|
||||
use rbx_dom_weak::types::{MaterialColors, TerrainMaterials, Variant};
|
||||
|
||||
use crate::{
|
||||
datatypes::types::{Color3, EnumItem},
|
||||
shared::classes::{add_class_restricted_method, add_class_restricted_method_mut},
|
||||
};
|
||||
|
||||
use super::Instance;
|
||||
|
||||
pub const CLASS_NAME: &str = "Terrain";
|
||||
|
||||
pub fn add_methods<'lua, M: LuaUserDataMethods<'lua, Instance>>(methods: &mut M) {
|
||||
add_class_restricted_method(
|
||||
methods,
|
||||
CLASS_NAME,
|
||||
"GetMaterialColor",
|
||||
terrain_get_material_color,
|
||||
);
|
||||
|
||||
add_class_restricted_method_mut(
|
||||
methods,
|
||||
CLASS_NAME,
|
||||
"SetMaterialColor",
|
||||
terrain_set_material_color,
|
||||
);
|
||||
}
|
||||
|
||||
fn get_or_create_material_colors(instance: &Instance) -> MaterialColors {
|
||||
if let Some(Variant::MaterialColors(inner)) = instance.get_property("MaterialColors") {
|
||||
inner
|
||||
} else {
|
||||
MaterialColors::default()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the color of the given terrain material.
|
||||
|
||||
### See Also
|
||||
* [`GetMaterialColor`](https://create.roblox.com/docs/reference/engine/classes/Terrain#GetMaterialColor)
|
||||
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);
|
||||
|
||||
if &material.parent.desc.name != "Material" {
|
||||
return Err(LuaError::RuntimeError(format!(
|
||||
"Expected Enum.Material, got Enum.{}",
|
||||
&material.parent.desc.name
|
||||
)));
|
||||
}
|
||||
|
||||
let terrain_material = material
|
||||
.name
|
||||
.parse::<TerrainMaterials>()
|
||||
.map_err(|err| LuaError::RuntimeError(err.to_string()))?;
|
||||
|
||||
Ok(material_colors.get_color(terrain_material).into())
|
||||
}
|
||||
|
||||
/**
|
||||
Sets the color of the given terrain material.
|
||||
|
||||
### See Also
|
||||
* [`SetMaterialColor`](https://create.roblox.com/docs/reference/engine/classes/Terrain#SetMaterialColor)
|
||||
on the Roblox Developer Hub
|
||||
*/
|
||||
fn terrain_set_material_color(
|
||||
_: &Lua,
|
||||
this: &mut Instance,
|
||||
args: (EnumItem, Color3),
|
||||
) -> LuaResult<()> {
|
||||
let mut material_colors = get_or_create_material_colors(this);
|
||||
let material = args.0;
|
||||
let color = args.1;
|
||||
|
||||
if &material.parent.desc.name != "Material" {
|
||||
return Err(LuaError::RuntimeError(format!(
|
||||
"Expected Enum.Material, got Enum.{}",
|
||||
&material.parent.desc.name
|
||||
)));
|
||||
}
|
||||
|
||||
let terrain_material = material
|
||||
.name
|
||||
.parse::<TerrainMaterials>()
|
||||
.map_err(|err| LuaError::RuntimeError(err.to_string()))?;
|
||||
|
||||
material_colors.set_color(terrain_material, color.into());
|
||||
this.set_property("MaterialColors", Variant::MaterialColors(material_colors));
|
||||
Ok(())
|
||||
}
|
|
@ -6,9 +6,9 @@ use super::Instance;
|
|||
|
||||
pub const CLASS_NAME: &str = "Workspace";
|
||||
|
||||
pub fn add_fields<'lua, M: LuaUserDataFields<'lua, Instance>>(m: &mut M) {
|
||||
add_class_restricted_getter(m, CLASS_NAME, "Terrain", workspace_get_terrain);
|
||||
add_class_restricted_getter(m, CLASS_NAME, "CurrentCamera", workspace_get_camera);
|
||||
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<'lua, M: LuaUserDataFields<'lua, Instance>>(m: &mut M) {
|
|||
|
||||
### See Also
|
||||
* [`Terrain`](https://create.roblox.com/docs/reference/engine/classes/Workspace#Terrain)
|
||||
on the Roblox Developer Hub
|
||||
on the Roblox Developer Hub
|
||||
*/
|
||||
fn 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")
|
72
crates/lune-roblox/src/lib.rs
Normal file
|
@ -0,0 +1,72 @@
|
|||
#![allow(clippy::cargo_common_metadata)]
|
||||
|
||||
use mlua::prelude::*;
|
||||
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
pub mod datatypes;
|
||||
pub mod document;
|
||||
pub mod instance;
|
||||
pub mod reflection;
|
||||
|
||||
pub(crate) mod exports;
|
||||
pub(crate) mod shared;
|
||||
|
||||
use exports::export;
|
||||
|
||||
fn create_all_exports(lua: &Lua) -> LuaResult<Vec<(&'static str, LuaValue)>> {
|
||||
use datatypes::types::*;
|
||||
use instance::Instance;
|
||||
Ok(vec![
|
||||
// Datatypes
|
||||
export::<Axes>(lua)?,
|
||||
export::<BrickColor>(lua)?,
|
||||
export::<CFrame>(lua)?,
|
||||
export::<Color3>(lua)?,
|
||||
export::<ColorSequence>(lua)?,
|
||||
export::<ColorSequenceKeypoint>(lua)?,
|
||||
export::<Content>(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)?,
|
||||
// Singletons
|
||||
("Enum", Enums.into_lua(lua)?),
|
||||
])
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a table containing all the Roblox datatypes, classes, and singletons.
|
||||
|
||||
Note that this is not guaranteed to contain any value unless indexed directly,
|
||||
it may be optimized to use lazy initialization in the future.
|
||||
|
||||
# Errors
|
||||
|
||||
Errors when out of memory or when a value cannot be created.
|
||||
*/
|
||||
pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
|
||||
// FUTURE: We can probably create these lazily as users
|
||||
// index the main exports (this return value) table and
|
||||
// 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)?;
|
||||
TableBuilder::new(lua)?
|
||||
.with_values(exports)?
|
||||
.build_readonly()
|
||||
}
|
155
crates/lune-roblox/src/reflection/class.rs
Normal file
|
@ -0,0 +1,155 @@
|
|||
use core::fmt;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use mlua::prelude::*;
|
||||
|
||||
use rbx_dom_weak::types::Variant as DomVariant;
|
||||
use rbx_reflection::{ClassDescriptor, DataType};
|
||||
|
||||
use super::{property::DatabaseProperty, utils::*};
|
||||
use crate::datatypes::{
|
||||
conversion::DomValueToLua, types::EnumItem, userdata_impl_eq, userdata_impl_to_string,
|
||||
};
|
||||
|
||||
type DbClass = &'static ClassDescriptor<'static>;
|
||||
|
||||
/**
|
||||
A wrapper for [`rbx_reflection::ClassDescriptor`] that
|
||||
also provides access to the class descriptor from lua.
|
||||
*/
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct DatabaseClass(DbClass);
|
||||
|
||||
impl DatabaseClass {
|
||||
pub(crate) fn new(inner: DbClass) -> Self {
|
||||
Self(inner)
|
||||
}
|
||||
|
||||
/**
|
||||
Get the name of this class.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn get_name(&self) -> String {
|
||||
self.0.name.to_string()
|
||||
}
|
||||
|
||||
/**
|
||||
Get the superclass (parent class) of this class.
|
||||
|
||||
May be `None` if no parent class exists.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn get_superclass(&self) -> Option<String> {
|
||||
let sup = self.0.superclass.as_ref()?;
|
||||
Some(sup.to_string())
|
||||
}
|
||||
|
||||
/**
|
||||
Get all known properties for this class.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn get_properties(&self) -> HashMap<String, DatabaseProperty> {
|
||||
self.0
|
||||
.properties
|
||||
.iter()
|
||||
.map(|(name, prop)| (name.to_string(), DatabaseProperty::new(self.0, prop)))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/**
|
||||
Get all default values for properties of this class.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn get_defaults(&self) -> HashMap<String, DomVariant> {
|
||||
self.0
|
||||
.default_properties
|
||||
.iter()
|
||||
.map(|(name, prop)| (name.to_string(), prop.clone()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/**
|
||||
Get all tags describing the class.
|
||||
|
||||
These include information such as if the class can be replicated
|
||||
to players at runtime, and top-level class categories.
|
||||
*/
|
||||
pub fn get_tags_str(&self) -> Vec<&'static str> {
|
||||
self.0
|
||||
.tags
|
||||
.iter()
|
||||
.copied()
|
||||
.map(class_tag_to_str)
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
}
|
||||
|
||||
impl LuaUserData for DatabaseClass {
|
||||
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()));
|
||||
fields.add_field_method_get("DefaultProperties", |lua, this| {
|
||||
let defaults = this.get_defaults();
|
||||
let mut map = HashMap::with_capacity(defaults.len());
|
||||
for (name, prop) in defaults {
|
||||
let value = if let DomVariant::Enum(enum_value) = prop {
|
||||
make_enum_value(this.0, &name, enum_value.to_u32())
|
||||
.and_then(|e| e.into_lua(lua))
|
||||
} else {
|
||||
LuaValue::dom_value_to_lua(lua, &prop).into_lua_err()
|
||||
};
|
||||
if let Ok(value) = value {
|
||||
map.insert(name, value);
|
||||
}
|
||||
}
|
||||
Ok(map)
|
||||
});
|
||||
fields.add_field_method_get("Tags", |_, this| Ok(this.get_tags_str()));
|
||||
}
|
||||
|
||||
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 PartialEq for DatabaseClass {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0.name == other.0.name
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for DatabaseClass {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "ReflectionDatabaseClass({})", self.0.name)
|
||||
}
|
||||
}
|
||||
|
||||
fn find_enum_name(inner: DbClass, name: impl AsRef<str>) -> Option<String> {
|
||||
inner.properties.iter().find_map(|(prop_name, prop_info)| {
|
||||
if prop_name == name.as_ref() {
|
||||
if let DataType::Enum(enum_name) = &prop_info.data_type {
|
||||
Some(enum_name.to_string())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn make_enum_value(inner: DbClass, name: impl AsRef<str>, value: u32) -> LuaResult<EnumItem> {
|
||||
let name = name.as_ref();
|
||||
let enum_name = find_enum_name(inner, name).ok_or_else(|| {
|
||||
LuaError::RuntimeError(format!(
|
||||
"Failed to get default property '{name}' - No enum descriptor was found",
|
||||
))
|
||||
})?;
|
||||
EnumItem::from_enum_name_and_value(&enum_name, value).ok_or_else(|| {
|
||||
LuaError::RuntimeError(format!(
|
||||
"Failed to get default property '{name}' - Enum.{enum_name} does not contain numeric value {value}",
|
||||
))
|
||||
})
|
||||
}
|
69
crates/lune-roblox/src/reflection/enums.rs
Normal file
|
@ -0,0 +1,69 @@
|
|||
use std::{collections::HashMap, fmt};
|
||||
|
||||
use mlua::prelude::*;
|
||||
|
||||
use rbx_reflection::EnumDescriptor;
|
||||
|
||||
use crate::datatypes::{userdata_impl_eq, userdata_impl_to_string};
|
||||
|
||||
type DbEnum = &'static EnumDescriptor<'static>;
|
||||
|
||||
/**
|
||||
A wrapper for [`rbx_reflection::EnumDescriptor`] that
|
||||
also provides access to the class descriptor from lua.
|
||||
*/
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct DatabaseEnum(DbEnum);
|
||||
|
||||
impl DatabaseEnum {
|
||||
pub(crate) fn new(inner: DbEnum) -> Self {
|
||||
Self(inner)
|
||||
}
|
||||
|
||||
/**
|
||||
Get the name of this enum.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn get_name(&self) -> String {
|
||||
self.0.name.to_string()
|
||||
}
|
||||
|
||||
/**
|
||||
Get all known members of this enum.
|
||||
|
||||
Note that this is a direct map of name -> enum values,
|
||||
and does not actually use the `EnumItem` datatype itself.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn get_items(&self) -> HashMap<String, u32> {
|
||||
self.0
|
||||
.items
|
||||
.iter()
|
||||
.map(|(k, v)| (k.to_string(), *v))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl LuaUserData for DatabaseEnum {
|
||||
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<'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 PartialEq for DatabaseEnum {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0.name == other.0.name
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for DatabaseEnum {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "ReflectionDatabaseEnum({})", self.0.name)
|
||||
}
|
||||
}
|
151
crates/lune-roblox/src/reflection/mod.rs
Normal file
|
@ -0,0 +1,151 @@
|
|||
use std::fmt;
|
||||
|
||||
use mlua::prelude::*;
|
||||
|
||||
use rbx_reflection::ReflectionDatabase;
|
||||
|
||||
use crate::datatypes::userdata_impl_eq;
|
||||
|
||||
mod class;
|
||||
mod enums;
|
||||
mod property;
|
||||
mod utils;
|
||||
|
||||
pub use class::DatabaseClass;
|
||||
pub use enums::DatabaseEnum;
|
||||
pub use property::DatabaseProperty;
|
||||
|
||||
use super::datatypes::userdata_impl_to_string;
|
||||
|
||||
type Db = &'static ReflectionDatabase<'static>;
|
||||
|
||||
/**
|
||||
A wrapper for [`rbx_reflection::ReflectionDatabase`] that
|
||||
also provides access to the reflection database from lua.
|
||||
*/
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Database(Db);
|
||||
|
||||
impl Database {
|
||||
/**
|
||||
Creates a new database struct, referencing the bundled reflection database.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/**
|
||||
Get the version string of the database.
|
||||
|
||||
This will follow the format `x.y.z.w`, which most
|
||||
commonly looks something like `0.567.0.123456789`.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn get_version(&self) -> String {
|
||||
let [x, y, z, w] = self.0.version;
|
||||
format!("{x}.{y}.{z}.{w}")
|
||||
}
|
||||
|
||||
/**
|
||||
Retrieves a list of all currently known enum names.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn get_enum_names(&self) -> Vec<String> {
|
||||
self.0.enums.keys().map(ToString::to_string).collect()
|
||||
}
|
||||
|
||||
/**
|
||||
Retrieves a list of all currently known class names.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn get_class_names(&self) -> Vec<String> {
|
||||
self.0.classes.keys().map(ToString::to_string).collect()
|
||||
}
|
||||
|
||||
/**
|
||||
Gets an enum with the exact given name, if one exists.
|
||||
*/
|
||||
pub fn get_enum(&self, name: impl AsRef<str>) -> Option<DatabaseEnum> {
|
||||
let e = self.0.enums.get(name.as_ref())?;
|
||||
Some(DatabaseEnum::new(e))
|
||||
}
|
||||
|
||||
/**
|
||||
Gets a class with the exact given name, if one exists.
|
||||
*/
|
||||
pub fn get_class(&self, name: impl AsRef<str>) -> Option<DatabaseClass> {
|
||||
let c = self.0.classes.get(name.as_ref())?;
|
||||
Some(DatabaseClass::new(c))
|
||||
}
|
||||
|
||||
/**
|
||||
Finds an enum with the given name.
|
||||
|
||||
This will use case-insensitive matching and ignore leading and trailing whitespace.
|
||||
*/
|
||||
pub fn find_enum(&self, name: impl AsRef<str>) -> Option<DatabaseEnum> {
|
||||
let name = name.as_ref().trim().to_lowercase();
|
||||
let (ename, _) = self
|
||||
.0
|
||||
.enums
|
||||
.iter()
|
||||
.find(|(ename, _)| ename.trim().to_lowercase() == name)?;
|
||||
self.get_enum(ename)
|
||||
}
|
||||
|
||||
/**
|
||||
Finds a class with the given name.
|
||||
|
||||
This will use case-insensitive matching and ignore leading and trailing whitespace.
|
||||
*/
|
||||
pub fn find_class(&self, name: impl AsRef<str>) -> Option<DatabaseClass> {
|
||||
let name = name.as_ref().trim().to_lowercase();
|
||||
let (cname, _) = self
|
||||
.0
|
||||
.classes
|
||||
.iter()
|
||||
.find(|(cname, _)| cname.trim().to_lowercase() == name)?;
|
||||
self.get_class(cname)
|
||||
}
|
||||
}
|
||||
|
||||
impl LuaUserData for Database {
|
||||
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("Version", |_, this| Ok(this.get_version()));
|
||||
}
|
||||
|
||||
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
|
||||
methods.add_meta_method(LuaMetaMethod::ToString, userdata_impl_to_string);
|
||||
methods.add_method("GetEnumNames", |_, this, (): ()| Ok(this.get_enum_names()));
|
||||
methods.add_method(
|
||||
"GetClassNames",
|
||||
|_, this, (): ()| Ok(this.get_class_names()),
|
||||
);
|
||||
methods.add_method("GetEnum", |_, this, name: String| Ok(this.get_enum(name)));
|
||||
methods.add_method("GetClass", |_, this, name: String| Ok(this.get_class(name)));
|
||||
methods.add_method("FindEnum", |_, this, name: String| Ok(this.find_enum(name)));
|
||||
methods.add_method("FindClass", |_, this, name: String| {
|
||||
Ok(this.find_class(name))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Database {
|
||||
fn default() -> Self {
|
||||
Self(rbx_reflection_database::get())
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Database {
|
||||
fn eq(&self, _other: &Self) -> bool {
|
||||
true // All database userdatas refer to the same underlying rbx-dom database
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Database {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "ReflectionDatabase")
|
||||
}
|
||||
}
|
99
crates/lune-roblox/src/reflection/property.rs
Normal file
|
@ -0,0 +1,99 @@
|
|||
use std::fmt;
|
||||
|
||||
use mlua::prelude::*;
|
||||
|
||||
use rbx_reflection::{ClassDescriptor, PropertyDescriptor};
|
||||
|
||||
use super::utils::*;
|
||||
use crate::datatypes::{userdata_impl_eq, userdata_impl_to_string};
|
||||
|
||||
type DbClass = &'static ClassDescriptor<'static>;
|
||||
type DbProp = &'static PropertyDescriptor<'static>;
|
||||
|
||||
/**
|
||||
A wrapper for [`rbx_reflection::PropertyDescriptor`] that
|
||||
also provides access to the property descriptor from lua.
|
||||
*/
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct DatabaseProperty(DbClass, DbProp);
|
||||
|
||||
impl DatabaseProperty {
|
||||
pub(crate) fn new(inner: DbClass, inner_prop: DbProp) -> Self {
|
||||
Self(inner, inner_prop)
|
||||
}
|
||||
|
||||
/**
|
||||
Get the name of this property.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn get_name(&self) -> String {
|
||||
self.1.name.to_string()
|
||||
}
|
||||
|
||||
/**
|
||||
Get the datatype name of the property.
|
||||
|
||||
For normal datatypes this will be a string such as `string`, `Color3`, ...
|
||||
|
||||
For enums this will be a string formatted as `Enum.EnumName`.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn get_datatype_name(&self) -> String {
|
||||
data_type_to_str(self.1.data_type.clone())
|
||||
}
|
||||
|
||||
/**
|
||||
Get the scriptability of this property, meaning if it can be written / read at runtime.
|
||||
|
||||
All properties are writable and readable in Lune even if scriptability is not.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn get_scriptability_str(&self) -> &'static str {
|
||||
scriptability_to_str(self.1.scriptability)
|
||||
}
|
||||
|
||||
/**
|
||||
Get all known tags describing the property.
|
||||
|
||||
These include information such as if the property can be replicated to players
|
||||
at runtime, if the property should be hidden in Roblox Studio, and more.
|
||||
*/
|
||||
pub fn get_tags_str(&self) -> Vec<&'static str> {
|
||||
self.1
|
||||
.tags
|
||||
.iter()
|
||||
.copied()
|
||||
.map(property_tag_to_str)
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
}
|
||||
|
||||
impl LuaUserData for DatabaseProperty {
|
||||
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<'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 PartialEq for DatabaseProperty {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0.name == other.0.name && self.1.name == other.1.name
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for DatabaseProperty {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"ReflectionDatabaseProperty({} > {})",
|
||||
self.0.name, self.1.name
|
||||
)
|
||||
}
|
||||
}
|
56
crates/lune-roblox/src/reflection/utils.rs
Normal file
|
@ -0,0 +1,56 @@
|
|||
use rbx_reflection::{ClassTag, DataType, PropertyTag, Scriptability};
|
||||
|
||||
use crate::datatypes::extension::DomValueExt;
|
||||
|
||||
pub fn data_type_to_str(data_type: DataType) -> String {
|
||||
match data_type {
|
||||
DataType::Enum(e) => format!("Enum.{e}"),
|
||||
DataType::Value(v) => v
|
||||
.variant_name()
|
||||
.expect("Encountered unknown data type variant")
|
||||
.to_string(),
|
||||
_ => panic!("Encountered unknown data type"),
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
NOTE: Remember to add any new strings here to typedefs too!
|
||||
*/
|
||||
|
||||
pub fn scriptability_to_str(scriptability: Scriptability) -> &'static str {
|
||||
match scriptability {
|
||||
Scriptability::None => "None",
|
||||
Scriptability::Custom => "Custom",
|
||||
Scriptability::Read => "Read",
|
||||
Scriptability::ReadWrite => "ReadWrite",
|
||||
Scriptability::Write => "Write",
|
||||
_ => panic!("Encountered unknown scriptability"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn property_tag_to_str(tag: PropertyTag) -> &'static str {
|
||||
match tag {
|
||||
PropertyTag::Deprecated => "Deprecated",
|
||||
PropertyTag::Hidden => "Hidden",
|
||||
PropertyTag::NotBrowsable => "NotBrowsable",
|
||||
PropertyTag::NotReplicated => "NotReplicated",
|
||||
PropertyTag::NotScriptable => "NotScriptable",
|
||||
PropertyTag::ReadOnly => "ReadOnly",
|
||||
PropertyTag::WriteOnly => "WriteOnly",
|
||||
_ => panic!("Encountered unknown property tag"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn class_tag_to_str(tag: ClassTag) -> &'static str {
|
||||
match tag {
|
||||
ClassTag::Deprecated => "Deprecated",
|
||||
ClassTag::NotBrowsable => "NotBrowsable",
|
||||
ClassTag::NotCreatable => "NotCreatable",
|
||||
ClassTag::NotReplicated => "NotReplicated",
|
||||
ClassTag::PlayerReplicated => "PlayerReplicated",
|
||||
ClassTag::Service => "Service",
|
||||
ClassTag::Settings => "Settings",
|
||||
ClassTag::UserSettings => "UserSettings",
|
||||
_ => panic!("Encountered unknown class tag"),
|
||||
}
|
||||
}
|
|
@ -20,8 +20,7 @@ pub(crate) fn add_class_restricted_getter<'lua, F: LuaUserDataFields<'lua, Insta
|
|||
field_getter(lua, this)
|
||||
} else {
|
||||
Err(LuaError::RuntimeError(format!(
|
||||
"{} is not a valid member of {}",
|
||||
field_name, class_name
|
||||
"{field_name} is not a valid member of {class_name}",
|
||||
)))
|
||||
}
|
||||
});
|
||||
|
@ -42,8 +41,7 @@ pub(crate) fn add_class_restricted_setter<'lua, F: LuaUserDataFields<'lua, Insta
|
|||
field_getter(lua, this, value)
|
||||
} else {
|
||||
Err(LuaError::RuntimeError(format!(
|
||||
"{} is not a valid member of {}",
|
||||
field_name, class_name
|
||||
"{field_name} is not a valid member of {class_name}",
|
||||
)))
|
||||
}
|
||||
});
|
||||
|
@ -64,14 +62,12 @@ pub(crate) fn add_class_restricted_method<'lua, M: LuaUserDataMethods<'lua, Inst
|
|||
method(lua, this, args)
|
||||
} else {
|
||||
Err(LuaError::RuntimeError(format!(
|
||||
"{} is not a valid member of {}",
|
||||
method_name, class_name
|
||||
"{method_name} is not a valid member of {class_name}",
|
||||
)))
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn add_class_restricted_method_mut<
|
||||
'lua,
|
||||
M: LuaUserDataMethods<'lua, Instance>,
|
||||
|
@ -93,8 +89,7 @@ pub(crate) fn add_class_restricted_method_mut<
|
|||
method(lua, this, args)
|
||||
} else {
|
||||
Err(LuaError::RuntimeError(format!(
|
||||
"{} is not a valid member of {}",
|
||||
method_name, class_name
|
||||
"{method_name} is not a valid member of {class_name}",
|
||||
)))
|
||||
}
|
||||
});
|
||||
|
@ -103,7 +98,7 @@ pub(crate) fn add_class_restricted_method_mut<
|
|||
/**
|
||||
Gets or creates the instance child with the given reference prop name and class name.
|
||||
|
||||
Note that the class name here must be an exact match, it is not checked using IsA.
|
||||
Note that the class name here must be an exact match, it is not checked using `IsA`.
|
||||
|
||||
The instance may be in one of several states but this function will guarantee that the
|
||||
property reference is correct and that the instance exists after it has been called:
|
||||
|
@ -127,7 +122,7 @@ pub(crate) fn get_or_create_property_ref_instance(
|
|||
Ok(inst)
|
||||
} else {
|
||||
let inst = Instance::new_orphaned(class_name);
|
||||
inst.set_parent(Some(this.clone()));
|
||||
inst.set_parent(Some(*this));
|
||||
this.set_property(prop_name, DomValue::Ref(inst.dom_ref));
|
||||
Ok(inst)
|
||||
}
|
|
@ -60,12 +60,12 @@ pub(crate) fn find_property_info(
|
|||
value_type: Some(*value_type),
|
||||
..Default::default()
|
||||
},
|
||||
_ => Default::default(),
|
||||
_ => PropertyInfo::default(),
|
||||
});
|
||||
break;
|
||||
} else if let Some(sup) = &class.superclass {
|
||||
// No property found, we should look at the superclass
|
||||
class_name = Cow::Borrowed(sup)
|
||||
class_name = Cow::Borrowed(sup);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
@ -87,7 +87,7 @@ pub(crate) fn find_property_info(
|
|||
break;
|
||||
} else if let Some(sup) = &class.superclass {
|
||||
// No default value found, we should look at the superclass
|
||||
class_name = Cow::Borrowed(sup)
|
||||
class_name = Cow::Borrowed(sup);
|
||||
} else {
|
||||
break;
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
#![allow(clippy::missing_errors_doc)]
|
||||
|
||||
use std::{any::type_name, cell::RefCell, fmt, ops};
|
||||
|
||||
use mlua::prelude::*;
|
||||
|
@ -5,21 +7,29 @@ use mlua::prelude::*;
|
|||
// Utility functions
|
||||
|
||||
type ListWriter = dyn Fn(&mut fmt::Formatter<'_>, bool, &str) -> fmt::Result;
|
||||
|
||||
#[must_use]
|
||||
pub fn make_list_writer() -> Box<ListWriter> {
|
||||
let first = RefCell::new(true);
|
||||
Box::new(move |f, flag, literal| {
|
||||
if flag {
|
||||
if first.take() {
|
||||
write!(f, "{}", literal)?;
|
||||
write!(f, "{literal}")?;
|
||||
} else {
|
||||
write!(f, ", {}", literal)?;
|
||||
write!(f, ", {literal}")?;
|
||||
}
|
||||
}
|
||||
Ok::<_, fmt::Error>(())
|
||||
})
|
||||
}
|
||||
|
||||
// Userdata metamethod implementations
|
||||
/*
|
||||
Userdata metamethod implementations
|
||||
|
||||
Note that many of these return [`LuaResult`] even though they don't
|
||||
return any errors - this is for consistency reasons and to make it
|
||||
easier to add these blanket implementations to [`LuaUserData`] impls.
|
||||
*/
|
||||
|
||||
pub fn userdata_impl_to_string<D>(_: &Lua, datatype: &D, _: ()) -> LuaResult<String>
|
||||
where
|
||||
|
@ -139,6 +149,37 @@ where
|
|||
})
|
||||
}
|
||||
|
||||
pub trait IDiv<Rhs = Self> {
|
||||
type Output;
|
||||
#[must_use]
|
||||
fn idiv(self, rhs: Rhs) -> Self::Output;
|
||||
}
|
||||
|
||||
pub fn userdata_impl_idiv_f32<D>(_: &Lua, datatype: &D, rhs: LuaValue) -> LuaResult<D>
|
||||
where
|
||||
D: LuaUserData + IDiv<D, Output = D> + IDiv<f32, Output = D> + Copy + 'static,
|
||||
{
|
||||
match &rhs {
|
||||
LuaValue::Number(n) => return Ok(datatype.idiv(*n as f32)),
|
||||
LuaValue::Integer(i) => return Ok(datatype.idiv(*i as f32)),
|
||||
LuaValue::UserData(ud) => {
|
||||
if let Ok(vec) = ud.borrow::<D>() {
|
||||
return Ok(datatype.idiv(*vec));
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
Err(LuaError::FromLuaConversionError {
|
||||
from: rhs.type_name(),
|
||||
to: type_name::<D>(),
|
||||
message: Some(format!(
|
||||
"Expected {} or number, got {}",
|
||||
type_name::<D>(),
|
||||
rhs.type_name()
|
||||
)),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn userdata_impl_div_i32<D>(_: &Lua, datatype: &D, rhs: LuaValue) -> LuaResult<D>
|
||||
where
|
||||
D: LuaUserData + ops::Div<D, Output = D> + ops::Div<i32, Output = D> + Copy + 'static,
|
22
crates/lune-std-datetime/Cargo.toml
Normal file
|
@ -0,0 +1,22 @@
|
|||
[package]
|
||||
name = "lune-std-datetime"
|
||||
version = "0.1.3"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/lune-org/lune"
|
||||
description = "Lune standard library - DateTime"
|
||||
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
mlua = { version = "0.9.9", features = ["luau"] }
|
||||
|
||||
thiserror = "1.0"
|
||||
chrono = "0.4.38"
|
||||
chrono_lc = "0.1.6"
|
||||
|
||||
lune-utils = { version = "0.1.3", path = "../lune-utils" }
|
250
crates/lune-std-datetime/src/date_time.rs
Normal file
|
@ -0,0 +1,250 @@
|
|||
use std::cmp::Ordering;
|
||||
|
||||
use mlua::prelude::*;
|
||||
|
||||
use chrono::prelude::*;
|
||||
use chrono::DateTime as ChronoDateTime;
|
||||
use chrono_lc::LocaleDate;
|
||||
|
||||
use crate::result::{DateTimeError, DateTimeResult};
|
||||
use crate::values::DateTimeValues;
|
||||
|
||||
const DEFAULT_FORMAT: &str = "%Y-%m-%d %H:%M:%S";
|
||||
const DEFAULT_LOCALE: &str = "en";
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct DateTime {
|
||||
// NOTE: We store this as the UTC time zone since it is the most commonly
|
||||
// used and getting the generics right for TimeZone is somewhat tricky,
|
||||
// but none of the method implementations below should rely on this tz
|
||||
inner: ChronoDateTime<Utc>,
|
||||
}
|
||||
|
||||
impl DateTime {
|
||||
/**
|
||||
Creates a new `DateTime` struct representing the current moment in time.
|
||||
|
||||
See [`chrono::DateTime::now`] for additional details.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn now() -> Self {
|
||||
Self { inner: Utc::now() }
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a new `DateTime` struct from the given `unix_timestamp`,
|
||||
which is a float of seconds passed since the UNIX epoch.
|
||||
|
||||
This is somewhat unconventional, but fits our Luau interface and dynamic types quite well.
|
||||
To use this method the same way you would use a more traditional `from_unix_timestamp`
|
||||
that takes a `u64` of seconds or similar type, casting the value is sufficient:
|
||||
|
||||
```rust ignore
|
||||
DateTime::from_unix_timestamp_float(123456789u64 as f64)
|
||||
```
|
||||
|
||||
See [`chrono::DateTime::from_timestamp`] for additional details.
|
||||
|
||||
# Errors
|
||||
|
||||
Returns an error if the input value is out of range.
|
||||
*/
|
||||
pub fn from_unix_timestamp_float(unix_timestamp: f64) -> DateTimeResult<Self> {
|
||||
let whole = unix_timestamp.trunc() as i64;
|
||||
let fract = unix_timestamp.fract();
|
||||
let nanos = (fract * 1_000_000_000f64)
|
||||
.round()
|
||||
.clamp(u32::MIN as f64, u32::MAX as f64) as u32;
|
||||
let inner = ChronoDateTime::<Utc>::from_timestamp(whole, nanos)
|
||||
.ok_or(DateTimeError::OutOfRangeUnspecified)?;
|
||||
Ok(Self { inner })
|
||||
}
|
||||
|
||||
/**
|
||||
Transforms individual date & time values into a new
|
||||
`DateTime` struct, using the universal (UTC) time zone.
|
||||
|
||||
See [`chrono::NaiveDate::from_ymd_opt`] and [`chrono::NaiveTime::from_hms_milli_opt`]
|
||||
for additional details and cases where this constructor may return an error.
|
||||
|
||||
# Errors
|
||||
|
||||
Returns an error if the date or time values are invalid.
|
||||
*/
|
||||
pub fn from_universal_time(values: &DateTimeValues) -> DateTimeResult<Self> {
|
||||
let date = NaiveDate::from_ymd_opt(values.year, values.month, values.day)
|
||||
.ok_or(DateTimeError::InvalidDate)?;
|
||||
|
||||
let time = NaiveTime::from_hms_milli_opt(
|
||||
values.hour,
|
||||
values.minute,
|
||||
values.second,
|
||||
values.millisecond,
|
||||
)
|
||||
.ok_or(DateTimeError::InvalidTime)?;
|
||||
|
||||
let inner = Utc.from_utc_datetime(&NaiveDateTime::new(date, time));
|
||||
|
||||
Ok(Self { inner })
|
||||
}
|
||||
|
||||
/**
|
||||
Transforms individual date & time values into a new
|
||||
`DateTime` struct, using the current local time zone.
|
||||
|
||||
See [`chrono::NaiveDate::from_ymd_opt`] and [`chrono::NaiveTime::from_hms_milli_opt`]
|
||||
for additional details and cases where this constructor may return an error.
|
||||
|
||||
# Errors
|
||||
|
||||
Returns an error if the date or time values are invalid or ambiguous.
|
||||
*/
|
||||
pub fn from_local_time(values: &DateTimeValues) -> DateTimeResult<Self> {
|
||||
let date = NaiveDate::from_ymd_opt(values.year, values.month, values.day)
|
||||
.ok_or(DateTimeError::InvalidDate)?;
|
||||
|
||||
let time = NaiveTime::from_hms_milli_opt(
|
||||
values.hour,
|
||||
values.minute,
|
||||
values.second,
|
||||
values.millisecond,
|
||||
)
|
||||
.ok_or(DateTimeError::InvalidTime)?;
|
||||
|
||||
let inner = Local
|
||||
.from_local_datetime(&NaiveDateTime::new(date, time))
|
||||
.single()
|
||||
.ok_or(DateTimeError::Ambiguous)?
|
||||
.with_timezone(&Utc);
|
||||
|
||||
Ok(Self { inner })
|
||||
}
|
||||
|
||||
/**
|
||||
Formats the `DateTime` using the universal (UTC) time
|
||||
zone, the given format string, and the given locale.
|
||||
|
||||
`format` and `locale` default to `"%Y-%m-%d %H:%M:%S"` and `"en"` respectively.
|
||||
|
||||
See [`chrono_lc::DateTime::formatl`] for additional details.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn format_string_local(&self, format: Option<&str>, locale: Option<&str>) -> String {
|
||||
self.inner
|
||||
.with_timezone(&Local)
|
||||
.formatl(
|
||||
format.unwrap_or(DEFAULT_FORMAT),
|
||||
locale.unwrap_or(DEFAULT_LOCALE),
|
||||
)
|
||||
.to_string()
|
||||
}
|
||||
|
||||
/**
|
||||
Formats the `DateTime` using the universal (UTC) time
|
||||
zone, the given format string, and the given locale.
|
||||
|
||||
`format` and `locale` default to `"%Y-%m-%d %H:%M:%S"` and `"en"` respectively.
|
||||
|
||||
See [`chrono_lc::DateTime::formatl`] for additional details.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn format_string_universal(&self, format: Option<&str>, locale: Option<&str>) -> String {
|
||||
self.inner
|
||||
.with_timezone(&Utc)
|
||||
.formatl(
|
||||
format.unwrap_or(DEFAULT_FORMAT),
|
||||
locale.unwrap_or(DEFAULT_LOCALE),
|
||||
)
|
||||
.to_string()
|
||||
}
|
||||
|
||||
/**
|
||||
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.
|
||||
|
||||
# Errors
|
||||
|
||||
Returns an error if the input string is not a valid RFC 3339 date-time.
|
||||
*/
|
||||
pub fn from_iso_date(iso_date: impl AsRef<str>) -> DateTimeResult<Self> {
|
||||
let inner = ChronoDateTime::parse_from_rfc3339(iso_date.as_ref())?.with_timezone(&Utc);
|
||||
Ok(Self { inner })
|
||||
}
|
||||
|
||||
/**
|
||||
Extracts individual date & time values from this
|
||||
`DateTime`, using the current local time zone.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn to_local_time(self) -> DateTimeValues {
|
||||
DateTimeValues::from(self.inner.with_timezone(&Local))
|
||||
}
|
||||
|
||||
/**
|
||||
Extracts individual date & time values from this
|
||||
`DateTime`, using the universal (UTC) time zone.
|
||||
*/
|
||||
#[must_use]
|
||||
pub fn to_universal_time(self) -> DateTimeValues {
|
||||
DateTimeValues::from(self.inner.with_timezone(&Utc))
|
||||
}
|
||||
|
||||
/**
|
||||
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_iso_date(self) -> String {
|
||||
self.inner.to_rfc3339()
|
||||
}
|
||||
}
|
||||
|
||||
impl LuaUserData for DateTime {
|
||||
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<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
|
||||
// Metamethods to compare DateTime as instants in time
|
||||
methods.add_meta_method(
|
||||
LuaMetaMethod::Eq,
|
||||
|_, this: &Self, other: LuaUserDataRef<Self>| Ok(this.eq(&other)),
|
||||
);
|
||||
methods.add_meta_method(
|
||||
LuaMetaMethod::Lt,
|
||||
|_, this: &Self, other: LuaUserDataRef<Self>| {
|
||||
Ok(matches!(this.cmp(&other), Ordering::Less))
|
||||
},
|
||||
);
|
||||
methods.add_meta_method(
|
||||
LuaMetaMethod::Le,
|
||||
|_, this: &Self, other: LuaUserDataRef<Self>| {
|
||||
Ok(matches!(this.cmp(&other), Ordering::Less | Ordering::Equal))
|
||||
},
|
||||
);
|
||||
// Normal methods
|
||||
methods.add_method("toIsoDate", |_, this, ()| Ok(this.to_iso_date()));
|
||||
methods.add_method(
|
||||
"formatUniversalTime",
|
||||
|_, this, (format, locale): (Option<String>, Option<String>)| {
|
||||
Ok(this.format_string_universal(format.as_deref(), locale.as_deref()))
|
||||
},
|
||||
);
|
||||
methods.add_method(
|
||||
"formatLocalTime",
|
||||
|_, this, (format, locale): (Option<String>, Option<String>)| {
|
||||
Ok(this.format_string_local(format.as_deref(), locale.as_deref()))
|
||||
},
|
||||
);
|
||||
methods.add_method("toUniversalTime", |_, this: &Self, ()| {
|
||||
Ok(this.to_universal_time())
|
||||
});
|
||||
methods.add_method("toLocalTime", |_, this: &Self, ()| Ok(this.to_local_time()));
|
||||
}
|
||||
}
|
36
crates/lune-std-datetime/src/lib.rs
Normal file
|
@ -0,0 +1,36 @@
|
|||
#![allow(clippy::cargo_common_metadata)]
|
||||
|
||||
use mlua::prelude::*;
|
||||
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
mod date_time;
|
||||
mod result;
|
||||
mod values;
|
||||
|
||||
pub use self::date_time::DateTime;
|
||||
|
||||
/**
|
||||
Creates the `datetime` standard library module.
|
||||
|
||||
# Errors
|
||||
|
||||
Errors when out of memory.
|
||||
*/
|
||||
pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
|
||||
TableBuilder::new(lua)?
|
||||
.with_function("fromIsoDate", |_, iso_date: String| {
|
||||
Ok(DateTime::from_iso_date(iso_date)?)
|
||||
})?
|
||||
.with_function("fromLocalTime", |_, values| {
|
||||
Ok(DateTime::from_local_time(&values)?)
|
||||
})?
|
||||
.with_function("fromUniversalTime", |_, values| {
|
||||
Ok(DateTime::from_universal_time(&values)?)
|
||||
})?
|
||||
.with_function("fromUnixTimestamp", |_, timestamp| {
|
||||
Ok(DateTime::from_unix_timestamp_float(timestamp)?)
|
||||
})?
|
||||
.with_function("now", |_, ()| Ok(DateTime::now()))?
|
||||
.build_readonly()
|
||||
}
|
32
crates/lune-std-datetime/src/result.rs
Normal file
|
@ -0,0 +1,32 @@
|
|||
use mlua::prelude::*;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
pub type DateTimeResult<T, E = DateTimeError> = Result<T, E>;
|
||||
|
||||
#[derive(Debug, Clone, Error)]
|
||||
pub enum DateTimeError {
|
||||
#[error("invalid date")]
|
||||
InvalidDate,
|
||||
#[error("invalid time")]
|
||||
InvalidTime,
|
||||
#[error("ambiguous date or time")]
|
||||
Ambiguous,
|
||||
#[error("date or time is outside allowed range")]
|
||||
OutOfRangeUnspecified,
|
||||
#[error("{name} must be within range {min} -> {max}, got {value}")]
|
||||
OutOfRange {
|
||||
name: &'static str,
|
||||
value: String,
|
||||
min: String,
|
||||
max: String,
|
||||
},
|
||||
#[error(transparent)]
|
||||
ParseError(#[from] chrono::ParseError),
|
||||
}
|
||||
|
||||
impl From<DateTimeError> for LuaError {
|
||||
fn from(value: DateTimeError) -> Self {
|
||||
LuaError::runtime(value.to_string())
|
||||
}
|
||||
}
|
170
crates/lune-std-datetime/src/values.rs
Normal file
|
@ -0,0 +1,170 @@
|
|||
use mlua::prelude::*;
|
||||
|
||||
use chrono::prelude::*;
|
||||
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
use super::result::{DateTimeError, DateTimeResult};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct DateTimeValues {
|
||||
pub year: i32,
|
||||
pub month: u32,
|
||||
pub day: u32,
|
||||
pub hour: u32,
|
||||
pub minute: u32,
|
||||
pub second: u32,
|
||||
pub millisecond: u32,
|
||||
}
|
||||
|
||||
impl DateTimeValues {
|
||||
/**
|
||||
Verifies that all of the date & time values are within allowed ranges:
|
||||
|
||||
| Name | Range |
|
||||
|---------------|----------------|
|
||||
| `year` | `1400 -> 9999` |
|
||||
| `month` | `1 -> 12` |
|
||||
| `day` | `1 -> 31` |
|
||||
| `hour` | `0 -> 23` |
|
||||
| `minute` | `0 -> 59` |
|
||||
| `second` | `0 -> 60` |
|
||||
| `millisecond` | `0 -> 999` |
|
||||
*/
|
||||
pub fn verify(self) -> DateTimeResult<Self> {
|
||||
verify_in_range("year", self.year, 1400, 9999)?;
|
||||
verify_in_range("month", self.month, 1, 12)?;
|
||||
verify_in_range("day", self.day, 1, 31)?;
|
||||
verify_in_range("hour", self.hour, 0, 23)?;
|
||||
verify_in_range("minute", self.minute, 0, 59)?;
|
||||
verify_in_range("second", self.second, 0, 60)?;
|
||||
verify_in_range("millisecond", self.millisecond, 0, 999)?;
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
fn verify_in_range<T>(name: &'static str, value: T, min: T, max: T) -> DateTimeResult<T>
|
||||
where
|
||||
T: PartialOrd + std::fmt::Display,
|
||||
{
|
||||
assert!(max > min);
|
||||
if value < min || value > max {
|
||||
Err(DateTimeError::OutOfRange {
|
||||
name,
|
||||
min: min.to_string(),
|
||||
max: max.to_string(),
|
||||
value: value.to_string(),
|
||||
})
|
||||
} else {
|
||||
Ok(value)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Conversion methods between `DateTimeValues` and plain lua tables
|
||||
|
||||
Note that the `IntoLua` implementation here uses a read-only table,
|
||||
since we generally want to convert into lua when we know we have
|
||||
a fixed point in time, and we guarantee that it doesn't change
|
||||
*/
|
||||
|
||||
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",
|
||||
message: Some("value must be a table".to_string()),
|
||||
});
|
||||
};
|
||||
|
||||
let value = value.as_table().unwrap();
|
||||
let values = Self {
|
||||
year: value.get("year")?,
|
||||
month: value.get("month")?,
|
||||
day: value.get("day")?,
|
||||
hour: value.get("hour")?,
|
||||
minute: value.get("minute")?,
|
||||
second: value.get("second")?,
|
||||
millisecond: value.get("millisecond").unwrap_or(0),
|
||||
};
|
||||
|
||||
match values.verify() {
|
||||
Ok(dt) => Ok(dt),
|
||||
Err(e) => Err(LuaError::FromLuaConversionError {
|
||||
from: "table",
|
||||
to: "DateTimeValues",
|
||||
message: Some(e.to_string()),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoLua<'_> for DateTimeValues {
|
||||
fn into_lua(self, lua: &Lua) -> LuaResult<LuaValue> {
|
||||
let tab = TableBuilder::new(lua)?
|
||||
.with_value("year", self.year)?
|
||||
.with_values(vec![
|
||||
("month", self.month),
|
||||
("day", self.day),
|
||||
("hour", self.hour),
|
||||
("minute", self.minute),
|
||||
("second", self.second),
|
||||
("millisecond", self.millisecond),
|
||||
])?
|
||||
.build_readonly()?;
|
||||
Ok(LuaValue::Table(tab))
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Conversion methods between chrono's timezone-aware `DateTime` to
|
||||
and from our non-timezone-aware `DateTimeValues` values struct
|
||||
*/
|
||||
|
||||
impl<T: TimeZone> From<DateTime<T>> for DateTimeValues {
|
||||
fn from(value: DateTime<T>) -> Self {
|
||||
Self {
|
||||
year: value.year(),
|
||||
month: value.month(),
|
||||
day: value.day(),
|
||||
hour: value.hour(),
|
||||
minute: value.minute(),
|
||||
second: value.second(),
|
||||
millisecond: value.timestamp_subsec_millis(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<DateTimeValues> for DateTime<Utc> {
|
||||
type Error = DateTimeError;
|
||||
fn try_from(value: DateTimeValues) -> Result<Self, Self::Error> {
|
||||
Utc.with_ymd_and_hms(
|
||||
value.year,
|
||||
value.month,
|
||||
value.day,
|
||||
value.hour,
|
||||
value.minute,
|
||||
value.second,
|
||||
)
|
||||
.single()
|
||||
.ok_or(DateTimeError::Ambiguous)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<DateTimeValues> for DateTime<Local> {
|
||||
type Error = DateTimeError;
|
||||
fn try_from(value: DateTimeValues) -> Result<Self, Self::Error> {
|
||||
Local
|
||||
.with_ymd_and_hms(
|
||||
value.year,
|
||||
value.month,
|
||||
value.day,
|
||||
value.hour,
|
||||
value.minute,
|
||||
value.second,
|
||||
)
|
||||
.single()
|
||||
.ok_or(DateTimeError::Ambiguous)
|
||||
}
|
||||
}
|
23
crates/lune-std-fs/Cargo.toml
Normal file
|
@ -0,0 +1,23 @@
|
|||
[package]
|
||||
name = "lune-std-fs"
|
||||
version = "0.1.2"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/lune-org/lune"
|
||||
description = "Lune standard library - FS"
|
||||
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
mlua = { version = "0.9.9", features = ["luau"] }
|
||||
|
||||
bstr = "1.9"
|
||||
|
||||
tokio = { version = "1", default-features = false, features = ["fs"] }
|
||||
|
||||
lune-utils = { version = "0.1.3", path = "../lune-utils" }
|
||||
lune-std-datetime = { version = "0.1.2", path = "../lune-std-datetime" }
|
166
crates/lune-std-fs/src/copy.rs
Normal file
|
@ -0,0 +1,166 @@
|
|||
use std::collections::VecDeque;
|
||||
use std::io::ErrorKind;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use mlua::prelude::*;
|
||||
use tokio::fs;
|
||||
|
||||
use super::options::FsWriteOptions;
|
||||
|
||||
pub struct CopyContents {
|
||||
// Vec<(relative depth, path)>
|
||||
pub dirs: Vec<(usize, PathBuf)>,
|
||||
pub files: Vec<(usize, PathBuf)>,
|
||||
}
|
||||
|
||||
async fn get_contents_at(root: PathBuf, _: FsWriteOptions) -> LuaResult<CopyContents> {
|
||||
let mut dirs = Vec::new();
|
||||
let mut files = Vec::new();
|
||||
|
||||
let mut queue = VecDeque::new();
|
||||
|
||||
let normalized_root = fs::canonicalize(&root).await.map_err(|e| {
|
||||
LuaError::RuntimeError(format!("Failed to canonicalize root directory path\n{e}"))
|
||||
})?;
|
||||
|
||||
// Push initial children of the root path into the queue
|
||||
let mut entries = fs::read_dir(&normalized_root).await?;
|
||||
while let Some(entry) = entries.next_entry().await? {
|
||||
queue.push_back((1, entry.path()));
|
||||
}
|
||||
|
||||
// Go through the current queue, pushing to it
|
||||
// when we find any new descendant directories
|
||||
// FUTURE: Try to do async reading here concurrently to speed it up a bit
|
||||
while let Some((current_depth, current_path)) = queue.pop_front() {
|
||||
let meta = fs::metadata(¤t_path).await?;
|
||||
if meta.is_symlink() {
|
||||
return Err(LuaError::RuntimeError(format!(
|
||||
"Symlinks are not yet supported, encountered at path '{}'",
|
||||
current_path.display()
|
||||
)));
|
||||
} else if meta.is_dir() {
|
||||
// FUTURE: Add an option in FsWriteOptions for max depth and limit it here
|
||||
let mut entries = fs::read_dir(¤t_path).await?;
|
||||
while let Some(entry) = entries.next_entry().await? {
|
||||
queue.push_back((current_depth + 1, entry.path()));
|
||||
}
|
||||
dirs.push((current_depth, current_path));
|
||||
} else {
|
||||
files.push((current_depth, current_path));
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that all directory and file paths are relative to the root path
|
||||
// SAFETY: Since we only ever push dirs and files relative to the root, unwrap is safe
|
||||
for (_, dir) in &mut dirs {
|
||||
*dir = dir.strip_prefix(&normalized_root).unwrap().to_path_buf();
|
||||
}
|
||||
for (_, file) in &mut files {
|
||||
*file = file.strip_prefix(&normalized_root).unwrap().to_path_buf();
|
||||
}
|
||||
|
||||
// FUTURE: Deduplicate paths such that these directories:
|
||||
// - foo/
|
||||
// - foo/bar/
|
||||
// - foo/bar/baz/
|
||||
// turn into a single foo/bar/baz/ and let create_dir_all do the heavy lifting
|
||||
|
||||
Ok(CopyContents { dirs, files })
|
||||
}
|
||||
|
||||
async fn ensure_no_dir_exists(path: impl AsRef<Path>) -> LuaResult<()> {
|
||||
let path = path.as_ref();
|
||||
match fs::metadata(&path).await {
|
||||
Ok(meta) if meta.is_dir() => Err(LuaError::RuntimeError(format!(
|
||||
"A directory already exists at the path '{}'",
|
||||
path.display()
|
||||
))),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
async fn ensure_no_file_exists(path: impl AsRef<Path>) -> LuaResult<()> {
|
||||
let path = path.as_ref();
|
||||
match fs::metadata(&path).await {
|
||||
Ok(meta) if meta.is_file() => Err(LuaError::RuntimeError(format!(
|
||||
"A file already exists at the path '{}'",
|
||||
path.display()
|
||||
))),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn copy(
|
||||
source: impl AsRef<Path>,
|
||||
target: impl AsRef<Path>,
|
||||
options: FsWriteOptions,
|
||||
) -> LuaResult<()> {
|
||||
let source = source.as_ref();
|
||||
let target = target.as_ref();
|
||||
|
||||
// Check if we got a file or directory - we will handle them differently below
|
||||
let (is_dir, is_file) = match fs::metadata(&source).await {
|
||||
Ok(meta) => (meta.is_dir(), meta.is_file()),
|
||||
Err(e) if e.kind() == ErrorKind::NotFound => {
|
||||
return Err(LuaError::RuntimeError(format!(
|
||||
"No file or directory exists at the path '{}'",
|
||||
source.display()
|
||||
)))
|
||||
}
|
||||
Err(e) => return Err(e.into()),
|
||||
};
|
||||
if !is_file && !is_dir {
|
||||
return Err(LuaError::RuntimeError(format!(
|
||||
"The given path '{}' is not a file or a directory",
|
||||
source.display()
|
||||
)));
|
||||
}
|
||||
|
||||
// Perform copying:
|
||||
//
|
||||
// 1. If we are not allowed to overwrite, make sure nothing exists at the target path
|
||||
// 2. If we are allowed to overwrite, remove any previous entry at the path
|
||||
// 3. Write all directories first
|
||||
// 4. Write all files
|
||||
|
||||
if !options.overwrite {
|
||||
if is_file {
|
||||
ensure_no_file_exists(target).await?;
|
||||
} else if is_dir {
|
||||
ensure_no_dir_exists(target).await?;
|
||||
}
|
||||
}
|
||||
|
||||
if is_file {
|
||||
fs::copy(source, target).await?;
|
||||
} else if is_dir {
|
||||
let contents = get_contents_at(source.to_path_buf(), options).await?;
|
||||
|
||||
if options.overwrite {
|
||||
let (is_dir, is_file) = match fs::metadata(&target).await {
|
||||
Ok(meta) => (meta.is_dir(), meta.is_file()),
|
||||
Err(e) if e.kind() == ErrorKind::NotFound => (false, false),
|
||||
Err(e) => return Err(e.into()),
|
||||
};
|
||||
if is_dir {
|
||||
fs::remove_dir_all(target).await?;
|
||||
} else if is_file {
|
||||
fs::remove_file(target).await?;
|
||||
}
|
||||
}
|
||||
|
||||
fs::create_dir_all(target).await?;
|
||||
|
||||
// FUTURE: Write dirs / files concurrently
|
||||
// to potentially speed these operations up
|
||||
for (_, dir) in &contents.dirs {
|
||||
fs::create_dir_all(target.join(dir)).await?;
|
||||
}
|
||||
for (_, file) in &contents.files {
|
||||
fs::copy(source.join(file), target.join(file)).await?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
126
crates/lune-std-fs/src/lib.rs
Executable file
|
@ -0,0 +1,126 @@
|
|||
#![allow(clippy::cargo_common_metadata)]
|
||||
|
||||
use std::io::ErrorKind as IoErrorKind;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use bstr::{BString, ByteSlice};
|
||||
use mlua::prelude::*;
|
||||
use tokio::fs;
|
||||
|
||||
use lune_utils::TableBuilder;
|
||||
|
||||
mod copy;
|
||||
mod metadata;
|
||||
mod options;
|
||||
|
||||
use self::copy::copy;
|
||||
use self::metadata::FsMetadata;
|
||||
use self::options::FsWriteOptions;
|
||||
|
||||
/**
|
||||
Creates the `fs` standard library module.
|
||||
|
||||
# Errors
|
||||
|
||||
Errors when out of memory.
|
||||
*/
|
||||
pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
|
||||
TableBuilder::new(lua)?
|
||||
.with_async_function("readFile", fs_read_file)?
|
||||
.with_async_function("readDir", fs_read_dir)?
|
||||
.with_async_function("writeFile", fs_write_file)?
|
||||
.with_async_function("writeDir", fs_write_dir)?
|
||||
.with_async_function("removeFile", fs_remove_file)?
|
||||
.with_async_function("removeDir", fs_remove_dir)?
|
||||
.with_async_function("metadata", fs_metadata)?
|
||||
.with_async_function("isFile", fs_is_file)?
|
||||
.with_async_function("isDir", fs_is_dir)?
|
||||
.with_async_function("move", fs_move)?
|
||||
.with_async_function("copy", fs_copy)?
|
||||
.build_readonly()
|
||||
}
|
||||
|
||||
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>> {
|
||||
let mut dir_strings = Vec::new();
|
||||
let mut dir = fs::read_dir(&path).await.into_lua_err()?;
|
||||
while let Some(dir_entry) = dir.next_entry().await.into_lua_err()? {
|
||||
if let Some(dir_name_str) = dir_entry.file_name().to_str() {
|
||||
dir_strings.push(dir_name_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()
|
||||
)));
|
||||
}
|
||||
}
|
||||
Ok(dir_strings)
|
||||
}
|
||||
|
||||
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<()> {
|
||||
fs::create_dir_all(&path).await.into_lua_err()
|
||||
}
|
||||
|
||||
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<()> {
|
||||
fs::remove_dir_all(&path).await.into_lua_err()
|
||||
}
|
||||
|
||||
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)),
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
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()),
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
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()),
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
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!(
|
||||
"No file or directory exists at the path '{}'",
|
||||
path_from.display()
|
||||
)));
|
||||
}
|
||||
let path_to = PathBuf::from(to);
|
||||
if !options.overwrite && path_to.exists() {
|
||||
return Err(LuaError::RuntimeError(format!(
|
||||
"A file or directory already exists at the path '{}'",
|
||||
path_to.display()
|
||||
)));
|
||||
}
|
||||
fs::rename(path_from, path_to).await.into_lua_err()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn fs_copy(_: &Lua, (from, to, options): (String, String, FsWriteOptions)) -> LuaResult<()> {
|
||||
copy(from, to, options).await
|
||||
}
|
154
crates/lune-std-fs/src/metadata.rs
Normal file
|
@ -0,0 +1,154 @@
|
|||
use std::{
|
||||
fmt,
|
||||
fs::{FileType as StdFileType, Metadata as StdMetadata, Permissions as StdPermissions},
|
||||
io::Result as IoResult,
|
||||
str::FromStr,
|
||||
time::SystemTime,
|
||||
};
|
||||
|
||||
use mlua::prelude::*;
|
||||
|
||||
use lune_std_datetime::DateTime;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum FsMetadataKind {
|
||||
None,
|
||||
File,
|
||||
Dir,
|
||||
Symlink,
|
||||
}
|
||||
|
||||
impl fmt::Display for FsMetadataKind {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Self::None => "none",
|
||||
Self::File => "file",
|
||||
Self::Dir => "dir",
|
||||
Self::Symlink => "symlink",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for FsMetadataKind {
|
||||
type Err = &'static str;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.trim().to_ascii_lowercase().as_ref() {
|
||||
"none" => Ok(Self::None),
|
||||
"file" => Ok(Self::File),
|
||||
"dir" => Ok(Self::Dir),
|
||||
"symlink" => Ok(Self::Symlink),
|
||||
_ => Err("Invalid metadata kind"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StdFileType> for FsMetadataKind {
|
||||
fn from(value: StdFileType) -> Self {
|
||||
if value.is_file() {
|
||||
Self::File
|
||||
} else if value.is_dir() {
|
||||
Self::Dir
|
||||
} else if value.is_symlink() {
|
||||
Self::Symlink
|
||||
} else {
|
||||
panic!("Encountered unknown filesystem filetype")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'lua> IntoLua<'lua> for FsMetadataKind {
|
||||
fn into_lua(self, lua: &'lua Lua) -> LuaResult<LuaValue<'lua>> {
|
||||
if self == Self::None {
|
||||
Ok(LuaValue::Nil)
|
||||
} else {
|
||||
self.to_string().into_lua(lua)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FsPermissions {
|
||||
pub(crate) read_only: bool,
|
||||
}
|
||||
|
||||
impl From<StdPermissions> for FsPermissions {
|
||||
fn from(value: StdPermissions) -> Self {
|
||||
Self {
|
||||
read_only: value.readonly(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
Ok(LuaValue::Table(tab))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct FsMetadata {
|
||||
pub(crate) kind: FsMetadataKind,
|
||||
pub(crate) exists: bool,
|
||||
pub(crate) created_at: Option<DateTime>,
|
||||
pub(crate) modified_at: Option<DateTime>,
|
||||
pub(crate) accessed_at: Option<DateTime>,
|
||||
pub(crate) permissions: Option<FsPermissions>,
|
||||
}
|
||||
|
||||
impl FsMetadata {
|
||||
pub fn not_found() -> Self {
|
||||
Self {
|
||||
kind: FsMetadataKind::None,
|
||||
exists: false,
|
||||
created_at: None,
|
||||
modified_at: None,
|
||||
accessed_at: None,
|
||||
permissions: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)?;
|
||||
tab.set("createdAt", self.created_at)?;
|
||||
tab.set("modifiedAt", self.modified_at)?;
|
||||
tab.set("accessedAt", self.accessed_at)?;
|
||||
tab.set("permissions", self.permissions)?;
|
||||
tab.set_readonly(true);
|
||||
Ok(LuaValue::Table(tab))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StdMetadata> for FsMetadata {
|
||||
fn from(value: StdMetadata) -> Self {
|
||||
Self {
|
||||
kind: value.file_type().into(),
|
||||
exists: true,
|
||||
created_at: system_time_to_timestamp(value.created()),
|
||||
modified_at: system_time_to_timestamp(value.modified()),
|
||||
accessed_at: system_time_to_timestamp(value.accessed()),
|
||||
permissions: Some(FsPermissions::from(value.permissions())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn system_time_to_timestamp(res: IoResult<SystemTime>) -> Option<DateTime> {
|
||||
match res {
|
||||
Ok(t) => match t.duration_since(SystemTime::UNIX_EPOCH) {
|
||||
Ok(d) => DateTime::from_unix_timestamp_float(d.as_secs_f64()).ok(),
|
||||
Err(_) => None,
|
||||
},
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
use mlua::prelude::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct FsWriteOptions {
|
||||
pub(crate) overwrite: bool,
|
||||
}
|
18
crates/lune-std-luau/Cargo.toml
Normal file
|
@ -0,0 +1,18 @@
|
|||
[package]
|
||||
name = "lune-std-luau"
|
||||
version = "0.1.2"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/lune-org/lune"
|
||||
description = "Lune standard library - Luau"
|
||||
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
mlua = { version = "0.9.9", features = ["luau", "luau-jit"] }
|
||||
|
||||
lune-utils = { version = "0.1.3", path = "../lune-utils" }
|
90
crates/lune-std-luau/src/lib.rs
Normal file
|
@ -0,0 +1,90 @@
|
|||
#![allow(clippy::cargo_common_metadata)]
|
||||
|
||||
use mlua::prelude::*;
|
||||
|
||||
use lune_utils::{jit::JitStatus, TableBuilder};
|
||||
|
||||
mod options;
|
||||
|
||||
use self::options::{LuauCompileOptions, LuauLoadOptions};
|
||||
|
||||
const BYTECODE_ERROR_BYTE: u8 = 0;
|
||||
|
||||
/**
|
||||
Creates the `luau` standard library module.
|
||||
|
||||
# Errors
|
||||
|
||||
Errors when out of memory.
|
||||
*/
|
||||
pub fn module(lua: &Lua) -> LuaResult<LuaTable> {
|
||||
TableBuilder::new(lua)?
|
||||
.with_function("compile", compile_source)?
|
||||
.with_function("load", load_source)?
|
||||
.build_readonly()
|
||||
}
|
||||
|
||||
fn compile_source<'lua>(
|
||||
lua: &'lua Lua,
|
||||
(source, options): (LuaString<'lua>, LuauCompileOptions),
|
||||
) -> LuaResult<LuaString<'lua>> {
|
||||
let bytecode = options.into_compiler().compile(source);
|
||||
|
||||
match bytecode.first() {
|
||||
Some(&BYTECODE_ERROR_BYTE) => Err(LuaError::RuntimeError(
|
||||
String::from_utf8_lossy(&bytecode).into_owned(),
|
||||
)),
|
||||
Some(_) => lua.create_string(bytecode),
|
||||
None => panic!("Compiling resulted in empty bytecode"),
|
||||
}
|
||||
}
|
||||
|
||||
fn load_source<'lua>(
|
||||
lua: &'lua Lua,
|
||||
(source, options): (LuaString<'lua>, LuauLoadOptions),
|
||||
) -> LuaResult<LuaFunction<'lua>> {
|
||||
let mut chunk = lua.load(source.as_bytes()).set_name(options.debug_name);
|
||||
let env_changed = options.environment.is_some();
|
||||
|
||||
if let Some(custom_environment) = options.environment {
|
||||
let environment = lua.create_table()?;
|
||||
|
||||
// Inject all globals into the environment
|
||||
if options.inject_globals {
|
||||
for pair in lua.globals().pairs() {
|
||||
let (key, value): (LuaValue, LuaValue) = pair?;
|
||||
environment.set(key, value)?;
|
||||
}
|
||||
|
||||
if let Some(global_metatable) = lua.globals().get_metatable() {
|
||||
environment.set_metatable(Some(global_metatable));
|
||||
}
|
||||
} else if let Some(custom_metatable) = custom_environment.get_metatable() {
|
||||
// Since we don't need to set the global metatable,
|
||||
// we can just set a custom metatable if it exists
|
||||
environment.set_metatable(Some(custom_metatable));
|
||||
}
|
||||
|
||||
// Inject the custom environment
|
||||
for pair in custom_environment.pairs() {
|
||||
let (key, value): (LuaValue, LuaValue) = pair?;
|
||||
environment.set(key, value)?;
|
||||
}
|
||||
|
||||
chunk = chunk.set_environment(environment);
|
||||
}
|
||||
|
||||
// Enable JIT if codegen is enabled and the environment hasn't
|
||||
// changed, otherwise disable JIT since it'll fall back anyways
|
||||
lua.enable_jit(options.codegen_enabled && !env_changed);
|
||||
let function = chunk.into_function()?;
|
||||
lua.enable_jit(
|
||||
lua.app_data_ref::<JitStatus>()
|
||||
.ok_or(LuaError::runtime(
|
||||
"Failed to get current JitStatus ref from AppData",
|
||||
))?
|
||||
.enabled(),
|
||||
);
|
||||
|
||||
Ok(function)
|
||||
}
|
143
crates/lune-std-luau/src/options.rs
Normal file
|
@ -0,0 +1,143 @@
|
|||
#![allow(clippy::struct_field_names)]
|
||||
|
||||
use mlua::prelude::*;
|
||||
use mlua::Compiler as LuaCompiler;
|
||||
|
||||
const DEFAULT_DEBUG_NAME: &str = "luau.load(...)";
|
||||
|
||||
/**
|
||||
Options for compiling Lua source code.
|
||||
*/
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct LuauCompileOptions {
|
||||
pub(crate) optimization_level: u8,
|
||||
pub(crate) coverage_level: u8,
|
||||
pub(crate) debug_level: u8,
|
||||
}
|
||||
|
||||
impl LuauCompileOptions {
|
||||
pub fn into_compiler(self) -> LuaCompiler {
|
||||
LuaCompiler::default()
|
||||
.set_optimization_level(self.optimization_level)
|
||||
.set_coverage_level(self.coverage_level)
|
||||
.set_debug_level(self.debug_level)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for LuauCompileOptions {
|
||||
fn default() -> Self {
|
||||
// NOTE: This is the same as LuaCompiler::default() values, but they are
|
||||
// not accessible from outside of mlua so we need to recreate them here.
|
||||
Self {
|
||||
optimization_level: 1,
|
||||
coverage_level: 0,
|
||||
debug_level: 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) => {
|
||||
let mut options = Self::default();
|
||||
|
||||
let get_and_check = |name: &'static str| -> LuaResult<Option<u8>> {
|
||||
match t.get(name)? {
|
||||
Some(n @ (0..=2)) => Ok(Some(n)),
|
||||
Some(n) => Err(LuaError::runtime(format!(
|
||||
"'{name}' must be one of: 0, 1, or 2 - got {n}"
|
||||
))),
|
||||
None => Ok(None),
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(optimization_level) = get_and_check("optimizationLevel")? {
|
||||
options.optimization_level = optimization_level;
|
||||
}
|
||||
if let Some(coverage_level) = get_and_check("coverageLevel")? {
|
||||
options.coverage_level = coverage_level;
|
||||
}
|
||||
if let Some(debug_level) = get_and_check("debugLevel")? {
|
||||
options.debug_level = debug_level;
|
||||
}
|
||||
|
||||
options
|
||||
}
|
||||
_ => {
|
||||
return Err(LuaError::FromLuaConversionError {
|
||||
from: value.type_name(),
|
||||
to: "CompileOptions",
|
||||
message: Some(format!(
|
||||
"Invalid compile options - expected table, got {}",
|
||||
value.type_name()
|
||||
)),
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LuauLoadOptions<'lua> {
|
||||
pub(crate) debug_name: String,
|
||||
pub(crate) environment: Option<LuaTable<'lua>>,
|
||||
pub(crate) inject_globals: bool,
|
||||
pub(crate) codegen_enabled: bool,
|
||||
}
|
||||
|
||||
impl Default for LuauLoadOptions<'_> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
debug_name: DEFAULT_DEBUG_NAME.to_string(),
|
||||
environment: None,
|
||||
inject_globals: true,
|
||||
codegen_enabled: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) => {
|
||||
let mut options = Self::default();
|
||||
|
||||
if let Some(debug_name) = t.get("debugName")? {
|
||||
options.debug_name = debug_name;
|
||||
}
|
||||
|
||||
if let Some(environment) = t.get("environment")? {
|
||||
options.environment = Some(environment);
|
||||
}
|
||||
|
||||
if let Some(inject_globals) = t.get("injectGlobals")? {
|
||||
options.inject_globals = inject_globals;
|
||||
}
|
||||
|
||||
if let Some(codegen_enabled) = t.get("codegenEnabled")? {
|
||||
options.codegen_enabled = codegen_enabled;
|
||||
}
|
||||
|
||||
options
|
||||
}
|
||||
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",
|
||||
message: Some(format!(
|
||||
"Invalid load options - expected string or table, got {}",
|
||||
value.type_name()
|
||||
)),
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
39
crates/lune-std-net/Cargo.toml
Normal file
|
@ -0,0 +1,39 @@
|
|||
[package]
|
||||
name = "lune-std-net"
|
||||
version = "0.1.2"
|
||||
edition = "2021"
|
||||
license = "MPL-2.0"
|
||||
repository = "https://github.com/lune-org/lune"
|
||||
description = "Lune standard library - Net"
|
||||
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
mlua = { version = "0.9.9", features = ["luau"] }
|
||||
mlua-luau-scheduler = { version = "0.0.2", path = "../mlua-luau-scheduler" }
|
||||
|
||||
bstr = "1.9"
|
||||
futures-util = "0.3"
|
||||
hyper = { version = "1.1", features = ["full"] }
|
||||
hyper-util = { version = "0.1", features = ["full"] }
|
||||
http = "1.0"
|
||||
http-body-util = { version = "0.1" }
|
||||
hyper-tungstenite = { version = "0.13" }
|
||||
reqwest = { version = "0.11", default-features = false, features = [
|
||||
"rustls-tls",
|
||||
] }
|
||||
tokio-tungstenite = { version = "0.21", features = ["rustls-tls-webpki-roots"] }
|
||||
urlencoding = "2.1"
|
||||
|
||||
tokio = { version = "1", default-features = false, features = [
|
||||
"sync",
|
||||
"net",
|
||||
"macros",
|
||||
] }
|
||||
|
||||
lune-utils = { version = "0.1.3", path = "../lune-utils" }
|
||||
lune-std-serde = { version = "0.1.2", path = "../lune-std-serde" }
|