Merge branch 'main' of https://github.com/4x8Matrix/lune into feature/impl-luau-lib

This commit is contained in:
AsynchronousMatrix 2023-08-09 18:41:03 +01:00
commit bd8b5ce8b7
26 changed files with 663 additions and 703 deletions

View file

@ -31,4 +31,4 @@ jobs:
run: cargo clippy run: cargo clippy
- name: Test - name: Test
run: cargo test --package lune -- --test-threads 1 run: cargo test --lib

View file

@ -8,4 +8,4 @@ run-file FILE_NAME:
# Run tests for the Lune library # Run tests for the Lune library
test: test:
cargo test --lib -- --test-threads 1 cargo test --lib

View file

@ -4,7 +4,7 @@
"luau-lsp.types.roblox": false, "luau-lsp.types.roblox": false,
"luau-lsp.require.mode": "relativeToFile", "luau-lsp.require.mode": "relativeToFile",
"luau-lsp.require.directoryAliases": { "luau-lsp.require.directoryAliases": {
"@lune/": "~/.lune/.typedefs/0.7.4/" "@lune/": "~/.lune/.typedefs/0.7.5/"
}, },
// Luau - ignore type defs file in docs dir and dev scripts we use // Luau - ignore type defs file in docs dir and dev scripts we use
"luau-lsp.ignoreGlobs": [ "luau-lsp.ignoreGlobs": [

View file

@ -8,6 +8,20 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
### 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 `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 ## `0.7.5` - July 22nd, 2023
### Added ### Added

249
Cargo.lock generated
View file

@ -41,15 +41,6 @@ dependencies = [
"alloc-no-stdlib", "alloc-no-stdlib",
] ]
[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [
"winapi",
]
[[package]] [[package]]
name = "anstream" name = "anstream"
version = "0.3.2" version = "0.3.2"
@ -145,18 +136,7 @@ checksum = "cc6dde6e4ed435a4c1ee4e73592f5ba9da2151af10076cc04858746af9352d09"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.27", "syn 2.0.28",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi 0.1.19",
"libc",
"winapi",
] ]
[[package]] [[package]]
@ -301,9 +281,12 @@ checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.79" version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" checksum = "305fe645edc1442a0fa8b6726ba61d422798d37a52e12eaecf4b022ebbb88f01"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
@ -311,21 +294,6 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "2.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
dependencies = [
"ansi_term",
"atty",
"bitflags 1.3.2",
"strsim 0.8.0",
"textwrap",
"unicode-width",
"vec_map",
]
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.3.19" version = "4.3.19"
@ -346,7 +314,7 @@ dependencies = [
"anstream", "anstream",
"anstyle", "anstyle",
"clap_lex", "clap_lex",
"strsim 0.10.0", "strsim",
] ]
[[package]] [[package]]
@ -358,7 +326,7 @@ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.27", "syn 2.0.28",
] ]
[[package]] [[package]]
@ -457,6 +425,12 @@ version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
[[package]]
name = "deranged"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7684a49fb1af197853ef7b2ee694bc1f5b4179556f1e5710e1760c5db6f5e929"
[[package]] [[package]]
name = "dialoguer" name = "dialoguer"
version = "0.10.4" version = "0.10.4"
@ -545,19 +519,6 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "env_logger"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7"
dependencies = [
"atty",
"humantime",
"log",
"regex",
"termcolor",
]
[[package]] [[package]]
name = "env_logger" name = "env_logger"
version = "0.10.0" version = "0.10.0"
@ -588,9 +549,9 @@ dependencies = [
[[package]] [[package]]
name = "errno" name = "errno"
version = "0.3.1" version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f"
dependencies = [ dependencies = [
"errno-dragonfly", "errno-dragonfly",
"libc", "libc",
@ -661,7 +622,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.27", "syn 2.0.28",
] ]
[[package]] [[package]]
@ -778,15 +739,6 @@ version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
version = "0.3.2" version = "0.3.2"
@ -873,12 +825,12 @@ dependencies = [
[[package]] [[package]]
name = "hyper-tungstenite" name = "hyper-tungstenite"
version = "0.10.0" version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "226df6fd0aece319a325419d770aa9d947defa60463f142cd82b329121f906a3" checksum = "7cc7dcb1ab67cd336f468a12491765672e61a3b6b148634dbfe2fe8acd3fe7d9"
dependencies = [ dependencies = [
"hyper", "hyper",
"pin-project", "pin-project-lite",
"tokio", "tokio",
"tokio-tungstenite", "tokio-tungstenite",
"tungstenite", "tungstenite",
@ -946,7 +898,7 @@ version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
dependencies = [ dependencies = [
"hermit-abi 0.3.2", "hermit-abi",
"rustix", "rustix",
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
@ -998,9 +950,9 @@ dependencies = [
[[package]] [[package]]
name = "linux-raw-sys" name = "linux-raw-sys"
version = "0.4.3" version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09fc20d2ca12cb9f044c93e3bd6d32d523e6e2ec3db4f7b2939cd99026ecd3f0" checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
@ -1020,9 +972,9 @@ checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4"
[[package]] [[package]]
name = "luau0-src" name = "luau0-src"
version = "0.5.11+luau583" version = "0.6.0+luau588"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a7183d0a3eaa387403dd4ac7b449536af82c1545b7a48247af1652ca66926d5" checksum = "4c628f5525cc62a89a2d478b2ee619c77b35da55c8e3231752f3b8fe528a6c49"
dependencies = [ dependencies = [
"cc", "cc",
] ]
@ -1034,12 +986,12 @@ dependencies = [
"anyhow", "anyhow",
"async-compression", "async-compression",
"async-trait", "async-trait",
"clap 4.3.19", "clap",
"console", "console",
"dialoguer", "dialoguer",
"directories", "directories",
"dunce", "dunce",
"env_logger 0.10.0", "env_logger",
"futures-util", "futures-util",
"glam", "glam",
"hyper", "hyper",
@ -1092,9 +1044,9 @@ dependencies = [
[[package]] [[package]]
name = "lz4_flex" name = "lz4_flex"
version = "0.10.0" version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b8c72594ac26bfd34f2d99dfced2edfaddfe8a476e3ff2ca0eb293d925c4f83" checksum = "3ea9b256699eda7b0387ffbc776dd625e28bde3918446381781245b7a50349d8"
dependencies = [ dependencies = [
"twox-hash", "twox-hash",
] ]
@ -1133,9 +1085,9 @@ dependencies = [
[[package]] [[package]]
name = "mlua" name = "mlua"
version = "0.9.0-rc.1" version = "0.9.0-rc.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5369212118d0f115c9adbe7f7905e36fb3ef2994266039c51fc3e96374d705d" checksum = "01a6500a9fb74b519a85ac206cd57f9f91b270ce39d6cb12ab06a8ed29c3563d"
dependencies = [ dependencies = [
"bstr", "bstr",
"erased-serde", "erased-serde",
@ -1149,9 +1101,9 @@ dependencies = [
[[package]] [[package]]
name = "mlua-sys" name = "mlua-sys"
version = "0.2.1" version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3daecc55a656cae8e54fc599701ab597b67cdde68f780cac8c1c49b9cfff2f5" checksum = "aa5b61f6c943d77dd6ab5f670865670f65b978400127c8bf31c2df7d6e76289a"
dependencies = [ dependencies = [
"cc", "cc",
"cfg-if", "cfg-if",
@ -1174,7 +1126,7 @@ version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
dependencies = [ dependencies = [
"hermit-abi 0.3.2", "hermit-abi",
"libc", "libc",
] ]
@ -1254,29 +1206,29 @@ checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
[[package]] [[package]]
name = "pin-project" name = "pin-project"
version = "1.1.2" version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "030ad2bc4db10a8944cb0d837f158bdfec4d4a4873ab701a95046770d11f8842" checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422"
dependencies = [ dependencies = [
"pin-project-internal", "pin-project-internal",
] ]
[[package]] [[package]]
name = "pin-project-internal" name = "pin-project-internal"
version = "1.1.2" version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.27", "syn 2.0.28",
] ]
[[package]] [[package]]
name = "pin-project-lite" name = "pin-project-lite"
version = "0.2.10" version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" checksum = "2c516611246607d0c04186886dbb3a754368ef82c79e9827a802c6d836dd111c"
[[package]] [[package]]
name = "pin-utils" name = "pin-utils"
@ -1301,7 +1253,7 @@ dependencies = [
"line-wrap", "line-wrap",
"quick-xml", "quick-xml",
"serde", "serde",
"time 0.3.23", "time 0.3.25",
] ]
[[package]] [[package]]
@ -1327,21 +1279,21 @@ dependencies = [
[[package]] [[package]]
name = "profiling" name = "profiling"
version = "1.0.8" version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "332cd62e95873ea4f41f3dfd6bbbfc5b52aec892d7e8d534197c4720a0bbbab2" checksum = "46b2164ebdb1dfeec5e337be164292351e11daf63a05174c6776b2f47460f0c9"
dependencies = [ dependencies = [
"profiling-procmacros", "profiling-procmacros",
] ]
[[package]] [[package]]
name = "profiling-procmacros" name = "profiling-procmacros"
version = "1.0.8" version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a10adb8d151bb1280afb8bed41ae5db26be1b056964947133c7525b0bf39c0b0" checksum = "097bf8b99121dfb8c75eed54dfbdbdb1d53e372c53d2353e8a15aad2a479249d"
dependencies = [ dependencies = [
"quote", "quote",
"syn 1.0.109", "syn 2.0.28",
] ]
[[package]] [[package]]
@ -1355,9 +1307,9 @@ dependencies = [
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.31" version = "1.0.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fe8a65d69dd0808184ebb5f836ab526bb259db23c657efa38711b1072ee47f0" checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@ -1408,15 +1360,13 @@ dependencies = [
[[package]] [[package]]
name = "rbx_cookie" name = "rbx_cookie"
version = "0.1.2" version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d909d60469944842c9d204fa44afc90d9cb1e1f3f30a29bd1def490edd525a96" checksum = "6716fd418196130f1dd19947608b94bda942d096b514be9b441409d262bf5e59"
dependencies = [ dependencies = [
"byteorder 0.5.3", "byteorder 0.5.3",
"clap 2.34.0",
"cookie", "cookie",
"dirs", "dirs",
"env_logger 0.9.3",
"log", "log",
"plist", "plist",
"winapi", "winapi",
@ -1528,9 +1478,9 @@ dependencies = [
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.9.1" version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
@ -1540,9 +1490,9 @@ dependencies = [
[[package]] [[package]]
name = "regex-automata" name = "regex-automata"
version = "0.3.3" version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310" checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
@ -1666,9 +1616,9 @@ dependencies = [
[[package]] [[package]]
name = "rustix" name = "rustix"
version = "0.38.4" version = "0.38.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a962918ea88d644592894bc6dc55acc6c0956488adcebbfb6e273506b7fd6e5" checksum = "172891ebdceb05aa0005f533a6cbfca599ddd7d966f6f5d4d9b2e70478e70399"
dependencies = [ dependencies = [
"bitflags 2.3.3", "bitflags 2.3.3",
"errno", "errno",
@ -1679,13 +1629,13 @@ dependencies = [
[[package]] [[package]]
name = "rustls" name = "rustls"
version = "0.21.5" version = "0.21.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79ea77c539259495ce8ca47f53e66ae0330a8819f67e23ac96ca02f50e7b7d36" checksum = "1d1feddffcfcc0b33f5c6ce9a29e341e4cd59c3f78e7ee45f4a40c038b1d6cbb"
dependencies = [ dependencies = [
"log", "log",
"ring", "ring",
"rustls-webpki 0.101.1", "rustls-webpki 0.101.2",
"sct", "sct",
] ]
@ -1710,9 +1660,9 @@ dependencies = [
[[package]] [[package]]
name = "rustls-webpki" name = "rustls-webpki"
version = "0.101.1" version = "0.101.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15f36a6828982f422756984e47912a7a51dcbc2a197aa791158f8ca61cd8204e" checksum = "513722fd73ad80a71f72b61009ea1b584bcfa1483ca93949c8f290298837fa59"
dependencies = [ dependencies = [
"ring", "ring",
"untrusted", "untrusted",
@ -1763,9 +1713,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.174" version = "1.0.183"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b88756493a5bd5e5395d53baa70b194b05764ab85b59e43e4b8f4e1192fa9b1" checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
@ -1782,20 +1732,20 @@ dependencies = [
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.174" version = "1.0.183"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e5c3a298c7f978e53536f95a63bdc4c4a64550582f31a0359a9afda6aede62e" checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.27", "syn 2.0.28",
] ]
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.103" version = "1.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d03b412469450d4404fe8499a268edd7f8b79fecb074b0d812ad64ca21f4031b" checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c"
dependencies = [ dependencies = [
"indexmap 2.0.0", "indexmap 2.0.0",
"itoa", "itoa",
@ -1973,12 +1923,6 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0"
[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]] [[package]]
name = "strsim" name = "strsim"
version = "0.10.0" version = "0.10.0"
@ -2004,9 +1948,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.27" version = "2.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b60f673f44a8255b9c8c657daf66a596d435f2da81a555b06dc644d080ba45e0" checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2015,9 +1959,9 @@ dependencies = [
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.7.0" version = "3.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5486094ee78b2e5038a6382ed7645bc084dc2ec433426ca4c3cb61e2007b8998" checksum = "dc02fddf48964c42031a0b3fe0428320ecf3a73c401040fc0096f97794310651"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"fastrand", "fastrand",
@ -2035,15 +1979,6 @@ dependencies = [
"winapi-util", "winapi-util",
] ]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
"unicode-width",
]
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.44" version = "1.0.44"
@ -2061,7 +1996,7 @@ checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.27", "syn 2.0.28",
] ]
[[package]] [[package]]
@ -2081,14 +2016,15 @@ dependencies = [
[[package]] [[package]]
name = "time" name = "time"
version = "0.3.23" version = "0.3.25"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59e399c068f43a5d116fedaf73b203fa4f9c519f17e2b34f63221d3792f81446" checksum = "b0fdd63d58b18d663fbdf70e049f00a22c8e42be082203be7f26589213cd75ea"
dependencies = [ dependencies = [
"deranged",
"itoa", "itoa",
"serde", "serde",
"time-core", "time-core",
"time-macros 0.2.10", "time-macros 0.2.11",
] ]
[[package]] [[package]]
@ -2109,9 +2045,9 @@ dependencies = [
[[package]] [[package]]
name = "time-macros" name = "time-macros"
version = "0.2.10" version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96ba15a897f3c86766b757e5ac7221554c6750054d74d5b28844fce5fb36a6c4" checksum = "eb71511c991639bb078fd5bf97757e03914361c48100d52878b8e52b46fb92cd"
dependencies = [ dependencies = [
"time-core", "time-core",
] ]
@ -2172,7 +2108,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.27", "syn 2.0.28",
] ]
[[package]] [[package]]
@ -2187,9 +2123,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio-tungstenite" name = "tokio-tungstenite"
version = "0.19.0" version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec509ac96e9a0c43427c74f003127d953a265737636129424288d27cb5c4b12c" checksum = "2b2dbec703c26b00d74844519606ef15d09a7d6857860f84ad223dec002ddea2"
dependencies = [ dependencies = [
"futures-util", "futures-util",
"log", "log",
@ -2283,9 +2219,9 @@ checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
[[package]] [[package]]
name = "tungstenite" name = "tungstenite"
version = "0.19.0" version = "0.20.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15fba1a6d6bb030745759a9a2a588bfe8490fc8b4751a277db3a0be1c9ebbf67" checksum = "e862a1c4128df0112ab625f55cd5c934bcb4312ba80b39ae4b4835a3fd58e649"
dependencies = [ dependencies = [
"byteorder 1.4.3", "byteorder 1.4.3",
"bytes", "bytes",
@ -2299,7 +2235,6 @@ dependencies = [
"thiserror", "thiserror",
"url", "url",
"utf-8", "utf-8",
"webpki",
] ]
[[package]] [[package]]
@ -2386,12 +2321,6 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]] [[package]]
name = "version_check" name = "version_check"
version = "0.9.4" version = "0.9.4"
@ -2440,7 +2369,7 @@ dependencies = [
"once_cell", "once_cell",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.27", "syn 2.0.28",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@ -2474,7 +2403,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.27", "syn 2.0.28",
"wasm-bindgen-backend", "wasm-bindgen-backend",
"wasm-bindgen-shared", "wasm-bindgen-shared",
] ]
@ -2688,9 +2617,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "0.5.0" version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81fac9742fd1ad1bd9643b991319f72dd031016d44b77039a26977eb667141e7" checksum = "acaaa1190073b2b101e15083c38ee8ec891b5e05cbee516521e94ec008f61e64"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]

View file

@ -70,14 +70,18 @@ thiserror = "1.0"
async-trait = "0.1" async-trait = "0.1"
dialoguer = "0.10" dialoguer = "0.10"
dunce = "1.0" dunce = "1.0"
lz4_flex = "0.10" lz4_flex = "0.11"
pin-project = "1.0" pin-project = "1.0"
os_str_bytes = "6.4" os_str_bytes = "6.4"
urlencoding = "2.1.2" urlencoding = "2.1"
### RUNTIME ### RUNTIME
mlua = { version = "0.9.0-beta.3", features = ["luau", "serialize"] } mlua = { version = "0.9.0-beta.3", features = [
"luau",
"luau-jit",
"serialize",
] }
tokio = { version = "1.24", features = ["full"] } tokio = { version = "1.24", features = ["full"] }
### SERDE ### SERDE
@ -97,11 +101,11 @@ toml = { version = "0.7", features = ["preserve_order"] }
### NET ### NET
hyper = { version = "0.14", features = ["full"] } hyper = { version = "0.14", features = ["full"] }
hyper-tungstenite = { version = "0.10" } hyper-tungstenite = { version = "0.11" }
reqwest = { version = "0.11", default-features = false, features = [ reqwest = { version = "0.11", default-features = false, features = [
"rustls-tls", "rustls-tls",
] } ] }
tokio-tungstenite = { version = "0.19", features = ["rustls-tls-webpki-roots"] } tokio-tungstenite = { version = "0.20", features = ["rustls-tls-webpki-roots"] }
### CLI ### CLI
@ -121,7 +125,7 @@ regex = { optional = true, version = "1.7", default-features = false, features =
glam = { optional = true, version = "0.24" } glam = { optional = true, version = "0.24" }
rand = { optional = true, version = "0.8" } rand = { optional = true, version = "0.8" }
rbx_cookie = { optional = true, version = "0.1.2" } rbx_cookie = { optional = true, version = "0.1.3", default-features = false }
rbx_binary = { optional = true, git = "https://github.com/rojo-rbx/rbx-dom", rev = "e7a813d569c3f8a54be8a8873c33f8976c37b8b1" } rbx_binary = { optional = true, git = "https://github.com/rojo-rbx/rbx-dom", rev = "e7a813d569c3f8a54be8a8873c33f8976c37b8b1" }
rbx_dom_weak = { optional = true, git = "https://github.com/rojo-rbx/rbx-dom", rev = "e7a813d569c3f8a54be8a8873c33f8976c37b8b1" } rbx_dom_weak = { optional = true, git = "https://github.com/rojo-rbx/rbx-dom", rev = "e7a813d569c3f8a54be8a8873c33f8976c37b8b1" }

View file

@ -26,14 +26,14 @@ pub fn create(lua: &'static Lua) -> LuaResult<LuaTable> {
} }
async fn fs_read_file(lua: &'static Lua, path: String) -> LuaResult<LuaString> { async fn fs_read_file(lua: &'static Lua, path: String) -> LuaResult<LuaString> {
let bytes = fs::read(&path).await.map_err(LuaError::external)?; let bytes = fs::read(&path).await.into_lua_err()?;
lua.create_string(bytes) lua.create_string(bytes)
} }
async fn fs_read_dir(_: &'static Lua, path: String) -> LuaResult<Vec<String>> { async fn fs_read_dir(_: &'static Lua, path: String) -> LuaResult<Vec<String>> {
let mut dir_strings = Vec::new(); let mut dir_strings = Vec::new();
let mut dir = fs::read_dir(&path).await.map_err(LuaError::external)?; let mut dir = fs::read_dir(&path).await.into_lua_err()?;
while let Some(dir_entry) = dir.next_entry().await.map_err(LuaError::external)? { while let Some(dir_entry) = dir.next_entry().await.into_lua_err()? {
if let Some(dir_path_str) = dir_entry.path().to_str() { if let Some(dir_path_str) = dir_entry.path().to_str() {
dir_strings.push(dir_path_str.to_owned()); dir_strings.push(dir_path_str.to_owned());
} else { } else {
@ -63,21 +63,19 @@ async fn fs_write_file(
_: &'static Lua, _: &'static Lua,
(path, contents): (String, LuaString<'_>), (path, contents): (String, LuaString<'_>),
) -> LuaResult<()> { ) -> LuaResult<()> {
fs::write(&path, &contents.as_bytes()) fs::write(&path, &contents.as_bytes()).await.into_lua_err()
.await
.map_err(LuaError::external)
} }
async fn fs_write_dir(_: &'static Lua, path: String) -> LuaResult<()> { async fn fs_write_dir(_: &'static Lua, path: String) -> LuaResult<()> {
fs::create_dir_all(&path).await.map_err(LuaError::external) fs::create_dir_all(&path).await.into_lua_err()
} }
async fn fs_remove_file(_: &'static Lua, path: String) -> LuaResult<()> { async fn fs_remove_file(_: &'static Lua, path: String) -> LuaResult<()> {
fs::remove_file(&path).await.map_err(LuaError::external) fs::remove_file(&path).await.into_lua_err()
} }
async fn fs_remove_dir(_: &'static Lua, path: String) -> LuaResult<()> { async fn fs_remove_dir(_: &'static Lua, path: String) -> LuaResult<()> {
fs::remove_dir_all(&path).await.map_err(LuaError::external) fs::remove_dir_all(&path).await.into_lua_err()
} }
async fn fs_metadata(_: &'static Lua, path: String) -> LuaResult<FsMetadata> { async fn fs_metadata(_: &'static Lua, path: String) -> LuaResult<FsMetadata> {
@ -122,9 +120,7 @@ async fn fs_move(
path_to.display() path_to.display()
))); )));
} }
fs::rename(path_from, path_to) fs::rename(path_from, path_to).await.into_lua_err()?;
.await
.map_err(LuaError::external)?;
Ok(()) Ok(())
} }

View file

@ -73,7 +73,7 @@ async fn net_request<'a>(lua: &'static Lua, config: RequestConfig<'a>) -> LuaRes
.body(config.body.unwrap_or_default()) .body(config.body.unwrap_or_default())
.send() .send()
.await .await
.map_err(LuaError::external)?; .into_lua_err()?;
// Extract status, headers // Extract status, headers
let res_status = res.status().as_u16(); let res_status = res.status().as_u16();
let res_status_text = res.status().canonical_reason(); let res_status_text = res.status().canonical_reason();
@ -88,7 +88,7 @@ async fn net_request<'a>(lua: &'static Lua, config: RequestConfig<'a>) -> LuaRes
}) })
.collect::<HashMap<String, String>>(); .collect::<HashMap<String, String>>();
// Read response bytes // Read response bytes
let mut res_bytes = res.bytes().await.map_err(LuaError::external)?.to_vec(); let mut res_bytes = res.bytes().await.into_lua_err()?.to_vec();
// Check for extra options, decompression // Check for extra options, decompression
if config.options.decompress { if config.options.decompress {
// NOTE: Header names are guaranteed to be lowercase because of the above // NOTE: Header names are guaranteed to be lowercase because of the above
@ -120,9 +120,7 @@ async fn net_request<'a>(lua: &'static Lua, config: RequestConfig<'a>) -> LuaRes
} }
async fn net_socket<'a>(lua: &'static Lua, url: String) -> LuaResult<LuaTable> { async fn net_socket<'a>(lua: &'static Lua, url: String) -> LuaResult<LuaTable> {
let (ws, _) = tokio_tungstenite::connect_async(url) let (ws, _) = tokio_tungstenite::connect_async(url).await.into_lua_err()?;
.await
.map_err(LuaError::external)?;
NetWebSocket::new(ws).into_lua_table(lua) NetWebSocket::new(ws).into_lua_table(lua)
} }

View file

@ -41,7 +41,7 @@ async fn deserialize_place<'lua>(
let data_model = doc.into_data_model_instance()?; let data_model = doc.into_data_model_instance()?;
Ok::<_, DocumentError>(data_model) Ok::<_, DocumentError>(data_model)
}); });
fut.await.map_err(LuaError::external)??.into_lua(lua) fut.await.into_lua_err()??.into_lua(lua)
} }
async fn deserialize_model<'lua>( async fn deserialize_model<'lua>(
@ -54,7 +54,7 @@ async fn deserialize_model<'lua>(
let instance_array = doc.into_instance_array()?; let instance_array = doc.into_instance_array()?;
Ok::<_, DocumentError>(instance_array) Ok::<_, DocumentError>(instance_array)
}); });
fut.await.map_err(LuaError::external)??.into_lua(lua) fut.await.into_lua_err()??.into_lua(lua)
} }
async fn serialize_place<'lua>( async fn serialize_place<'lua>(
@ -70,7 +70,7 @@ async fn serialize_place<'lua>(
})?; })?;
Ok::<_, DocumentError>(bytes) Ok::<_, DocumentError>(bytes)
}); });
let bytes = fut.await.map_err(LuaError::external)??; let bytes = fut.await.into_lua_err()??;
lua.create_string(bytes) lua.create_string(bytes)
} }
@ -87,7 +87,7 @@ async fn serialize_model<'lua>(
})?; })?;
Ok::<_, DocumentError>(bytes) Ok::<_, DocumentError>(bytes)
}); });
let bytes = fut.await.map_err(LuaError::external)??; let bytes = fut.await.into_lua_err()??;
lua.create_string(bytes) lua.create_string(bytes)
} }

View file

@ -43,7 +43,7 @@ pub fn create(lua: &'static Lua) -> LuaResult<LuaTable> {
.with_async_function("prompt", |_, options: PromptOptions| async move { .with_async_function("prompt", |_, options: PromptOptions| async move {
task::spawn_blocking(move || prompt(options)) task::spawn_blocking(move || prompt(options))
.await .await
.map_err(LuaError::external)? .into_lua_err()?
})? })?
.build_readonly() .build_readonly()
} }

View file

@ -6,8 +6,7 @@ use std::{
sync::Arc, sync::Arc,
}; };
use dunce::canonicalize; use mlua::prelude::*;
use mlua::{prelude::*, Compiler as LuaCompiler};
use tokio::fs; use tokio::fs;
use tokio::sync::Mutex as AsyncMutex; use tokio::sync::Mutex as AsyncMutex;
@ -29,6 +28,19 @@ return yield()
type RequireWakersVec<'lua> = Vec<Arc<AsyncMutex<RequireWakerState<'lua>>>>; type RequireWakersVec<'lua> = Vec<Arc<AsyncMutex<RequireWakerState<'lua>>>>;
fn append_extension_and_canonicalize(
path: impl Into<PathBuf>,
ext: &'static str,
) -> Result<PathBuf, std::io::Error> {
let mut new = path.into();
match new.extension() {
// FUTURE: There's probably a better way to do this than converting to a lossy string
Some(e) => new.set_extension(format!("{}.{ext}", e.to_string_lossy())),
None => new.set_extension(ext),
};
dunce::canonicalize(new)
}
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
struct RequireContext<'lua> { struct RequireContext<'lua> {
// NOTE: We need to use arc here so that mlua clones // NOTE: We need to use arc here so that mlua clones
@ -128,25 +140,28 @@ impl<'lua> RequireContext<'lua> {
.join(&require_path); .join(&require_path);
// Try to normalize and resolve relative path segments such as './' and '../' // Try to normalize and resolve relative path segments such as './' and '../'
let file_path = match ( let file_path = match (
canonicalize(path_relative_to_pwd.with_extension("luau")), append_extension_and_canonicalize(&path_relative_to_pwd, "luau"),
canonicalize(path_relative_to_pwd.with_extension("lua")), append_extension_and_canonicalize(&path_relative_to_pwd, "lua"),
) { ) {
(Ok(luau), _) => luau, (Ok(luau), _) => luau,
(_, Ok(lua)) => lua, (_, Ok(lua)) => lua,
// If we did not find a luau/lua file at the wanted path, // If we did not find a luau/lua file at the wanted path,
// we should also look for "init" files in directories // we should also look for "init" files in directories
_ => match ( _ => {
canonicalize(path_relative_to_pwd.join("init").with_extension("luau")), let init_dir_path = path_relative_to_pwd.join("init");
canonicalize(path_relative_to_pwd.join("init").with_extension("lua")), match (
) { append_extension_and_canonicalize(&init_dir_path, "luau"),
(Ok(luau), _) => luau, append_extension_and_canonicalize(&init_dir_path, "lua"),
(_, Ok(lua)) => lua, ) {
_ => { (Ok(luau), _) => luau,
return Err(LuaError::RuntimeError(format!( (_, Ok(lua)) => lua,
"File does not exist at path '{require_path}'" _ => {
))) return Err(LuaError::RuntimeError(format!(
"File does not exist at path '{require_path}'"
)));
}
} }
}, }
}; };
let absolute = file_path.to_string_lossy().to_string(); let absolute = file_path.to_string_lossy().to_string();
let relative = absolute.trim_start_matches(&self.pwd).to_string(); let relative = absolute.trim_start_matches(&self.pwd).to_string();
@ -187,16 +202,15 @@ async fn load_file<'lua>(
} }
// Try to read the wanted file, note that we use bytes instead of reading // Try to read the wanted file, note that we use bytes instead of reading
// to a string since lua scripts are not necessarily valid utf-8 strings // to a string since lua scripts are not necessarily valid utf-8 strings
let contents = fs::read(&absolute_path).await.map_err(LuaError::external)?; let contents = fs::read(&absolute_path).await.into_lua_err()?;
// Use a name without extensions for loading the chunk, some // Use a name without extensions for loading the chunk, some
// other code assumes the require path is without extensions // other code assumes the require path is without extensions
let path_relative_no_extension = relative_path let path_relative_no_extension = relative_path
.trim_end_matches(".lua") .trim_end_matches(".lua")
.trim_end_matches(".luau"); .trim_end_matches(".luau");
// Load the file into a thread // Load the file into a thread
let compiled_func = LuaCompiler::default().compile(&contents);
let loaded_func = lua let loaded_func = lua
.load(compiled_func) .load(contents)
.set_name(path_relative_no_extension) .set_name(path_relative_no_extension)
.into_function()?; .into_function()?;
let loaded_thread = lua.create_thread(loaded_func)?; let loaded_thread = lua.create_thread(loaded_func)?;

View file

@ -1,4 +1,4 @@
use mlua::prelude::*; use mlua::{prelude::*, Compiler as LuaCompiler};
/* /*
- Level 0 is the call to info - Level 0 is the call to info
@ -76,14 +76,28 @@ end
*/ */
pub fn create() -> LuaResult<&'static Lua> { pub fn create() -> LuaResult<&'static Lua> {
let lua = Lua::new().into_static(); let lua = Lua::new().into_static();
// Enable jit and set global compiler options
lua.enable_jit(true);
lua.set_compiler(
LuaCompiler::default()
.set_coverage_level(0)
.set_debug_level(1)
.set_optimization_level(1),
);
// Extract some global tables that we will extract
// built-in functions from and store in the registry
let globals = &lua.globals(); let globals = &lua.globals();
let debug: LuaTable = globals.raw_get("debug")?; let debug: LuaTable = globals.raw_get("debug")?;
let table: LuaTable = globals.raw_get("table")?; let table: LuaTable = globals.raw_get("table")?;
let string: LuaTable = globals.raw_get("string")?; let string: LuaTable = globals.raw_get("string")?;
let coroutine: LuaTable = globals.get("coroutine")?; let coroutine: LuaTable = globals.get("coroutine")?;
// Create a _G table that is separate from our built-in globals // Create a _G table that is separate from our built-in globals
let global_table = lua.create_table()?; let global_table = lua.create_table()?;
globals.set("_G", global_table)?; globals.set("_G", global_table)?;
// Store original lua global functions in the registry so we can use // Store original lua global functions in the registry so we can use
// them later without passing them around and dealing with lifetimes // them later without passing them around and dealing with lifetimes
lua.set_named_registry_value("print", globals.get::<_, LuaFunction>("print")?)?; lua.set_named_registry_value("print", globals.get::<_, LuaFunction>("print")?)?;
@ -109,6 +123,7 @@ pub fn create() -> LuaResult<&'static Lua> {
"tab.setmeta", "tab.setmeta",
globals.get::<_, LuaFunction>("setmetatable")?, globals.get::<_, LuaFunction>("setmetatable")?,
)?; )?;
// Create a trace function that can be called to obtain a full stack trace from // Create a trace function that can be called to obtain a full stack trace from
// lua, this is not possible to do from rust when using our manual scheduler // lua, this is not possible to do from rust when using our manual scheduler
let dbg_trace_env = lua.create_table_with_capacity(0, 1)?; let dbg_trace_env = lua.create_table_with_capacity(0, 1)?;
@ -122,6 +137,7 @@ pub fn create() -> LuaResult<&'static Lua> {
.set_environment(dbg_trace_env) .set_environment(dbg_trace_env)
.into_function()?; .into_function()?;
lua.set_named_registry_value("dbg.trace", dbg_trace_fn)?; lua.set_named_registry_value("dbg.trace", dbg_trace_fn)?;
// Modify the _VERSION global to also contain the current version of Lune // Modify the _VERSION global to also contain the current version of Lune
let luau_version_full = globals let luau_version_full = globals
.get::<_, LuaString>("_VERSION") .get::<_, LuaString>("_VERSION")
@ -142,6 +158,7 @@ pub fn create() -> LuaResult<&'static Lua> {
luau = luau_version, luau = luau_version,
))?, ))?,
)?; )?;
// All done // All done
Ok(lua) Ok(lua)
} }

View file

@ -23,8 +23,8 @@ impl NetClientBuilder {
{ {
let mut map = HeaderMap::new(); let mut map = HeaderMap::new();
for (key, val) in headers { for (key, val) in headers {
let hkey = HeaderName::from_str(key.as_ref()).map_err(LuaError::external)?; let hkey = HeaderName::from_str(key.as_ref()).into_lua_err()?;
let hval = HeaderValue::from_bytes(val.as_ref()).map_err(LuaError::external)?; let hval = HeaderValue::from_bytes(val.as_ref()).into_lua_err()?;
map.insert(hkey, hval); map.insert(hkey, hval);
} }
self.builder = self.builder.default_headers(map); self.builder = self.builder.default_headers(map);
@ -32,7 +32,7 @@ impl NetClientBuilder {
} }
pub fn build(self) -> LuaResult<NetClient> { pub fn build(self) -> LuaResult<NetClient> {
let client = self.builder.build().map_err(LuaError::external)?; let client = self.builder.build().into_lua_err()?;
Ok(NetClient(client)) Ok(NetClient(client))
} }
} }

View file

@ -24,7 +24,7 @@ impl NetServeResponse {
.status(200) .status(200)
.header("Content-Type", "text/plain") .header("Content-Type", "text/plain")
.body(Body::from(self.body.unwrap())) .body(Body::from(self.body.unwrap()))
.map_err(LuaError::external)?, .into_lua_err()?,
NetServeResponseKind::Table => { NetServeResponseKind::Table => {
let mut response = Response::builder(); let mut response = Response::builder();
for (key, value) in self.headers { for (key, value) in self.headers {
@ -33,7 +33,7 @@ impl NetServeResponse {
response response
.status(self.status) .status(self.status)
.body(Body::from(self.body.unwrap_or_default())) .body(Body::from(self.body.unwrap_or_default()))
.map_err(LuaError::external)? .into_lua_err()?
} }
}) })
} }

View file

@ -57,7 +57,7 @@ impl Service<Request<Body>> for NetServiceInner {
task::spawn_local(async move { task::spawn_local(async move {
// Create our new full websocket object, then // Create our new full websocket object, then
// schedule our handler to get called asap // schedule our handler to get called asap
let ws = ws.await.map_err(LuaError::external)?; let ws = ws.await.into_lua_err()?;
let sock = NetWebSocket::new(ws).into_lua_table(lua)?; let sock = NetWebSocket::new(ws).into_lua_table(lua)?;
let sched = lua let sched = lua
.app_data_ref::<&TaskScheduler>() .app_data_ref::<&TaskScheduler>()
@ -77,7 +77,7 @@ impl Service<Request<Body>> for NetServiceInner {
let (parts, body) = req.into_parts(); let (parts, body) = req.into_parts();
Box::pin(async move { Box::pin(async move {
// Convert request body into bytes, extract handler // Convert request body into bytes, extract handler
let bytes = to_bytes(body).await.map_err(LuaError::external)?; let bytes = to_bytes(body).await.into_lua_err()?;
let handler: LuaFunction = lua.registry_value(&key)?; let handler: LuaFunction = lua.registry_value(&key)?;
// Create a readonly table for the request query params // Create a readonly table for the request query params
let query_params = TableBuilder::new(lua)? let query_params = TableBuilder::new(lua)?

View file

@ -167,10 +167,10 @@ where
reason: "".into(), reason: "".into(),
}))) })))
.await .await
.map_err(LuaError::external)?; .into_lua_err()?;
let res = ws.close(); let res = ws.close();
res.await.map_err(LuaError::external) res.await.into_lua_err()
} }
async fn send<'lua, T>( async fn send<'lua, T>(
@ -187,11 +187,11 @@ where
let msg = if matches!(as_binary, Some(true)) { let msg = if matches!(as_binary, Some(true)) {
WsMessage::Binary(string.as_bytes().to_vec()) WsMessage::Binary(string.as_bytes().to_vec())
} else { } else {
let s = string.to_str().map_err(LuaError::external)?; let s = string.to_str().into_lua_err()?;
WsMessage::Text(s.to_string()) WsMessage::Text(s.to_string())
}; };
let mut ws = socket.write_stream.lock().await; let mut ws = socket.write_stream.lock().await;
ws.send(msg).await.map_err(LuaError::external) ws.send(msg).await.into_lua_err()
} }
async fn next<'lua, T>( async fn next<'lua, T>(
@ -202,7 +202,7 @@ where
T: AsyncRead + AsyncWrite + Unpin, T: AsyncRead + AsyncWrite + Unpin,
{ {
let mut ws = socket.read_stream.lock().await; let mut ws = socket.read_stream.lock().await;
let item = ws.next().await.transpose().map_err(LuaError::external); let item = ws.next().await.transpose().into_lua_err();
let msg = match item { let msg = match item {
Ok(Some(WsMessage::Close(msg))) => { Ok(Some(WsMessage::Close(msg))) => {
if let Some(msg) = &msg { if let Some(msg) = &msg {

View file

@ -24,9 +24,7 @@ pub async fn pipe_and_inherit_child_process_stdio(
let mut stdout = io::stdout(); let mut stdout = io::stdout();
let mut tee = AsyncTeeWriter::new(&mut stdout); let mut tee = AsyncTeeWriter::new(&mut stdout);
io::copy(&mut child_stdout, &mut tee) io::copy(&mut child_stdout, &mut tee).await.into_lua_err()?;
.await
.map_err(LuaError::external)?;
Ok::<_, LuaError>(tee.into_vec()) Ok::<_, LuaError>(tee.into_vec())
}); });
@ -35,9 +33,7 @@ pub async fn pipe_and_inherit_child_process_stdio(
let mut stderr = io::stderr(); let mut stderr = io::stderr();
let mut tee = AsyncTeeWriter::new(&mut stderr); let mut tee = AsyncTeeWriter::new(&mut stderr);
io::copy(&mut child_stderr, &mut tee) io::copy(&mut child_stderr, &mut tee).await.into_lua_err()?;
.await
.map_err(LuaError::external)?;
Ok::<_, LuaError>(tee.into_vec()) Ok::<_, LuaError>(tee.into_vec())
}); });

View file

@ -102,7 +102,7 @@ pub async fn compress<'lua>(
let source = source.as_ref().to_vec(); let source = source.as_ref().to_vec();
return task::spawn_blocking(move || compress_prepend_size(&source)) return task::spawn_blocking(move || compress_prepend_size(&source))
.await .await
.map_err(LuaError::external); .into_lua_err();
} }
let mut bytes = Vec::new(); let mut bytes = Vec::new();
@ -135,8 +135,8 @@ pub async fn decompress<'lua>(
let source = source.as_ref().to_vec(); let source = source.as_ref().to_vec();
return task::spawn_blocking(move || decompress_size_prepended(&source)) return task::spawn_blocking(move || decompress_size_prepended(&source))
.await .await
.map_err(LuaError::external)? .into_lua_err()?
.map_err(LuaError::external); .into_lua_err();
} }
let mut bytes = Vec::new(); let mut bytes = Vec::new();

View file

@ -4,6 +4,15 @@ use serde_json::Value as JsonValue;
use serde_yaml::Value as YamlValue; use serde_yaml::Value as YamlValue;
use toml::Value as TomlValue; use toml::Value as TomlValue;
const LUA_SERIALIZE_OPTIONS: LuaSerializeOptions = LuaSerializeOptions::new()
.set_array_metatable(false)
.serialize_none_to_null(false)
.serialize_unit_to_null(false);
const LUA_DESERIALIZE_OPTIONS: LuaDeserializeOptions = LuaDeserializeOptions::new()
.deny_recursive_tables(false)
.deny_unsupported_types(true);
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum EncodeDecodeFormat { pub enum EncodeDecodeFormat {
Json, Json,
@ -50,22 +59,25 @@ impl EncodeDecodeConfig {
) -> LuaResult<LuaString<'lua>> { ) -> LuaResult<LuaString<'lua>> {
let bytes = match self.format { let bytes = match self.format {
EncodeDecodeFormat::Json => { EncodeDecodeFormat::Json => {
let serialized: JsonValue = lua.from_value_with(value, LUA_DESERIALIZE_OPTIONS)?;
if self.pretty { if self.pretty {
serde_json::to_vec_pretty(&value).map_err(LuaError::external)? serde_json::to_vec_pretty(&serialized).into_lua_err()?
} else { } else {
serde_json::to_vec(&value).map_err(LuaError::external)? serde_json::to_vec(&serialized).into_lua_err()?
} }
} }
EncodeDecodeFormat::Yaml => { EncodeDecodeFormat::Yaml => {
let serialized: YamlValue = lua.from_value_with(value, LUA_DESERIALIZE_OPTIONS)?;
let mut writer = Vec::with_capacity(128); let mut writer = Vec::with_capacity(128);
serde_yaml::to_writer(&mut writer, &value).map_err(LuaError::external)?; serde_yaml::to_writer(&mut writer, &serialized).into_lua_err()?;
writer writer
} }
EncodeDecodeFormat::Toml => { EncodeDecodeFormat::Toml => {
let serialized: TomlValue = lua.from_value_with(value, LUA_DESERIALIZE_OPTIONS)?;
let s = if self.pretty { let s = if self.pretty {
toml::to_string_pretty(&value).map_err(LuaError::external)? toml::to_string_pretty(&serialized).into_lua_err()?
} else { } else {
toml::to_string(&value).map_err(LuaError::external)? toml::to_string(&serialized).into_lua_err()?
}; };
s.as_bytes().to_vec() s.as_bytes().to_vec()
} }
@ -81,17 +93,17 @@ impl EncodeDecodeConfig {
let bytes = string.as_bytes(); let bytes = string.as_bytes();
match self.format { match self.format {
EncodeDecodeFormat::Json => { EncodeDecodeFormat::Json => {
let value: JsonValue = serde_json::from_slice(bytes).map_err(LuaError::external)?; let value: JsonValue = serde_json::from_slice(bytes).into_lua_err()?;
lua.to_value(&value) lua.to_value_with(&value, LUA_SERIALIZE_OPTIONS)
} }
EncodeDecodeFormat::Yaml => { EncodeDecodeFormat::Yaml => {
let value: YamlValue = serde_yaml::from_slice(bytes).map_err(LuaError::external)?; let value: YamlValue = serde_yaml::from_slice(bytes).into_lua_err()?;
lua.to_value(&value) lua.to_value_with(&value, LUA_SERIALIZE_OPTIONS)
} }
EncodeDecodeFormat::Toml => { EncodeDecodeFormat::Toml => {
if let Ok(s) = string.to_str() { if let Ok(s) = string.to_str() {
let value: TomlValue = toml::from_str(s).map_err(LuaError::external)?; let value: TomlValue = toml::from_str(s).into_lua_err()?;
lua.to_value(&value) lua.to_value_with(&value, LUA_SERIALIZE_OPTIONS)
} else { } else {
Err(LuaError::RuntimeError( Err(LuaError::RuntimeError(
"TOML must be valid utf-8".to_string(), "TOML must be valid utf-8".to_string(),

View file

@ -197,12 +197,12 @@ pub fn pretty_format_multi_value(multi: &LuaMultiValue) -> LuaResult<String> {
for value in multi { for value in multi {
counter += 1; counter += 1;
if let LuaValue::String(s) = value { if let LuaValue::String(s) = value {
write!(buffer, "{}", s.to_string_lossy()).map_err(LuaError::external)?; write!(buffer, "{}", s.to_string_lossy()).into_lua_err()?;
} else { } else {
pretty_format_value(&mut buffer, value, 0).map_err(LuaError::external)?; pretty_format_value(&mut buffer, value, 0).into_lua_err()?;
} }
if counter < multi.len() { if counter < multi.len() {
write!(&mut buffer, " ").map_err(LuaError::external)?; write!(&mut buffer, " ").into_lua_err()?;
} }
} }
Ok(buffer) Ok(buffer)

View file

@ -2,7 +2,6 @@ use std::process::ExitCode;
use lua::task::{TaskScheduler, TaskSchedulerResumeExt, TaskSchedulerScheduleExt}; use lua::task::{TaskScheduler, TaskSchedulerResumeExt, TaskSchedulerScheduleExt};
use mlua::prelude::*; use mlua::prelude::*;
use mlua::Compiler as LuaCompiler;
use tokio::task::LocalSet; use tokio::task::LocalSet;
pub mod builtins; pub mod builtins;
@ -67,7 +66,6 @@ impl Lune {
) -> Result<ExitCode, LuaError> { ) -> Result<ExitCode, LuaError> {
// Create our special lune-flavored Lua object with extra registry values // Create our special lune-flavored Lua object with extra registry values
let lua = lua::create_lune_lua()?; let lua = lua::create_lune_lua()?;
let script = LuaCompiler::default().compile(script_contents);
// Create our task scheduler and all globals // Create our task scheduler and all globals
// NOTE: Some globals require the task scheduler to exist on startup // NOTE: Some globals require the task scheduler to exist on startup
let sched = TaskScheduler::new(lua)?.into_static(); let sched = TaskScheduler::new(lua)?.into_static();
@ -75,7 +73,7 @@ impl Lune {
importer::create(lua, self.args.clone())?; importer::create(lua, self.args.clone())?;
// Create the main thread and schedule it // Create the main thread and schedule it
let main_chunk = lua let main_chunk = lua
.load(script) .load(script_contents.as_ref())
.set_name(script_name.as_ref()) .set_name(script_name.as_ref())
.into_function()?; .into_function()?;
let main_thread = lua.create_thread(main_chunk)?; let main_thread = lua.create_thread(main_chunk)?;

356
src/roblox/instance/base.rs Normal file
View file

@ -0,0 +1,356 @@
use mlua::prelude::*;
use rbx_dom_weak::{
types::{Variant as DomValue, VariantType as DomType},
Instance as DomInstance,
};
use crate::roblox::{
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, Instance};
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("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| {
ensure_not_destroyed(this)?;
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.into_iter() {
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)?;
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>> {
ensure_not_destroyed(this)?;
match prop_name.as_str() {
"ClassName" => return this.get_class_name().into_lua(lua),
"Name" => {
return this.get_name().into_lua(lua);
}
"Parent" => {
return this.get_parent().into_lua(lua);
}
_ => {}
}
if let Some(info) = find_property_info(&this.class_name, &prop_name) {
if let Some(prop) = this.get_property(&prop_name) {
if let DomValue::Enum(enum_value) = prop {
let enum_name = info.enum_name.ok_or_else(|| {
LuaError::RuntimeError(format!(
"Failed to get property '{}' - encountered unknown enum",
prop_name
))
})?;
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 '{}' - Enum.{} does not contain numeric value {}",
prop_name, enum_name, 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 '{}' - missing default value",
prop_name
)))
}
} else {
Err(LuaError::RuntimeError(format!(
"Failed to get property '{}' - malformed property info",
prop_name
)))
}
} else if let Some(inst) = this.find_child(|inst| inst.name == prop_name) {
Ok(LuaValue::UserData(lua.create_userdata(inst)?))
} else {
Err(LuaError::RuntimeError(format!(
"{} is not a valid member of {}",
prop_name, 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.clone()));
return Ok(());
}
_ => {}
}
let info = match find_property_info(&this.class_name, &prop_name) {
Some(b) => b,
None => {
return Err(LuaError::RuntimeError(format!(
"{} is not a valid member of {}",
prop_name, this
)))
}
};
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::Enum((*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 '{}' - malformed property info",
prop_name
)))
}
}

View file

@ -2,36 +2,27 @@ use std::{
collections::{BTreeMap, VecDeque}, collections::{BTreeMap, VecDeque},
fmt, fmt,
hash::{Hash, Hasher}, hash::{Hash, Hasher},
sync::RwLock, sync::Mutex,
}; };
use mlua::prelude::*; use mlua::prelude::*;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use rbx_dom_weak::{ use rbx_dom_weak::{
types::{ types::{Attributes as DomAttributes, Ref as DomRef, Variant as DomValue},
Attributes as DomAttributes, Ref as DomRef, Variant as DomValue, VariantType as DomType,
},
Instance as DomInstance, InstanceBuilder as DomInstanceBuilder, WeakDom, Instance as DomInstance, InstanceBuilder as DomInstanceBuilder, WeakDom,
}; };
use crate::roblox::{ use crate::roblox::shared::instance::{class_exists, class_is_a};
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_exists, class_is_a, find_property_info},
};
pub(crate) mod base;
pub(crate) mod data_model; pub(crate) mod data_model;
pub(crate) mod workspace; pub(crate) mod workspace;
const PROPERTY_NAME_ATTRIBUTES: &str = "Attributes"; const PROPERTY_NAME_ATTRIBUTES: &str = "Attributes";
const PROPERTY_NAME_TAGS: &str = "Tags"; const PROPERTY_NAME_TAGS: &str = "Tags";
static INTERNAL_DOM: Lazy<RwLock<WeakDom>> = static INTERNAL_DOM: Lazy<Mutex<WeakDom>> =
Lazy::new(|| RwLock::new(WeakDom::new(DomInstanceBuilder::new("ROOT")))); Lazy::new(|| Mutex::new(WeakDom::new(DomInstanceBuilder::new("ROOT"))));
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Instance { pub struct Instance {
@ -45,11 +36,12 @@ impl Instance {
Panics if the instance does not exist in the internal dom, Panics if the instance does not exist in the internal dom,
or if the given dom object ref points to the dom root. or if the given dom object ref points to the dom root.
**WARNING:** Creating a new instance requires locking the internal dom,
any existing lock must first be released to prevent any deadlocking.
*/ */
pub(crate) fn new(dom_ref: DomRef) -> Self { pub(crate) fn new(dom_ref: DomRef) -> Self {
let dom = INTERNAL_DOM let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
.try_read()
.expect("Failed to get read access to document");
let instance = dom let instance = dom
.get_by_ref(dom_ref) .get_by_ref(dom_ref)
@ -69,11 +61,12 @@ impl Instance {
Creates a new `Instance` from a dom object ref, if the instance exists. Creates a new `Instance` from a dom object ref, if the instance exists.
Panics if the given dom object ref points to the dom root. Panics if the given dom object ref points to the dom root.
**WARNING:** Creating a new instance requires locking the internal dom,
any existing lock must first be released to prevent any deadlocking.
*/ */
pub(crate) fn new_opt(dom_ref: DomRef) -> Option<Self> { pub(crate) fn new_opt(dom_ref: DomRef) -> Option<Self> {
let dom = INTERNAL_DOM let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
.try_read()
.expect("Failed to get read access to document");
if let Some(instance) = dom.get_by_ref(dom_ref) { if let Some(instance) = dom.get_by_ref(dom_ref) {
if instance.referent() == dom.root_ref() { if instance.referent() == dom.root_ref() {
@ -93,11 +86,12 @@ impl Instance {
Creates a new orphaned `Instance` with a given class name. Creates a new orphaned `Instance` with a given class name.
An orphaned instance is an instance at the root of a weak dom. An orphaned instance is an instance at the root of a weak dom.
**WARNING:** Creating a new instance requires locking the internal dom,
any existing lock must first be released to prevent any deadlocking.
*/ */
pub(crate) fn new_orphaned(class_name: impl AsRef<str>) -> Self { pub(crate) fn new_orphaned(class_name: impl AsRef<str>) -> Self {
let mut dom = INTERNAL_DOM let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
.try_write()
.expect("Failed to get write access to document");
let class_name = class_name.as_ref(); let class_name = class_name.as_ref();
@ -121,15 +115,12 @@ impl Instance {
Panics if the given dom ref is the root dom ref of the external weak dom. Panics if the given dom ref is the root dom ref of the external weak dom.
*/ */
pub fn from_external_dom(external_dom: &mut WeakDom, external_dom_ref: DomRef) -> Self { pub fn from_external_dom(external_dom: &mut WeakDom, external_dom_ref: DomRef) -> Self {
{ let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
let mut dom = INTERNAL_DOM let dom_root = dom.root_ref();
.try_write()
.expect("Failed to get write access to document");
let dom_root = dom.root_ref();
external_dom.transfer(external_dom_ref, &mut dom, dom_root); 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) Self::new(external_dom_ref)
} }
@ -140,9 +131,7 @@ impl Instance {
root of the weak dom, and return its referent. root of the weak dom, and return its referent.
*/ */
pub fn clone_into_external_dom(self, external_dom: &mut WeakDom) -> DomRef { pub fn clone_into_external_dom(self, external_dom: &mut WeakDom) -> DomRef {
let dom = INTERNAL_DOM let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
.try_read()
.expect("Failed to get read access to document");
let cloned = dom.clone_into_external(self.dom_ref, external_dom); let cloned = dom.clone_into_external(self.dom_ref, external_dom);
external_dom.transfer_within(cloned, external_dom.root_ref()); external_dom.transfer_within(cloned, external_dom.root_ref());
@ -161,15 +150,9 @@ impl Instance {
on the Roblox Developer Hub on the Roblox Developer Hub
*/ */
pub fn clone_instance(&self) -> Instance { pub fn clone_instance(&self) -> Instance {
// NOTE: We create a new scope here to avoid deadlocking since let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
// our clone implementation must have exclusive write access let new_ref = dom.clone_within(self.dom_ref);
let new_ref = { drop(dom); // Self::new needs mutex handle, drop it first
let mut dom = INTERNAL_DOM
.try_write()
.expect("Failed to get write access to document");
dom.clone_within(self.dom_ref)
};
let new_inst = Self::new(new_ref); let new_inst = Self::new(new_ref);
new_inst.set_parent(None); new_inst.set_parent(None);
@ -193,32 +176,18 @@ impl Instance {
if self.is_destroyed() { if self.is_destroyed() {
false false
} else { } else {
let mut dom = INTERNAL_DOM let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
.try_write()
.expect("Failed to get write access to document");
dom.destroy(self.dom_ref); dom.destroy(self.dom_ref);
true true
} }
} }
fn ensure_not_destroyed(&self) -> LuaResult<()> {
if self.is_destroyed() {
Err(LuaError::RuntimeError(
"Instance has been destroyed".to_string(),
))
} else {
Ok(())
}
}
fn is_destroyed(&self) -> bool { fn is_destroyed(&self) -> bool {
// NOTE: This property can not be cached since instance references // NOTE: This property can not be cached since instance references
// other than this one may have destroyed this one, and we don't // other than this one may have destroyed this one, and we don't
// keep track of all current instance reference structs // keep track of all current instance reference structs
let dom = INTERNAL_DOM let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
.try_read()
.expect("Failed to get read access to document");
dom.get_by_ref(self.dom_ref).is_none() dom.get_by_ref(self.dom_ref).is_none()
} }
@ -231,9 +200,7 @@ impl Instance {
on the Roblox Developer Hub on the Roblox Developer Hub
*/ */
pub fn clear_all_children(&mut self) { pub fn clear_all_children(&mut self) {
let mut dom = INTERNAL_DOM let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
.try_write()
.expect("Failed to get write access to document");
let instance = dom let instance = dom
.get_by_ref(self.dom_ref) .get_by_ref(self.dom_ref)
@ -277,9 +244,7 @@ impl Instance {
on the Roblox Developer Hub on the Roblox Developer Hub
*/ */
pub fn get_name(&self) -> String { pub fn get_name(&self) -> String {
let dom = INTERNAL_DOM let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
.try_read()
.expect("Failed to get read access to document");
dom.get_by_ref(self.dom_ref) dom.get_by_ref(self.dom_ref)
.expect("Failed to find instance in document") .expect("Failed to find instance in document")
@ -295,9 +260,7 @@ impl Instance {
on the Roblox Developer Hub on the Roblox Developer Hub
*/ */
pub fn set_name(&self, name: impl Into<String>) { pub fn set_name(&self, name: impl Into<String>) {
let mut dom = INTERNAL_DOM let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
.try_write()
.expect("Failed to get write access to document");
dom.get_by_ref_mut(self.dom_ref) dom.get_by_ref_mut(self.dom_ref)
.expect("Failed to find instance in document") .expect("Failed to find instance in document")
@ -312,9 +275,7 @@ impl Instance {
on the Roblox Developer Hub on the Roblox Developer Hub
*/ */
pub fn get_parent(&self) -> Option<Instance> { pub fn get_parent(&self) -> Option<Instance> {
let dom = INTERNAL_DOM let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
.try_read()
.expect("Failed to get read access to document");
let parent_ref = dom let parent_ref = dom
.get_by_ref(self.dom_ref) .get_by_ref(self.dom_ref)
@ -324,6 +285,7 @@ impl Instance {
if parent_ref == dom.root_ref() { if parent_ref == dom.root_ref() {
None None
} else { } else {
drop(dom); // Self::new needs mutex handle, drop it first
Some(Self::new(parent_ref)) Some(Self::new(parent_ref))
} }
} }
@ -340,9 +302,7 @@ impl Instance {
on the Roblox Developer Hub on the Roblox Developer Hub
*/ */
pub fn set_parent(&self, parent: Option<Instance>) { pub fn set_parent(&self, parent: Option<Instance>) {
let mut dom = INTERNAL_DOM let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
.try_write()
.expect("Failed to get write access to target document");
let parent_ref = parent let parent_ref = parent
.map(|parent| parent.dom_ref) .map(|parent| parent.dom_ref)
@ -356,8 +316,8 @@ impl Instance {
*/ */
pub fn get_property(&self, name: impl AsRef<str>) -> Option<DomValue> { pub fn get_property(&self, name: impl AsRef<str>) -> Option<DomValue> {
INTERNAL_DOM INTERNAL_DOM
.try_read() .lock()
.expect("Failed to get read access to document") .expect("Failed to lock document")
.get_by_ref(self.dom_ref) .get_by_ref(self.dom_ref)
.expect("Failed to find instance in document") .expect("Failed to find instance in document")
.properties .properties
@ -373,8 +333,8 @@ impl Instance {
*/ */
pub fn set_property(&self, name: impl AsRef<str>, value: DomValue) { pub fn set_property(&self, name: impl AsRef<str>, value: DomValue) {
INTERNAL_DOM INTERNAL_DOM
.try_write() .lock()
.expect("Failed to get read access to document") .expect("Failed to lock document")
.get_by_ref_mut(self.dom_ref) .get_by_ref_mut(self.dom_ref)
.expect("Failed to find instance in document") .expect("Failed to find instance in document")
.properties .properties
@ -389,9 +349,7 @@ impl Instance {
on the Roblox Developer Hub on the Roblox Developer Hub
*/ */
pub fn get_attribute(&self, name: impl AsRef<str>) -> Option<DomValue> { pub fn get_attribute(&self, name: impl AsRef<str>) -> Option<DomValue> {
let dom = INTERNAL_DOM let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
.try_read()
.expect("Failed to get read access to document");
let inst = dom let inst = dom
.get_by_ref(self.dom_ref) .get_by_ref(self.dom_ref)
.expect("Failed to find instance in document"); .expect("Failed to find instance in document");
@ -412,9 +370,7 @@ impl Instance {
on the Roblox Developer Hub on the Roblox Developer Hub
*/ */
pub fn get_attributes(&self) -> BTreeMap<String, DomValue> { pub fn get_attributes(&self) -> BTreeMap<String, DomValue> {
let dom = INTERNAL_DOM let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
.try_read()
.expect("Failed to get read access to document");
let inst = dom let inst = dom
.get_by_ref(self.dom_ref) .get_by_ref(self.dom_ref)
.expect("Failed to find instance in document"); .expect("Failed to find instance in document");
@ -435,9 +391,7 @@ impl Instance {
on the Roblox Developer Hub on the Roblox Developer Hub
*/ */
pub fn set_attribute(&self, name: impl AsRef<str>, value: DomValue) { pub fn set_attribute(&self, name: impl AsRef<str>, value: DomValue) {
let mut dom = INTERNAL_DOM let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
.try_write()
.expect("Failed to get write access to document");
let inst = dom let inst = dom
.get_by_ref_mut(self.dom_ref) .get_by_ref_mut(self.dom_ref)
.expect("Failed to find instance in document"); .expect("Failed to find instance in document");
@ -469,9 +423,7 @@ impl Instance {
on the Roblox Developer Hub on the Roblox Developer Hub
*/ */
pub fn add_tag(&self, name: impl AsRef<str>) { pub fn add_tag(&self, name: impl AsRef<str>) {
let mut dom = INTERNAL_DOM let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
.try_write()
.expect("Failed to get write access to document");
let inst = dom let inst = dom
.get_by_ref_mut(self.dom_ref) .get_by_ref_mut(self.dom_ref)
.expect("Failed to find instance in document"); .expect("Failed to find instance in document");
@ -493,9 +445,7 @@ impl Instance {
on the Roblox Developer Hub on the Roblox Developer Hub
*/ */
pub fn get_tags(&self) -> Vec<String> { pub fn get_tags(&self) -> Vec<String> {
let dom = INTERNAL_DOM let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
.try_read()
.expect("Failed to get read access to document");
let inst = dom let inst = dom
.get_by_ref(self.dom_ref) .get_by_ref(self.dom_ref)
.expect("Failed to find instance in document"); .expect("Failed to find instance in document");
@ -514,9 +464,7 @@ impl Instance {
on the Roblox Developer Hub on the Roblox Developer Hub
*/ */
pub fn has_tag(&self, name: impl AsRef<str>) -> bool { pub fn has_tag(&self, name: impl AsRef<str>) -> bool {
let dom = INTERNAL_DOM let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
.try_read()
.expect("Failed to get read access to document");
let inst = dom let inst = dom
.get_by_ref(self.dom_ref) .get_by_ref(self.dom_ref)
.expect("Failed to find instance in document"); .expect("Failed to find instance in document");
@ -536,9 +484,7 @@ impl Instance {
on the Roblox Developer Hub on the Roblox Developer Hub
*/ */
pub fn remove_tag(&self, name: impl AsRef<str>) { pub fn remove_tag(&self, name: impl AsRef<str>) {
let mut dom = INTERNAL_DOM let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
.try_write()
.expect("Failed to get write access to document");
let inst = dom let inst = dom
.get_by_ref_mut(self.dom_ref) .get_by_ref_mut(self.dom_ref)
.expect("Failed to find instance in document"); .expect("Failed to find instance in document");
@ -564,9 +510,7 @@ impl Instance {
on the Roblox Developer Hub on the Roblox Developer Hub
*/ */
pub fn get_children(&self) -> Vec<Instance> { pub fn get_children(&self) -> Vec<Instance> {
let dom = INTERNAL_DOM let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
.try_read()
.expect("Failed to get read access to document");
let children = dom let children = dom
.get_by_ref(self.dom_ref) .get_by_ref(self.dom_ref)
@ -574,6 +518,7 @@ impl Instance {
.children() .children()
.to_vec(); .to_vec();
drop(dom); // Self::new needs mutex handle, drop it first
children.into_iter().map(Self::new).collect() children.into_iter().map(Self::new).collect()
} }
@ -588,9 +533,7 @@ impl Instance {
on the Roblox Developer Hub on the Roblox Developer Hub
*/ */
pub fn get_descendants(&self) -> Vec<Instance> { pub fn get_descendants(&self) -> Vec<Instance> {
let dom = INTERNAL_DOM let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
.try_read()
.expect("Failed to get read access to document");
let mut descendants = Vec::new(); let mut descendants = Vec::new();
let mut queue = VecDeque::from_iter( let mut queue = VecDeque::from_iter(
@ -607,6 +550,7 @@ impl Instance {
} }
} }
drop(dom); // Self::new needs mutex handle, drop it first
descendants.into_iter().map(Self::new).collect() descendants.into_iter().map(Self::new).collect()
} }
@ -623,9 +567,7 @@ impl Instance {
on the Roblox Developer Hub on the Roblox Developer Hub
*/ */
pub fn get_full_name(&self) -> String { pub fn get_full_name(&self) -> String {
let dom = INTERNAL_DOM let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
.try_read()
.expect("Failed to get read access to document");
let dom_root = dom.root_ref(); let dom_root = dom.root_ref();
let mut parts = Vec::new(); let mut parts = Vec::new();
@ -656,9 +598,7 @@ impl Instance {
where where
F: Fn(&DomInstance) -> bool, F: Fn(&DomInstance) -> bool,
{ {
let dom = INTERNAL_DOM let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
.try_read()
.expect("Failed to get read access to document");
let children = dom let children = dom
.get_by_ref(self.dom_ref) .get_by_ref(self.dom_ref)
@ -666,17 +606,16 @@ impl Instance {
.children() .children()
.to_vec(); .to_vec();
children.into_iter().find_map(|child_ref| { let found_ref = children.into_iter().find(|child_ref| {
if let Some(child_inst) = dom.get_by_ref(child_ref) { if let Some(child_inst) = dom.get_by_ref(*child_ref) {
if predicate(child_inst) { predicate(child_inst)
Some(Self::new(child_ref))
} else {
None
}
} else { } else {
None false
} }
}) });
drop(dom); // Self::new needs mutex handle, drop it first
found_ref.map(Self::new)
} }
/** /**
@ -691,9 +630,7 @@ impl Instance {
where where
F: Fn(&DomInstance) -> bool, F: Fn(&DomInstance) -> bool,
{ {
let dom = INTERNAL_DOM let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
.try_read()
.expect("Failed to get read access to document");
let mut ancestor_ref = dom let mut ancestor_ref = dom
.get_by_ref(self.dom_ref) .get_by_ref(self.dom_ref)
@ -702,6 +639,7 @@ impl Instance {
while let Some(ancestor) = dom.get_by_ref(ancestor_ref) { while let Some(ancestor) = dom.get_by_ref(ancestor_ref) {
if predicate(ancestor) { if predicate(ancestor) {
drop(dom); // Self::new needs mutex handle, drop it first
return Some(Self::new(ancestor_ref)); return Some(Self::new(ancestor_ref));
} else { } else {
ancestor_ref = ancestor.parent(); ancestor_ref = ancestor.parent();
@ -723,9 +661,7 @@ impl Instance {
where where
F: Fn(&DomInstance) -> bool, F: Fn(&DomInstance) -> bool,
{ {
let dom = INTERNAL_DOM let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
.try_read()
.expect("Failed to get read access to document");
let mut queue = VecDeque::from_iter( let mut queue = VecDeque::from_iter(
dom.get_by_ref(self.dom_ref) dom.get_by_ref(self.dom_ref)
@ -738,7 +674,9 @@ impl Instance {
.and_then(|queue_ref| dom.get_by_ref(*queue_ref)) .and_then(|queue_ref| dom.get_by_ref(*queue_ref))
{ {
if predicate(queue_item) { if predicate(queue_item) {
return Some(Self::new(queue_item.referent())); let queue_ref = queue_item.referent();
drop(dom); // Self::new needs mutex handle, drop it first
return Some(Self::new(queue_ref));
} else { } else {
queue.extend(queue_item.children()) queue.extend(queue_item.children())
} }
@ -766,341 +704,23 @@ impl Instance {
} }
} }
/*
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
*/
impl LuaUserData for Instance { impl LuaUserData for Instance {
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) { fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
// Here we add inheritance-like behavior for instances by creating
// fields that are restricted to specific classnames / base classes
data_model::add_fields(fields); data_model::add_fields(fields);
workspace::add_fields(fields); workspace::add_fields(fields);
} }
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_method(LuaMetaMethod::ToString, |lua, this, ()| { base::add_methods(methods);
this.ensure_not_destroyed()?;
userdata_impl_to_string(lua, this, ())
});
methods.add_meta_method(LuaMetaMethod::Eq, userdata_impl_eq);
/*
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
*/
methods.add_meta_method(LuaMetaMethod::Index, |lua, this, prop_name: String| {
this.ensure_not_destroyed()?;
match prop_name.as_str() {
"ClassName" => return this.get_class_name().into_lua(lua),
"Name" => {
return this.get_name().into_lua(lua);
}
"Parent" => {
return this.get_parent().into_lua(lua);
}
_ => {}
}
if let Some(info) = find_property_info(&this.class_name, &prop_name) {
if let Some(prop) = this.get_property(&prop_name) {
if let DomValue::Enum(enum_value) = prop {
let enum_name = info.enum_name.ok_or_else(|| {
LuaError::RuntimeError(format!(
"Failed to get property '{}' - encountered unknown enum",
prop_name
))
})?;
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 '{}' - Enum.{} does not contain numeric value {}",
prop_name, enum_name, 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 '{}' - missing default value",
prop_name
)))
}
} else {
Err(LuaError::RuntimeError(format!(
"Failed to get property '{}' - malformed property info",
prop_name
)))
}
} else if let Some(inst) = this.find_child(|inst| inst.name == prop_name) {
Ok(LuaValue::UserData(lua.create_userdata(inst)?))
} else {
Err(LuaError::RuntimeError(format!(
"{} is not a valid member of {}",
prop_name, this
)))
}
});
/*
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
*/
methods.add_meta_method_mut(
LuaMetaMethod::NewIndex,
|lua, this, (prop_name, prop_value): (String, LuaValue)| {
this.ensure_not_destroyed()?;
match prop_name.as_str() {
"ClassName" => {
return Err(LuaError::RuntimeError(format!(
"Failed to set property '{}' - property is read-only",
prop_name
)));
}
"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(format!(
"Failed to set property '{}' - DataModel can not be reparented",
prop_name
)));
}
type Parent<'lua> = Option<LuaUserDataRef<'lua, Instance>>;
let parent = Parent::from_lua(prop_value, lua)?;
this.set_parent(parent.map(|p| p.clone()));
return Ok(());
}
_ => {}
}
let info = match find_property_info(&this.class_name, &prop_name) {
Some(b) => b,
None => {
return Err(LuaError::RuntimeError(format!(
"{} is not a valid member of {}",
prop_name, this
)))
}
};
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::Enum((*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 '{}' - malformed property info",
prop_name
)))
}
},
);
/*
Implementations of base methods on the Instance class
It should be noted that any methods that deal with events
and/or have functionality that affects instances other
than this instance itself are intentionally left out.
*/
methods.add_method("Clone", |lua, this, ()| {
this.ensure_not_destroyed()?;
this.clone_instance().into_lua(lua)
});
methods.add_method_mut("Destroy", |_, this, ()| {
this.destroy();
Ok(())
});
methods.add_method_mut("ClearAllChildren", |_, this, ()| {
this.clear_all_children();
Ok(())
});
methods.add_method("GetChildren", |lua, this, ()| {
this.ensure_not_destroyed()?;
this.get_children().into_lua(lua)
});
methods.add_method("GetDescendants", |lua, this, ()| {
this.ensure_not_destroyed()?;
this.get_descendants().into_lua(lua)
});
methods.add_method("GetFullName", |lua, this, ()| {
this.ensure_not_destroyed()?;
this.get_full_name().into_lua(lua)
});
methods.add_method("FindFirstAncestor", |lua, this, name: String| {
this.ensure_not_destroyed()?;
this.find_ancestor(|child| child.name == name).into_lua(lua)
});
methods.add_method(
"FindFirstAncestorOfClass",
|lua, this, class_name: String| {
this.ensure_not_destroyed()?;
this.find_ancestor(|child| child.class == class_name)
.into_lua(lua)
},
);
methods.add_method(
"FindFirstAncestorWhichIsA",
|lua, this, class_name: String| {
this.ensure_not_destroyed()?;
this.find_ancestor(|child| class_is_a(&child.class, &class_name).unwrap_or(false))
.into_lua(lua)
},
);
methods.add_method(
"FindFirstChild",
|lua, this, (name, recursive): (String, Option<bool>)| {
this.ensure_not_destroyed()?;
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)
}
},
);
methods.add_method(
"FindFirstChildOfClass",
|lua, this, (class_name, recursive): (String, Option<bool>)| {
this.ensure_not_destroyed()?;
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)
}
},
);
methods.add_method(
"FindFirstChildWhichIsA",
|lua, this, (class_name, recursive): (String, Option<bool>)| {
this.ensure_not_destroyed()?;
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)
}
},
);
methods.add_method("IsA", |_, this, class_name: String| {
this.ensure_not_destroyed()?;
Ok(class_is_a(&this.class_name, class_name).unwrap_or(false))
});
methods.add_method(
"IsAncestorOf",
|_, this, instance: LuaUserDataRef<Instance>| {
this.ensure_not_destroyed()?;
Ok(instance
.find_ancestor(|ancestor| ancestor.referent() == this.dom_ref)
.is_some())
},
);
methods.add_method(
"IsDescendantOf",
|_, this, instance: LuaUserDataRef<Instance>| {
this.ensure_not_destroyed()?;
Ok(this
.find_ancestor(|ancestor| ancestor.referent() == instance.dom_ref)
.is_some())
},
);
methods.add_method("GetAttribute", |lua, this, name: String| {
this.ensure_not_destroyed()?;
match this.get_attribute(name) {
Some(attribute) => Ok(LuaValue::dom_value_to_lua(lua, &attribute)?),
None => Ok(LuaValue::Nil),
}
});
methods.add_method("GetAttributes", |lua, this, ()| {
this.ensure_not_destroyed()?;
let attributes = this.get_attributes();
let tab = lua.create_table_with_capacity(0, attributes.len())?;
for (key, value) in attributes.into_iter() {
tab.set(key, LuaValue::dom_value_to_lua(lua, &value)?)?;
}
Ok(tab)
});
methods.add_method(
"SetAttribute",
|lua, this, (attribute_name, lua_value): (String, LuaValue)| {
this.ensure_not_destroyed()?;
ensure_valid_attribute_name(&attribute_name)?;
match lua_value.lua_to_dom_value(lua, None) {
Ok(dom_value) => {
ensure_valid_attribute_value(&dom_value)?;
this.set_attribute(attribute_name, dom_value);
Ok(())
}
Err(e) => Err(e.into()),
}
},
);
methods.add_method("GetTags", |_, this, ()| {
this.ensure_not_destroyed()?;
Ok(this.get_tags())
});
methods.add_method("HasTag", |_, this, tag: String| {
this.ensure_not_destroyed()?;
Ok(this.has_tag(tag))
});
methods.add_method("AddTag", |_, this, tag: String| {
this.ensure_not_destroyed()?;
this.add_tag(tag);
Ok(())
});
methods.add_method("RemoveTag", |_, this, tag: String| {
this.ensure_not_destroyed()?;
this.remove_tag(tag);
Ok(())
});
// Here we add inheritance-like behavior for instances by creating
// methods that are restricted to specific classnames / base classes
data_model::add_methods(methods); data_model::add_methods(methods);
} }
} }

View file

@ -69,6 +69,7 @@ create_tests! {
require_children: "require/tests/children", require_children: "require/tests/children",
require_init: "require/tests/init", require_init: "require/tests/init",
require_invalid: "require/tests/invalid", require_invalid: "require/tests/invalid",
require_multi_ext: "require/tests/multi_ext",
require_nested: "require/tests/nested", require_nested: "require/tests/nested",
require_parents: "require/tests/parents", require_parents: "require/tests/parents",
require_siblings: "require/tests/siblings", require_siblings: "require/tests/siblings",

View file

@ -0,0 +1,4 @@
return {
Foo = "Bar",
Hello = "World",
}

View file

@ -0,0 +1 @@
require("multi.ext.file")