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
- 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
test:
cargo test --lib -- --test-threads 1
cargo test --lib

View file

@ -4,7 +4,7 @@
"luau-lsp.types.roblox": false,
"luau-lsp.require.mode": "relativeToFile",
"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-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/),
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
### Added

249
Cargo.lock generated
View file

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

View file

@ -70,14 +70,18 @@ thiserror = "1.0"
async-trait = "0.1"
dialoguer = "0.10"
dunce = "1.0"
lz4_flex = "0.10"
lz4_flex = "0.11"
pin-project = "1.0"
os_str_bytes = "6.4"
urlencoding = "2.1.2"
urlencoding = "2.1"
### 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"] }
### SERDE
@ -97,11 +101,11 @@ toml = { version = "0.7", features = ["preserve_order"] }
### NET
hyper = { version = "0.14", features = ["full"] }
hyper-tungstenite = { version = "0.10" }
hyper-tungstenite = { version = "0.11" }
reqwest = { version = "0.11", default-features = false, features = [
"rustls-tls",
] }
tokio-tungstenite = { version = "0.19", features = ["rustls-tls-webpki-roots"] }
tokio-tungstenite = { version = "0.20", features = ["rustls-tls-webpki-roots"] }
### CLI
@ -121,7 +125,7 @@ regex = { optional = true, version = "1.7", default-features = false, features =
glam = { optional = true, version = "0.24" }
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_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> {
let bytes = fs::read(&path).await.map_err(LuaError::external)?;
let bytes = fs::read(&path).await.into_lua_err()?;
lua.create_string(bytes)
}
async fn fs_read_dir(_: &'static Lua, path: String) -> LuaResult<Vec<String>> {
let mut dir_strings = Vec::new();
let mut dir = fs::read_dir(&path).await.map_err(LuaError::external)?;
while let Some(dir_entry) = dir.next_entry().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.into_lua_err()? {
if let Some(dir_path_str) = dir_entry.path().to_str() {
dir_strings.push(dir_path_str.to_owned());
} else {
@ -63,21 +63,19 @@ async fn fs_write_file(
_: &'static Lua,
(path, contents): (String, LuaString<'_>),
) -> LuaResult<()> {
fs::write(&path, &contents.as_bytes())
.await
.map_err(LuaError::external)
fs::write(&path, &contents.as_bytes()).await.into_lua_err()
}
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<()> {
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<()> {
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> {
@ -122,9 +120,7 @@ async fn fs_move(
path_to.display()
)));
}
fs::rename(path_from, path_to)
.await
.map_err(LuaError::external)?;
fs::rename(path_from, path_to).await.into_lua_err()?;
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())
.send()
.await
.map_err(LuaError::external)?;
.into_lua_err()?;
// Extract status, headers
let res_status = res.status().as_u16();
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>>();
// 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
if config.options.decompress {
// 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> {
let (ws, _) = tokio_tungstenite::connect_async(url)
.await
.map_err(LuaError::external)?;
let (ws, _) = tokio_tungstenite::connect_async(url).await.into_lua_err()?;
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()?;
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>(
@ -54,7 +54,7 @@ async fn deserialize_model<'lua>(
let instance_array = doc.into_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>(
@ -70,7 +70,7 @@ async fn serialize_place<'lua>(
})?;
Ok::<_, DocumentError>(bytes)
});
let bytes = fut.await.map_err(LuaError::external)??;
let bytes = fut.await.into_lua_err()??;
lua.create_string(bytes)
}
@ -87,7 +87,7 @@ async fn serialize_model<'lua>(
})?;
Ok::<_, DocumentError>(bytes)
});
let bytes = fut.await.map_err(LuaError::external)??;
let bytes = fut.await.into_lua_err()??;
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 {
task::spawn_blocking(move || prompt(options))
.await
.map_err(LuaError::external)?
.into_lua_err()?
})?
.build_readonly()
}

View file

@ -6,8 +6,7 @@ use std::{
sync::Arc,
};
use dunce::canonicalize;
use mlua::{prelude::*, Compiler as LuaCompiler};
use mlua::prelude::*;
use tokio::fs;
use tokio::sync::Mutex as AsyncMutex;
@ -29,6 +28,19 @@ return yield()
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)]
struct RequireContext<'lua> {
// NOTE: We need to use arc here so that mlua clones
@ -128,25 +140,28 @@ impl<'lua> RequireContext<'lua> {
.join(&require_path);
// Try to normalize and resolve relative path segments such as './' and '../'
let file_path = match (
canonicalize(path_relative_to_pwd.with_extension("luau")),
canonicalize(path_relative_to_pwd.with_extension("lua")),
append_extension_and_canonicalize(&path_relative_to_pwd, "luau"),
append_extension_and_canonicalize(&path_relative_to_pwd, "lua"),
) {
(Ok(luau), _) => luau,
(_, Ok(lua)) => lua,
// If we did not find a luau/lua file at the wanted path,
// we should also look for "init" files in directories
_ => match (
canonicalize(path_relative_to_pwd.join("init").with_extension("luau")),
canonicalize(path_relative_to_pwd.join("init").with_extension("lua")),
) {
(Ok(luau), _) => luau,
(_, Ok(lua)) => lua,
_ => {
return Err(LuaError::RuntimeError(format!(
"File does not exist at path '{require_path}'"
)))
_ => {
let init_dir_path = path_relative_to_pwd.join("init");
match (
append_extension_and_canonicalize(&init_dir_path, "luau"),
append_extension_and_canonicalize(&init_dir_path, "lua"),
) {
(Ok(luau), _) => luau,
(_, Ok(lua)) => lua,
_ => {
return Err(LuaError::RuntimeError(format!(
"File does not exist at path '{require_path}'"
)));
}
}
},
}
};
let absolute = file_path.to_string_lossy().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
// 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
// other code assumes the require path is without extensions
let path_relative_no_extension = relative_path
.trim_end_matches(".lua")
.trim_end_matches(".luau");
// Load the file into a thread
let compiled_func = LuaCompiler::default().compile(&contents);
let loaded_func = lua
.load(compiled_func)
.load(contents)
.set_name(path_relative_no_extension)
.into_function()?;
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
@ -76,14 +76,28 @@ end
*/
pub fn create() -> LuaResult<&'static Lua> {
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 debug: LuaTable = globals.raw_get("debug")?;
let table: LuaTable = globals.raw_get("table")?;
let string: LuaTable = globals.raw_get("string")?;
let coroutine: LuaTable = globals.get("coroutine")?;
// Create a _G table that is separate from our built-in globals
let global_table = lua.create_table()?;
globals.set("_G", global_table)?;
// Store original lua global functions in the registry so we can use
// them later without passing them around and dealing with lifetimes
lua.set_named_registry_value("print", globals.get::<_, LuaFunction>("print")?)?;
@ -109,6 +123,7 @@ pub fn create() -> LuaResult<&'static Lua> {
"tab.setmeta",
globals.get::<_, LuaFunction>("setmetatable")?,
)?;
// 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
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)
.into_function()?;
lua.set_named_registry_value("dbg.trace", dbg_trace_fn)?;
// Modify the _VERSION global to also contain the current version of Lune
let luau_version_full = globals
.get::<_, LuaString>("_VERSION")
@ -142,6 +158,7 @@ pub fn create() -> LuaResult<&'static Lua> {
luau = luau_version,
))?,
)?;
// All done
Ok(lua)
}

View file

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

View file

@ -24,7 +24,7 @@ impl NetServeResponse {
.status(200)
.header("Content-Type", "text/plain")
.body(Body::from(self.body.unwrap()))
.map_err(LuaError::external)?,
.into_lua_err()?,
NetServeResponseKind::Table => {
let mut response = Response::builder();
for (key, value) in self.headers {
@ -33,7 +33,7 @@ impl NetServeResponse {
response
.status(self.status)
.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 {
// Create our new full websocket object, then
// 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 sched = lua
.app_data_ref::<&TaskScheduler>()
@ -77,7 +77,7 @@ impl Service<Request<Body>> for NetServiceInner {
let (parts, body) = req.into_parts();
Box::pin(async move {
// 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)?;
// Create a readonly table for the request query params
let query_params = TableBuilder::new(lua)?

View file

@ -167,10 +167,10 @@ where
reason: "".into(),
})))
.await
.map_err(LuaError::external)?;
.into_lua_err()?;
let res = ws.close();
res.await.map_err(LuaError::external)
res.await.into_lua_err()
}
async fn send<'lua, T>(
@ -187,11 +187,11 @@ where
let msg = if matches!(as_binary, Some(true)) {
WsMessage::Binary(string.as_bytes().to_vec())
} else {
let s = string.to_str().map_err(LuaError::external)?;
let s = string.to_str().into_lua_err()?;
WsMessage::Text(s.to_string())
};
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>(
@ -202,7 +202,7 @@ where
T: AsyncRead + AsyncWrite + Unpin,
{
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 {
Ok(Some(WsMessage::Close(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 tee = AsyncTeeWriter::new(&mut stdout);
io::copy(&mut child_stdout, &mut tee)
.await
.map_err(LuaError::external)?;
io::copy(&mut child_stdout, &mut tee).await.into_lua_err()?;
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 tee = AsyncTeeWriter::new(&mut stderr);
io::copy(&mut child_stderr, &mut tee)
.await
.map_err(LuaError::external)?;
io::copy(&mut child_stderr, &mut tee).await.into_lua_err()?;
Ok::<_, LuaError>(tee.into_vec())
});

View file

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

View file

@ -4,6 +4,15 @@ use serde_json::Value as JsonValue;
use serde_yaml::Value as YamlValue;
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)]
pub enum EncodeDecodeFormat {
Json,
@ -50,22 +59,25 @@ impl EncodeDecodeConfig {
) -> LuaResult<LuaString<'lua>> {
let bytes = match self.format {
EncodeDecodeFormat::Json => {
let serialized: JsonValue = lua.from_value_with(value, LUA_DESERIALIZE_OPTIONS)?;
if self.pretty {
serde_json::to_vec_pretty(&value).map_err(LuaError::external)?
serde_json::to_vec_pretty(&serialized).into_lua_err()?
} else {
serde_json::to_vec(&value).map_err(LuaError::external)?
serde_json::to_vec(&serialized).into_lua_err()?
}
}
EncodeDecodeFormat::Yaml => {
let serialized: YamlValue = lua.from_value_with(value, LUA_DESERIALIZE_OPTIONS)?;
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
}
EncodeDecodeFormat::Toml => {
let serialized: TomlValue = lua.from_value_with(value, LUA_DESERIALIZE_OPTIONS)?;
let s = if self.pretty {
toml::to_string_pretty(&value).map_err(LuaError::external)?
toml::to_string_pretty(&serialized).into_lua_err()?
} else {
toml::to_string(&value).map_err(LuaError::external)?
toml::to_string(&serialized).into_lua_err()?
};
s.as_bytes().to_vec()
}
@ -81,17 +93,17 @@ impl EncodeDecodeConfig {
let bytes = string.as_bytes();
match self.format {
EncodeDecodeFormat::Json => {
let value: JsonValue = serde_json::from_slice(bytes).map_err(LuaError::external)?;
lua.to_value(&value)
let value: JsonValue = serde_json::from_slice(bytes).into_lua_err()?;
lua.to_value_with(&value, LUA_SERIALIZE_OPTIONS)
}
EncodeDecodeFormat::Yaml => {
let value: YamlValue = serde_yaml::from_slice(bytes).map_err(LuaError::external)?;
lua.to_value(&value)
let value: YamlValue = serde_yaml::from_slice(bytes).into_lua_err()?;
lua.to_value_with(&value, LUA_SERIALIZE_OPTIONS)
}
EncodeDecodeFormat::Toml => {
if let Ok(s) = string.to_str() {
let value: TomlValue = toml::from_str(s).map_err(LuaError::external)?;
lua.to_value(&value)
let value: TomlValue = toml::from_str(s).into_lua_err()?;
lua.to_value_with(&value, LUA_SERIALIZE_OPTIONS)
} else {
Err(LuaError::RuntimeError(
"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 {
counter += 1;
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 {
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() {
write!(&mut buffer, " ").map_err(LuaError::external)?;
write!(&mut buffer, " ").into_lua_err()?;
}
}
Ok(buffer)

View file

@ -2,7 +2,6 @@ use std::process::ExitCode;
use lua::task::{TaskScheduler, TaskSchedulerResumeExt, TaskSchedulerScheduleExt};
use mlua::prelude::*;
use mlua::Compiler as LuaCompiler;
use tokio::task::LocalSet;
pub mod builtins;
@ -67,7 +66,6 @@ impl Lune {
) -> Result<ExitCode, LuaError> {
// Create our special lune-flavored Lua object with extra registry values
let lua = lua::create_lune_lua()?;
let script = LuaCompiler::default().compile(script_contents);
// Create our task scheduler and all globals
// NOTE: Some globals require the task scheduler to exist on startup
let sched = TaskScheduler::new(lua)?.into_static();
@ -75,7 +73,7 @@ impl Lune {
importer::create(lua, self.args.clone())?;
// Create the main thread and schedule it
let main_chunk = lua
.load(script)
.load(script_contents.as_ref())
.set_name(script_name.as_ref())
.into_function()?;
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},
fmt,
hash::{Hash, Hasher},
sync::RwLock,
sync::Mutex,
};
use mlua::prelude::*;
use once_cell::sync::Lazy;
use rbx_dom_weak::{
types::{
Attributes as DomAttributes, Ref as DomRef, Variant as DomValue, VariantType as DomType,
},
types::{Attributes as DomAttributes, Ref as DomRef, Variant as DomValue},
Instance as DomInstance, InstanceBuilder as DomInstanceBuilder, WeakDom,
};
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_exists, class_is_a, find_property_info},
};
use crate::roblox::shared::instance::{class_exists, class_is_a};
pub(crate) mod base;
pub(crate) mod data_model;
pub(crate) mod workspace;
const PROPERTY_NAME_ATTRIBUTES: &str = "Attributes";
const PROPERTY_NAME_TAGS: &str = "Tags";
static INTERNAL_DOM: Lazy<RwLock<WeakDom>> =
Lazy::new(|| RwLock::new(WeakDom::new(DomInstanceBuilder::new("ROOT"))));
static INTERNAL_DOM: Lazy<Mutex<WeakDom>> =
Lazy::new(|| Mutex::new(WeakDom::new(DomInstanceBuilder::new("ROOT"))));
#[derive(Debug, Clone)]
pub struct Instance {
@ -45,11 +36,12 @@ impl Instance {
Panics if the instance does not exist in the internal dom,
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 {
let dom = INTERNAL_DOM
.try_read()
.expect("Failed to get read access to document");
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
let instance = dom
.get_by_ref(dom_ref)
@ -69,11 +61,12 @@ impl Instance {
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.
**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> {
let dom = INTERNAL_DOM
.try_read()
.expect("Failed to get read access to document");
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
if let Some(instance) = dom.get_by_ref(dom_ref) {
if instance.referent() == dom.root_ref() {
@ -93,11 +86,12 @@ impl Instance {
Creates a new orphaned `Instance` with a given class name.
An orphaned instance is an instance at the root of a weak dom.
**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 {
let mut dom = INTERNAL_DOM
.try_write()
.expect("Failed to get write access to document");
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
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.
*/
pub fn from_external_dom(external_dom: &mut WeakDom, external_dom_ref: DomRef) -> Self {
{
let mut dom = INTERNAL_DOM
.try_write()
.expect("Failed to get write access to document");
let dom_root = dom.root_ref();
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
let dom_root = dom.root_ref();
external_dom.transfer(external_dom_ref, &mut dom, dom_root);
}
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)
}
@ -140,9 +131,7 @@ impl Instance {
root of the weak dom, and return its referent.
*/
pub fn clone_into_external_dom(self, external_dom: &mut WeakDom) -> DomRef {
let dom = INTERNAL_DOM
.try_read()
.expect("Failed to get read access to document");
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
let cloned = dom.clone_into_external(self.dom_ref, external_dom);
external_dom.transfer_within(cloned, external_dom.root_ref());
@ -161,15 +150,9 @@ impl Instance {
on the Roblox Developer Hub
*/
pub fn clone_instance(&self) -> Instance {
// NOTE: We create a new scope here to avoid deadlocking since
// our clone implementation must have exclusive write access
let new_ref = {
let mut dom = INTERNAL_DOM
.try_write()
.expect("Failed to get write access to document");
dom.clone_within(self.dom_ref)
};
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
let new_ref = dom.clone_within(self.dom_ref);
drop(dom); // Self::new needs mutex handle, drop it first
let new_inst = Self::new(new_ref);
new_inst.set_parent(None);
@ -193,32 +176,18 @@ impl Instance {
if self.is_destroyed() {
false
} else {
let mut dom = INTERNAL_DOM
.try_write()
.expect("Failed to get write access to document");
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
dom.destroy(self.dom_ref);
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 {
// NOTE: This property can not be cached since instance references
// other than this one may have destroyed this one, and we don't
// keep track of all current instance reference structs
let dom = INTERNAL_DOM
.try_read()
.expect("Failed to get read access to document");
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
dom.get_by_ref(self.dom_ref).is_none()
}
@ -231,9 +200,7 @@ impl Instance {
on the Roblox Developer Hub
*/
pub fn clear_all_children(&mut self) {
let mut dom = INTERNAL_DOM
.try_write()
.expect("Failed to get write access to document");
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
let instance = dom
.get_by_ref(self.dom_ref)
@ -277,9 +244,7 @@ impl Instance {
on the Roblox Developer Hub
*/
pub fn get_name(&self) -> String {
let dom = INTERNAL_DOM
.try_read()
.expect("Failed to get read access to document");
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
dom.get_by_ref(self.dom_ref)
.expect("Failed to find instance in document")
@ -295,9 +260,7 @@ impl Instance {
on the Roblox Developer Hub
*/
pub fn set_name(&self, name: impl Into<String>) {
let mut dom = INTERNAL_DOM
.try_write()
.expect("Failed to get write access to document");
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
dom.get_by_ref_mut(self.dom_ref)
.expect("Failed to find instance in document")
@ -312,9 +275,7 @@ impl Instance {
on the Roblox Developer Hub
*/
pub fn get_parent(&self) -> Option<Instance> {
let dom = INTERNAL_DOM
.try_read()
.expect("Failed to get read access to document");
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
let parent_ref = dom
.get_by_ref(self.dom_ref)
@ -324,6 +285,7 @@ impl Instance {
if parent_ref == dom.root_ref() {
None
} else {
drop(dom); // Self::new needs mutex handle, drop it first
Some(Self::new(parent_ref))
}
}
@ -340,9 +302,7 @@ impl Instance {
on the Roblox Developer Hub
*/
pub fn set_parent(&self, parent: Option<Instance>) {
let mut dom = INTERNAL_DOM
.try_write()
.expect("Failed to get write access to target document");
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
let parent_ref = parent
.map(|parent| parent.dom_ref)
@ -356,8 +316,8 @@ impl Instance {
*/
pub fn get_property(&self, name: impl AsRef<str>) -> Option<DomValue> {
INTERNAL_DOM
.try_read()
.expect("Failed to get read access to document")
.lock()
.expect("Failed to lock document")
.get_by_ref(self.dom_ref)
.expect("Failed to find instance in document")
.properties
@ -373,8 +333,8 @@ impl Instance {
*/
pub fn set_property(&self, name: impl AsRef<str>, value: DomValue) {
INTERNAL_DOM
.try_write()
.expect("Failed to get read access to document")
.lock()
.expect("Failed to lock document")
.get_by_ref_mut(self.dom_ref)
.expect("Failed to find instance in document")
.properties
@ -389,9 +349,7 @@ impl Instance {
on the Roblox Developer Hub
*/
pub fn get_attribute(&self, name: impl AsRef<str>) -> Option<DomValue> {
let dom = INTERNAL_DOM
.try_read()
.expect("Failed to get read access to document");
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
let inst = dom
.get_by_ref(self.dom_ref)
.expect("Failed to find instance in document");
@ -412,9 +370,7 @@ impl Instance {
on the Roblox Developer Hub
*/
pub fn get_attributes(&self) -> BTreeMap<String, DomValue> {
let dom = INTERNAL_DOM
.try_read()
.expect("Failed to get read access to document");
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
let inst = dom
.get_by_ref(self.dom_ref)
.expect("Failed to find instance in document");
@ -435,9 +391,7 @@ impl Instance {
on the Roblox Developer Hub
*/
pub fn set_attribute(&self, name: impl AsRef<str>, value: DomValue) {
let mut dom = INTERNAL_DOM
.try_write()
.expect("Failed to get write access to document");
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
let inst = dom
.get_by_ref_mut(self.dom_ref)
.expect("Failed to find instance in document");
@ -469,9 +423,7 @@ impl Instance {
on the Roblox Developer Hub
*/
pub fn add_tag(&self, name: impl AsRef<str>) {
let mut dom = INTERNAL_DOM
.try_write()
.expect("Failed to get write access to document");
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
let inst = dom
.get_by_ref_mut(self.dom_ref)
.expect("Failed to find instance in document");
@ -493,9 +445,7 @@ impl Instance {
on the Roblox Developer Hub
*/
pub fn get_tags(&self) -> Vec<String> {
let dom = INTERNAL_DOM
.try_read()
.expect("Failed to get read access to document");
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
let inst = dom
.get_by_ref(self.dom_ref)
.expect("Failed to find instance in document");
@ -514,9 +464,7 @@ impl Instance {
on the Roblox Developer Hub
*/
pub fn has_tag(&self, name: impl AsRef<str>) -> bool {
let dom = INTERNAL_DOM
.try_read()
.expect("Failed to get read access to document");
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
let inst = dom
.get_by_ref(self.dom_ref)
.expect("Failed to find instance in document");
@ -536,9 +484,7 @@ impl Instance {
on the Roblox Developer Hub
*/
pub fn remove_tag(&self, name: impl AsRef<str>) {
let mut dom = INTERNAL_DOM
.try_write()
.expect("Failed to get write access to document");
let mut dom = INTERNAL_DOM.lock().expect("Failed to lock document");
let inst = dom
.get_by_ref_mut(self.dom_ref)
.expect("Failed to find instance in document");
@ -564,9 +510,7 @@ impl Instance {
on the Roblox Developer Hub
*/
pub fn get_children(&self) -> Vec<Instance> {
let dom = INTERNAL_DOM
.try_read()
.expect("Failed to get read access to document");
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
let children = dom
.get_by_ref(self.dom_ref)
@ -574,6 +518,7 @@ impl Instance {
.children()
.to_vec();
drop(dom); // Self::new needs mutex handle, drop it first
children.into_iter().map(Self::new).collect()
}
@ -588,9 +533,7 @@ impl Instance {
on the Roblox Developer Hub
*/
pub fn get_descendants(&self) -> Vec<Instance> {
let dom = INTERNAL_DOM
.try_read()
.expect("Failed to get read access to document");
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
let mut descendants = Vec::new();
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()
}
@ -623,9 +567,7 @@ impl Instance {
on the Roblox Developer Hub
*/
pub fn get_full_name(&self) -> String {
let dom = INTERNAL_DOM
.try_read()
.expect("Failed to get read access to document");
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
let dom_root = dom.root_ref();
let mut parts = Vec::new();
@ -656,9 +598,7 @@ impl Instance {
where
F: Fn(&DomInstance) -> bool,
{
let dom = INTERNAL_DOM
.try_read()
.expect("Failed to get read access to document");
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
let children = dom
.get_by_ref(self.dom_ref)
@ -666,17 +606,16 @@ impl Instance {
.children()
.to_vec();
children.into_iter().find_map(|child_ref| {
if let Some(child_inst) = dom.get_by_ref(child_ref) {
if predicate(child_inst) {
Some(Self::new(child_ref))
} else {
None
}
let found_ref = children.into_iter().find(|child_ref| {
if let Some(child_inst) = dom.get_by_ref(*child_ref) {
predicate(child_inst)
} 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
F: Fn(&DomInstance) -> bool,
{
let dom = INTERNAL_DOM
.try_read()
.expect("Failed to get read access to document");
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
let mut ancestor_ref = dom
.get_by_ref(self.dom_ref)
@ -702,6 +639,7 @@ impl Instance {
while let Some(ancestor) = dom.get_by_ref(ancestor_ref) {
if predicate(ancestor) {
drop(dom); // Self::new needs mutex handle, drop it first
return Some(Self::new(ancestor_ref));
} else {
ancestor_ref = ancestor.parent();
@ -723,9 +661,7 @@ impl Instance {
where
F: Fn(&DomInstance) -> bool,
{
let dom = INTERNAL_DOM
.try_read()
.expect("Failed to get read access to document");
let dom = INTERNAL_DOM.lock().expect("Failed to lock document");
let mut queue = VecDeque::from_iter(
dom.get_by_ref(self.dom_ref)
@ -738,7 +674,9 @@ impl Instance {
.and_then(|queue_ref| dom.get_by_ref(*queue_ref))
{
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 {
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 {
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);
workspace::add_fields(fields);
}
fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) {
methods.add_meta_method(LuaMetaMethod::ToString, |lua, this, ()| {
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
base::add_methods(methods);
data_model::add_methods(methods);
}
}

View file

@ -69,6 +69,7 @@ create_tests! {
require_children: "require/tests/children",
require_init: "require/tests/init",
require_invalid: "require/tests/invalid",
require_multi_ext: "require/tests/multi_ext",
require_nested: "require/tests/nested",
require_parents: "require/tests/parents",
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")