diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 4dbf2ad..b871d69 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -31,4 +31,4 @@ jobs:
run: cargo clippy
- name: Test
- run: cargo test --package lune -- --test-threads 1
+ run: cargo test --lib
diff --git a/.justfile b/.justfile
index 04e1760..33d8a26 100644
--- a/.justfile
+++ b/.justfile
@@ -8,4 +8,4 @@ run-file FILE_NAME:
# Run tests for the Lune library
test:
- cargo test --lib -- --test-threads 1
+ cargo test --lib
diff --git a/.vscode/settings.json b/.vscode/settings.json
index e19a802..75095c1 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -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": [
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 51430e2..869c785 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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 🚀
+ 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
diff --git a/Cargo.lock b/Cargo.lock
index 2f707e2..b5f2529 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -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",
]
diff --git a/Cargo.toml b/Cargo.toml
index 2bb9309..e1d9ee5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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" }
diff --git a/src/lune/builtins/fs.rs b/src/lune/builtins/fs.rs
index 8c4a081..71fc131 100644
--- a/src/lune/builtins/fs.rs
+++ b/src/lune/builtins/fs.rs
@@ -26,14 +26,14 @@ pub fn create(lua: &'static Lua) -> LuaResult {
}
async fn fs_read_file(lua: &'static Lua, path: String) -> LuaResult {
- 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> {
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 {
@@ -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(())
}
diff --git a/src/lune/builtins/net.rs b/src/lune/builtins/net.rs
index 358d5ce..8bb7707 100644
--- a/src/lune/builtins/net.rs
+++ b/src/lune/builtins/net.rs
@@ -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::>();
// 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 {
- 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)
}
diff --git a/src/lune/builtins/roblox.rs b/src/lune/builtins/roblox.rs
index 7c19386..2141d5c 100644
--- a/src/lune/builtins/roblox.rs
+++ b/src/lune/builtins/roblox.rs
@@ -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)
}
diff --git a/src/lune/builtins/stdio.rs b/src/lune/builtins/stdio.rs
index 48b12c7..acd3727 100644
--- a/src/lune/builtins/stdio.rs
+++ b/src/lune/builtins/stdio.rs
@@ -43,7 +43,7 @@ pub fn create(lua: &'static Lua) -> LuaResult {
.with_async_function("prompt", |_, options: PromptOptions| async move {
task::spawn_blocking(move || prompt(options))
.await
- .map_err(LuaError::external)?
+ .into_lua_err()?
})?
.build_readonly()
}
diff --git a/src/lune/importer/require.rs b/src/lune/importer/require.rs
index 4211a22..113c593 100644
--- a/src/lune/importer/require.rs
+++ b/src/lune/importer/require.rs
@@ -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>>>;
+fn append_extension_and_canonicalize(
+ path: impl Into,
+ ext: &'static str,
+) -> Result {
+ 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)?;
diff --git a/src/lune/lua/create.rs b/src/lune/lua/create.rs
index 4ab8dde..dc22ec8 100644
--- a/src/lune/lua/create.rs
+++ b/src/lune/lua/create.rs
@@ -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)
}
diff --git a/src/lune/lua/net/client.rs b/src/lune/lua/net/client.rs
index 6381f63..2120c58 100644
--- a/src/lune/lua/net/client.rs
+++ b/src/lune/lua/net/client.rs
@@ -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 {
- let client = self.builder.build().map_err(LuaError::external)?;
+ let client = self.builder.build().into_lua_err()?;
Ok(NetClient(client))
}
}
diff --git a/src/lune/lua/net/response.rs b/src/lune/lua/net/response.rs
index 2e94e24..fa2e748 100644
--- a/src/lune/lua/net/response.rs
+++ b/src/lune/lua/net/response.rs
@@ -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()?
}
})
}
diff --git a/src/lune/lua/net/server.rs b/src/lune/lua/net/server.rs
index 236c2ae..d3428df 100644
--- a/src/lune/lua/net/server.rs
+++ b/src/lune/lua/net/server.rs
@@ -57,7 +57,7 @@ impl Service> 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> 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)?
diff --git a/src/lune/lua/net/websocket.rs b/src/lune/lua/net/websocket.rs
index 2f613a9..1057b1b 100644
--- a/src/lune/lua/net/websocket.rs
+++ b/src/lune/lua/net/websocket.rs
@@ -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 {
diff --git a/src/lune/lua/process/mod.rs b/src/lune/lua/process/mod.rs
index c4274bd..b74cd00 100644
--- a/src/lune/lua/process/mod.rs
+++ b/src/lune/lua/process/mod.rs
@@ -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())
});
diff --git a/src/lune/lua/serde/compress_decompress.rs b/src/lune/lua/serde/compress_decompress.rs
index 528847b..85e7ac5 100644
--- a/src/lune/lua/serde/compress_decompress.rs
+++ b/src/lune/lua/serde/compress_decompress.rs
@@ -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();
diff --git a/src/lune/lua/serde/encode_decode.rs b/src/lune/lua/serde/encode_decode.rs
index e753847..edbcc1b 100644
--- a/src/lune/lua/serde/encode_decode.rs
+++ b/src/lune/lua/serde/encode_decode.rs
@@ -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> {
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(),
diff --git a/src/lune/lua/stdio/formatting.rs b/src/lune/lua/stdio/formatting.rs
index d51a83e..8437299 100644
--- a/src/lune/lua/stdio/formatting.rs
+++ b/src/lune/lua/stdio/formatting.rs
@@ -197,12 +197,12 @@ pub fn pretty_format_multi_value(multi: &LuaMultiValue) -> LuaResult {
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)
diff --git a/src/lune/mod.rs b/src/lune/mod.rs
index 09b3842..e0eeec6 100644
--- a/src/lune/mod.rs
+++ b/src/lune/mod.rs
@@ -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 {
// 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)?;
diff --git a/src/roblox/instance/base.rs b/src/roblox/instance/base.rs
new file mode 100644
index 0000000..b927a60
--- /dev/null
+++ b/src/roblox/instance/base.rs
@@ -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)| {
+ 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)| {
+ 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)| {
+ 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| {
+ ensure_not_destroyed(this)?;
+ Ok(instance
+ .find_ancestor(|ancestor| ancestor.referent() == this.dom_ref)
+ .is_some())
+ },
+ );
+ m.add_method(
+ "IsDescendantOf",
+ |_, this, instance: LuaUserDataRef| {
+ 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> {
+ 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>;
+ 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::::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
+ )))
+ }
+}
diff --git a/src/roblox/instance/mod.rs b/src/roblox/instance/mod.rs
index 4aec36d..9f99267 100644
--- a/src/roblox/instance/mod.rs
+++ b/src/roblox/instance/mod.rs
@@ -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> =
- Lazy::new(|| RwLock::new(WeakDom::new(DomInstanceBuilder::new("ROOT"))));
+static INTERNAL_DOM: Lazy> =
+ 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 {
- 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) -> 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) {
- 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 {
- 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) {
- 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) -> Option {
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, 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) -> Option {
- 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 {
- 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, 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) {
- 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 {
- 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) -> 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) {
- 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 {
- 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 {
- 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>;
- 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::::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)| {
- 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)| {
- 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)| {
- 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| {
- this.ensure_not_destroyed()?;
- Ok(instance
- .find_ancestor(|ancestor| ancestor.referent() == this.dom_ref)
- .is_some())
- },
- );
- methods.add_method(
- "IsDescendantOf",
- |_, this, instance: LuaUserDataRef| {
- 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);
}
}
diff --git a/src/tests.rs b/src/tests.rs
index b80f5bd..96a840c 100644
--- a/src/tests.rs
+++ b/src/tests.rs
@@ -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",
diff --git a/tests/require/tests/multi.ext.file.luau b/tests/require/tests/multi.ext.file.luau
new file mode 100644
index 0000000..cb3159b
--- /dev/null
+++ b/tests/require/tests/multi.ext.file.luau
@@ -0,0 +1,4 @@
+return {
+ Foo = "Bar",
+ Hello = "World",
+}
diff --git a/tests/require/tests/multi_ext.luau b/tests/require/tests/multi_ext.luau
new file mode 100644
index 0000000..ec20042
--- /dev/null
+++ b/tests/require/tests/multi_ext.luau
@@ -0,0 +1 @@
+require("multi.ext.file")