From cd34dcb0dd1b123b27461748bc5eb898e174a651 Mon Sep 17 00:00:00 2001 From: Filip Tibell Date: Mon, 11 Mar 2024 19:11:14 +0100 Subject: [PATCH] Rewrite scheduler and make it smol (#165) --- Cargo.lock | 806 +++++++++++++----- Cargo.toml | 25 +- src/lune/builtins/fs/mod.rs | 2 +- src/lune/builtins/mod.rs | 7 +- src/lune/builtins/net/client.rs | 111 ++- src/lune/builtins/net/config.rs | 189 ++-- src/lune/builtins/net/mod.rs | 135 +-- src/lune/builtins/net/processing.rs | 101 --- src/lune/builtins/net/server.rs | 223 ----- src/lune/builtins/net/server/keys.rs | 61 ++ src/lune/builtins/net/server/mod.rs | 105 +++ src/lune/builtins/net/server/request.rs | 46 + .../builtins/net/{ => server}/response.rs | 61 +- src/lune/builtins/net/server/service.rs | 81 ++ src/lune/builtins/net/util.rs | 14 +- src/lune/builtins/net/websocket.rs | 264 +++--- src/lune/builtins/process/mod.rs | 56 +- src/lune/builtins/roblox/mod.rs | 21 +- .../builtins/serde/compress_decompress.rs | 15 +- src/lune/builtins/serde/mod.rs | 2 +- src/lune/builtins/stdio/mod.rs | 14 +- src/lune/builtins/task/mod.rs | 135 +-- src/lune/builtins/task/tof.rs | 30 - src/lune/globals/mod.rs | 2 +- src/lune/globals/require/alias.rs | 5 +- src/lune/globals/require/builtin.rs | 6 +- src/lune/globals/require/context.rs | 78 +- src/lune/globals/require/mod.rs | 17 +- src/lune/globals/require/path.rs | 27 +- src/lune/mod.rs | 59 +- src/lune/scheduler/impl_async.rs | 138 --- src/lune/scheduler/impl_runner.rs | 265 ------ src/lune/scheduler/impl_threads.rs | 185 ---- src/lune/scheduler/message.rs | 98 --- src/lune/scheduler/mod.rs | 120 --- src/lune/scheduler/state.rs | 176 ---- src/lune/scheduler/thread.rs | 105 --- src/lune/scheduler/traits.rs | 118 --- src/lune/util/futures.rs | 18 - src/lune/util/mod.rs | 1 - src/lune/util/table_builder.rs | 11 +- src/tests.rs | 2 +- tests/net/socket/basic.luau | 28 + tests/require/tests/async_background.luau | 51 -- 44 files changed, 1489 insertions(+), 2525 deletions(-) delete mode 100644 src/lune/builtins/net/processing.rs delete mode 100644 src/lune/builtins/net/server.rs create mode 100644 src/lune/builtins/net/server/keys.rs create mode 100644 src/lune/builtins/net/server/mod.rs create mode 100644 src/lune/builtins/net/server/request.rs rename src/lune/builtins/net/{ => server}/response.rs (53%) create mode 100644 src/lune/builtins/net/server/service.rs delete mode 100644 src/lune/builtins/task/tof.rs delete mode 100644 src/lune/scheduler/impl_async.rs delete mode 100644 src/lune/scheduler/impl_runner.rs delete mode 100644 src/lune/scheduler/impl_threads.rs delete mode 100644 src/lune/scheduler/message.rs delete mode 100644 src/lune/scheduler/mod.rs delete mode 100644 src/lune/scheduler/state.rs delete mode 100644 src/lune/scheduler/thread.rs delete mode 100644 src/lune/scheduler/traits.rs delete mode 100644 src/lune/util/futures.rs create mode 100644 tests/net/socket/basic.luau delete mode 100644 tests/require/tests/async_background.luau diff --git a/Cargo.lock b/Cargo.lock index 324c99d..710cf51 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -58,9 +58,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.7" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cd2405b3ac1faab2990b74d728624cd9fd115651fcecc7c2d8daf01376275ba" +checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" dependencies = [ "anstyle", "anstyle-parse", @@ -72,9 +72,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.4" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" [[package]] name = "anstyle-parse" @@ -129,10 +129,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] -name = "async-compression" -version = "0.4.5" +name = "async-channel" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc2d0cfb2a7388d34f590e76686704c494ed7aaceed62ee1ba35cbf363abc2a5" +checksum = "f28243a43d821d11341ab73c80bed182dc015c514b951616cf79bd4af39af0c3" +dependencies = [ + "concurrent-queue", + "event-listener 5.0.0", + "event-listener-strategy 0.5.0", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-compression" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a116f46a969224200a0a97f29cfd4c50e7534e4b4826bd23ea2c3c533039c82c" dependencies = [ "brotli", "flate2", @@ -142,6 +155,37 @@ dependencies = [ "tokio", ] +[[package]] +name = "async-executor" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ae5ebefcc48e7452b4987947920dac9450be1110cadf34d1b8c116bdbaf97c" +dependencies = [ + "async-lock", + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "slab", +] + +[[package]] +name = "async-lock" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d034b430882f8381900d3fe6f0aaa3ad94f2cb4ac519b429692a1bc2dda4ae7b" +dependencies = [ + "event-listener 4.0.3", + "event-listener-strategy 0.4.0", + "pin-project-lite", +] + +[[package]] +name = "async-task" +version = "4.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799" + [[package]] name = "async-trait" version = "0.1.77" @@ -153,6 +197,12 @@ dependencies = [ "syn 2.0.48", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.1.0" @@ -200,9 +250,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.4.1" +version = "2.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" [[package]] name = "blake2b_simd" @@ -237,6 +287,22 @@ dependencies = [ "generic-array", ] +[[package]] +name = "blocking" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a37913e8dc4ddcc604f0c6d3bf2887c995153af3611de9e23c352b44c1b9118" +dependencies = [ + "async-channel", + "async-lock", + "async-task", + "fastrand", + "futures-io", + "futures-lite", + "piper", + "tracing", +] + [[package]] name = "brotli" version = "3.4.0" @@ -309,16 +375,16 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.31" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +checksum = "5bc015644b92d5890fab7489e49d21f879d5c990186827d42ec511919404f38b" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "wasm-bindgen", - "windows-targets 0.48.5", + "windows-targets 0.52.0", ] [[package]] @@ -338,9 +404,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.16" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58e54881c004cec7895b0068a0a954cd5d62da01aef83fa35b1e594497bf5445" +checksum = "80c21025abd42669a92efc996ef13cfb2c5c627858421ea58d5c3b331a6c134f" dependencies = [ "clap_builder", "clap_derive", @@ -348,9 +414,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.16" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59cb82d7f531603d2fd1f507441cdd35184fa81beff7bd489570de7f773460bb" +checksum = "458bf1f341769dfcf849846f65dffdf9146daa56bcd2a47cb4e1de9915567c99" dependencies = [ "anstream", "anstyle", @@ -360,9 +426,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.4.7" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" dependencies = [ "heck", "proc-macro2", @@ -372,15 +438,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "clipboard-win" -version = "5.0.0" +version = "5.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c57002a5d9be777c1ef967e33674dac9ebd310d8893e4e3437b14d5f0f6372cc" +checksum = "3ec832972fefb8cf9313b45a0d1945e29c9c251f1d4c6eafc5fe2124c02d2e81" dependencies = [ "error-code", ] @@ -391,6 +457,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "concurrent-queue" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "console" version = "0.15.8" @@ -422,6 +497,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "cookie" version = "0.15.2" @@ -497,6 +578,19 @@ dependencies = [ "powerfmt", ] +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version 0.4.0", + "syn 1.0.109", +] + [[package]] name = "dialoguer" version = "0.11.0" @@ -566,9 +660,9 @@ checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" [[package]] name = "either" -version = "1.9.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" [[package]] name = "encode_unicode" @@ -592,16 +686,26 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" [[package]] -name = "env_logger" -version = "0.10.1" +name = "env_filter" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece" +checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" dependencies = [ - "humantime", - "is-terminal", "log", "regex", - "termcolor", +] + +[[package]] +name = "env_logger" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05e7cf40684ae96ade6232ed84582f40ce0a66efcd43a5117aef610534f8e0b8" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", ] [[package]] @@ -635,6 +739,48 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "281e452d3bad4005426416cdba5ccfd4f5c1280e10099e21db27f7c1c28347fc" +[[package]] +name = "event-listener" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b215c49b2b248c855fb73579eb1f4f26c38ffdc12973e20e07b91d78d5646e" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b72557800024fabbaa2449dd4bf24e37b93702d457a4d4f2b0dd1f0f039f20c1" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "958e4d70b6d5e81971bebec42271ec641e7ff4e170a6fa605f2b8a8b65cb97d3" +dependencies = [ + "event-listener 4.0.3", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "feedafcaa9b749175d5ac357452a9d41ea2911da598fde46ce1fe02c37751291" +dependencies = [ + "event-listener 5.0.0", + "pin-project-lite", +] + [[package]] name = "fastrand" version = "2.0.1" @@ -692,6 +838,25 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-lite" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445ba825b27408685aaecefd65178908c36c6e96aaf6d8599419d46e624192ba" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-macro" version = "0.3.30" @@ -782,16 +947,35 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "h2" -version = "0.3.23" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b553656127a00601c8ae5590fcfdc118e4083a7924b6cf4ffc1ea4b99dc429d7" +checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" dependencies = [ "bytes", "fnv", "futures-core", "futures-sink", "futures-util", - "http", + "http 0.2.11", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "h2" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31d030e59af851932b72ceebadf4a2b5986dba4c3b99dd2493f8273a0f151943" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 1.0.0", "indexmap", "slab", "tokio", @@ -813,9 +997,9 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" -version = "0.3.3" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +checksum = "d0c62115964e08cb8039170eb33c1d0e2388a256930279edca206fff675f82c3" [[package]] name = "home" @@ -837,6 +1021,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b32afd38673a8016f7c9ae69e5af41a58f81b1d31689040f2f1959594ce194ea" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http-body" version = "0.4.6" @@ -844,7 +1039,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", - "http", + "http 0.2.11", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +dependencies = [ + "bytes", + "http 1.0.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cb79eb393015dadd30fc252023adb0b2400a0caee0fa2a077e6e21a551e840" +dependencies = [ + "bytes", + "futures-util", + "http 1.0.0", + "http-body 1.0.0", "pin-project-lite", ] @@ -876,9 +1094,9 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2", - "http", - "http-body", + "h2 0.3.24", + "http 0.2.11", + "http-body 0.4.6", "httparse", "httpdate", "itoa", @@ -890,6 +1108,26 @@ dependencies = [ "want", ] +[[package]] +name = "hyper" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5aa53871fc917b1a9ed87b683a5d86db645e23acb32c2e0785a353e522fb75" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2 0.4.2", + "http 1.0.0", + "http-body 1.0.0", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "tokio", + "want", +] + [[package]] name = "hyper-rustls" version = "0.24.2" @@ -897,20 +1135,22 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", - "http", - "hyper", - "rustls", + "http 0.2.11", + "hyper 0.14.28", + "rustls 0.21.10", "tokio", - "tokio-rustls", + "tokio-rustls 0.24.1", ] [[package]] name = "hyper-tungstenite" -version = "0.11.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc7dcb1ab67cd336f468a12491765672e61a3b6b148634dbfe2fe8acd3fe7d9" +checksum = "7a343d17fe7885302ed7252767dc7bb83609a874b6ff581142241ec4b73957ad" dependencies = [ - "hyper", + "http-body-util", + "hyper 1.1.0", + "hyper-util", "pin-project-lite", "tokio", "tokio-tungstenite", @@ -918,10 +1158,30 @@ dependencies = [ ] [[package]] -name = "iana-time-zone" -version = "0.1.59" +name = "hyper-util" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539" +checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.0.0", + "http-body 1.0.0", + "hyper 1.1.0", + "pin-project-lite", + "socket2", + "tokio", + "tower", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -972,9 +1232,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.1.0" +version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177" dependencies = [ "equivalent", "hashbrown", @@ -986,22 +1246,11 @@ version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" -[[package]] -name = "is-terminal" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455" -dependencies = [ - "hermit-abi", - "rustix", - "windows-sys 0.52.0", -] - [[package]] name = "itertools" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" dependencies = [ "either", ] @@ -1014,9 +1263,9 @@ checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "js-sys" -version = "0.3.67" +version = "0.3.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1" +checksum = "406cda4b368d531c842222cf9d2600a9a4acce8d29423695379c6868a143a9ee" dependencies = [ "wasm-bindgen", ] @@ -1029,9 +1278,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.152" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libloading" @@ -1049,7 +1298,7 @@ version = "0.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.4.2", "libc", "redox_syscall 0.4.1", ] @@ -1065,9 +1314,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" [[package]] name = "lock_api" @@ -1087,9 +1336,9 @@ checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "luau0-src" -version = "0.7.11+luau606" +version = "0.8.1+luau611" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07ffc4945ee953a33cb2b331e00b19e11275fc105c8ac8a977c810597d790f08" +checksum = "32e9900dbff55d80afdd8c6b775e1ff657587aef6894cc8680a60449504c1279" dependencies = [ "cc", ] @@ -1101,6 +1350,7 @@ dependencies = [ "anyhow", "async-compression", "async-trait", + "blocking", "chrono", "chrono_lc", "clap", @@ -1111,12 +1361,16 @@ dependencies = [ "env_logger", "futures-util", "glam", - "hyper", + "http 1.0.0", + "http-body-util", + "hyper 1.1.0", "hyper-tungstenite", + "hyper-util", "include_dir", "itertools", "lz4_flex", "mlua", + "mlua-luau-scheduler", "once_cell", "os_str_bytes", "path-clean", @@ -1196,9 +1450,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" dependencies = [ "adler", ] @@ -1216,12 +1470,13 @@ dependencies = [ [[package]] name = "mlua" -version = "0.9.4" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "069264935e816c85884b99e88c8b408d6d92e40ae8760f726c983526a53546b5" +checksum = "868d02cb5eb97761bbf6bd6922c1c7a88b8ea252bbf43bd8350a0bf8497a1fc0" dependencies = [ "bstr", "erased-serde", + "futures-util", "libloading", "mlua-sys", "num-traits", @@ -1232,10 +1487,27 @@ dependencies = [ ] [[package]] -name = "mlua-sys" -version = "0.5.0" +name = "mlua-luau-scheduler" +version = "0.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4655631a02e3739d014951291ecfa08db49c4da3f7f8c6f3931ed236af5dd78e" +checksum = "a13eabdbc57fa38cf0b604d98ce3431573c79a964aac56e09c16c240d36cb1bf" +dependencies = [ + "async-executor", + "blocking", + "concurrent-queue", + "derive_more", + "event-listener 4.0.3", + "futures-lite", + "mlua", + "rustc-hash", + "tracing", +] + +[[package]] +name = "mlua-sys" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2847b42764435201d8cbee1f517edb79c4cca4181877b90047587c89e1b7bce4" dependencies = [ "cc", "cfg-if", @@ -1258,7 +1530,7 @@ version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2eb04e9c688eff1c89d72b407f168cf79bb9e867a9d3323ed6c01519eb9cc053" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.4.2", "cfg-if", "libc", ] @@ -1274,20 +1546,25 @@ dependencies = [ ] [[package]] -name = "num-integer" -version = "0.1.45" +name = "num-conv" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg", "num-traits", ] [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" dependencies = [ "autocfg", ] @@ -1334,9 +1611,9 @@ dependencies = [ [[package]] name = "os_str_bytes" -version = "6.6.1" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" +checksum = "7ac44c994af577c799b1b4bd80dc214701e349873ad894d6cdf96f4f7526e0b9" dependencies = [ "memchr", ] @@ -1347,6 +1624,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "parking" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" + [[package]] name = "parking_lot" version = "0.12.1" @@ -1396,18 +1679,18 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" dependencies = [ "proc-macro2", "quote", @@ -1427,10 +1710,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] -name = "pkg-config" -version = "0.3.28" +name = "piper" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a" +checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + +[[package]] +name = "pkg-config" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb" [[package]] name = "plist" @@ -1443,7 +1737,7 @@ dependencies = [ "line-wrap", "quick-xml", "serde", - "time 0.3.31", + "time 0.3.34", ] [[package]] @@ -1466,27 +1760,27 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.76" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" dependencies = [ "unicode-ident", ] [[package]] name = "profiling" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d135ede8821cf6376eb7a64148901e1690b788c11ae94dc297ae917dbc91dc0e" +checksum = "0f0f7f43585c34e4fdd7497d746bc32e14458cf11c69341cc0587b1d825dde42" dependencies = [ "profiling-procmacros", ] [[package]] name = "profiling-procmacros" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b322d7d65c1ab449be3c890fcbd0db6e1092d0dd05d79dba2dd28032cebeb05" +checksum = "ce97fecd27bc49296e5e20518b5a1bb54a14f7d5fe6228bc9686ee2a74915cc8" dependencies = [ "quote", "syn 2.0.48", @@ -1552,9 +1846,9 @@ dependencies = [ [[package]] name = "rbx_binary" -version = "0.7.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad50c13afe91296dad6508ea7e29f4b665fa56cb664ad01eaf8fdbd3da69d5e1" +checksum = "6314dd6bf5c21d0598cdb53cf5d241aa643ba41da8b8abf7402b4a35096f03f6" dependencies = [ "log", "lz4", @@ -1582,9 +1876,9 @@ dependencies = [ [[package]] name = "rbx_dom_weak" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843a2e0e1446623625943f7228d9d4b5cf3883017e3964733600682506864b34" +checksum = "9b67b56bac99849c2e3c57547b036927f71c57cf7f4d900d04e3e4ee774ec316" dependencies = [ "rbx_types", "serde", @@ -1592,9 +1886,9 @@ dependencies = [ [[package]] name = "rbx_reflection" -version = "4.4.0" +version = "4.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41e762dfca3217d2d37da631de2fa0d1616edaa61a0a2633263d5d3305baf8c3" +checksum = "0d41509c991b53a7276a746a795eae2b9204f398164920f61976995b47fe1722" dependencies = [ "rbx_types", "serde", @@ -1603,9 +1897,9 @@ dependencies = [ [[package]] name = "rbx_reflection_database" -version = "0.2.9+roblox-596" +version = "0.2.10+roblox-607" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b18f088a2b4aa66324ec97b5b6ffacb53188aef19f3497d95d6a1d1dbb28e66" +checksum = "12e20c06fa41f7aadc79005c8354f592b2c2f4d0c61e1080ed5718dafc30aea0" dependencies = [ "lazy_static", "rbx_reflection", @@ -1615,9 +1909,9 @@ dependencies = [ [[package]] name = "rbx_types" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a991523e3ad5f43a4d121cb4a1e5bc23f7826bb4a1db5aa51e94f1073150ec" +checksum = "7ca23bfd469d067d81ef14f65fe09aeddc25abcf576a889d1a7664fe021cf18c" dependencies = [ "base64 0.13.1", "bitflags 1.3.2", @@ -1630,9 +1924,9 @@ dependencies = [ [[package]] name = "rbx_xml" -version = "0.13.2" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc87343301303ff0510903fb7eb3dbd1c75bdb6ab780fea6091bdc3f58b5829f" +checksum = "f8c03f95500961c32340791d1fabd4587f6873bdbff077ecca6ae32db7960dea" dependencies = [ "base64 0.13.1", "log", @@ -1681,13 +1975,13 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.2" +version = "1.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.3", + "regex-automata 0.4.5", "regex-syntax 0.8.2", ] @@ -1702,9 +1996,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.3" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" dependencies = [ "aho-corasick", "memchr", @@ -1725,19 +2019,19 @@ checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "reqwest" -version = "0.11.23" +version = "0.11.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b1ae8d9ac08420c66222fb9096fc5de435c3c48542bc5336c51892cffafb41" +checksum = "c6920094eb85afde5e4a138be3f2de8bbdf28000f0029e72c45025a56b042251" dependencies = [ "base64 0.21.7", "bytes", "encoding_rs", "futures-core", "futures-util", - "h2", - "http", - "http-body", - "hyper", + "h2 0.3.24", + "http 0.2.11", + "http-body 0.4.6", + "hyper 0.14.28", "hyper-rustls", "ipnet", "js-sys", @@ -1746,20 +2040,21 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls", + "rustls 0.21.10", "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", + "sync_wrapper", "system-configuration", "tokio", - "tokio-rustls", + "tokio-rustls 0.24.1", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "webpki-roots", + "webpki-roots 0.25.4", "winreg 0.50.0", ] @@ -1829,16 +2124,25 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" dependencies = [ - "semver", + "semver 0.9.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver 1.0.21", ] [[package]] name = "rustix" -version = "0.38.30" +version = "0.38.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca" +checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.4.2", "errno", "libc", "linux-raw-sys", @@ -1853,10 +2157,24 @@ checksum = "f9d5a6813c0759e4609cd494e8e725babae6a2ca7b62a5536a13daaec6fcb7ba" dependencies = [ "log", "ring", - "rustls-webpki", + "rustls-webpki 0.101.7", "sct", ] +[[package]] +name = "rustls" +version = "0.22.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e87c9956bd9807afa1f77e0f7594af32566e830e088a5576d27c5b6f30f49d41" +dependencies = [ + "log", + "ring", + "rustls-pki-types", + "rustls-webpki 0.102.2", + "subtle", + "zeroize", +] + [[package]] name = "rustls-pemfile" version = "1.0.4" @@ -1866,6 +2184,12 @@ dependencies = [ "base64 0.21.7", ] +[[package]] +name = "rustls-pki-types" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a716eb65e3158e90e17cd93d855216e27bde02745ab842f2cab4a39dba1bacf" + [[package]] name = "rustls-webpki" version = "0.101.7" @@ -1876,13 +2200,24 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustls-webpki" +version = "0.102.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustyline" version = "13.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02a2d683a4ac90aeef5b1013933f6d977bd37d51ff3f4dad829d4931a7e6be86" dependencies = [ - "bitflags 2.4.1", + "bitflags 2.4.2", "cfg-if", "clipboard-win", "fd-lock", @@ -1944,6 +2279,12 @@ dependencies = [ "semver-parser", ] +[[package]] +name = "semver" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" + [[package]] name = "semver-parser" version = "0.7.0" @@ -1952,9 +2293,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.195" +version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" +checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32" dependencies = [ "serde_derive", ] @@ -1971,9 +2312,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.195" +version = "1.0.196" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" +checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67" dependencies = [ "proc-macro2", "quote", @@ -1982,9 +2323,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.111" +version = "1.0.113" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" +checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79" dependencies = [ "indexmap", "itoa", @@ -2015,9 +2356,9 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.9.30" +version = "0.9.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1bf28c79a99f70ee1f1d83d10c875d2e70618417fda01ad1785e027579d9d38" +checksum = "adf8a49373e98a4c5f0ceb5d05aa7c648d75f63774981ed95b7c7443bbd50c6e" dependencies = [ "indexmap", "itoa", @@ -2087,9 +2428,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.12.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2593d31f82ead8df961d8bd23a64c2ccf2eb5dd34b0a34bfb4dd54011c72009e" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" [[package]] name = "socket2" @@ -2129,7 +2470,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d022496b16281348b52d0e30ae99e01a73d737b2f45d38fed4edf79f9325a1d5" dependencies = [ "discard", - "rustc_version", + "rustc_version 0.2.3", "stdweb-derive", "stdweb-internal-macros", "stdweb-internal-runtime", @@ -2173,9 +2514,15 @@ checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" [[package]] name = "strsim" -version = "0.10.0" +version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "syn" @@ -2199,6 +2546,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "system-configuration" version = "0.5.1" @@ -2222,40 +2575,30 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.9.0" +version = "3.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01ce4141aa927a6d1bd34a041795abd0db1cccba5d5f24b009f694bdf3a1f3fa" +checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67" dependencies = [ "cfg-if", "fastrand", - "redox_syscall 0.4.1", "rustix", "windows-sys 0.52.0", ] -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - [[package]] name = "thiserror" -version = "1.0.56" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" +checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.56" +version = "1.0.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" +checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" dependencies = [ "proc-macro2", "quote", @@ -2289,16 +2632,17 @@ dependencies = [ [[package]] name = "time" -version = "0.3.31" +version = "0.3.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f657ba42c3f86e7680e53c8cd3af8abbe56b5491790b46e22e19c0d57463583e" +checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" dependencies = [ "deranged", "itoa", + "num-conv", "powerfmt", "serde", "time-core", - "time-macros 0.2.16", + "time-macros 0.2.17", ] [[package]] @@ -2319,10 +2663,11 @@ dependencies = [ [[package]] name = "time-macros" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26197e33420244aeb70c3e8c78376ca46571bc4e701e4791c2cd9f57dcb3a43f" +checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" dependencies = [ + "num-conv", "time-core", ] @@ -2356,9 +2701,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.35.1" +version = "1.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c89b4efa943be685f629b149f53829423f8f5531ea21249408e8e2f8671ec104" +checksum = "61285f6515fa018fb2d1e46eb21223fff441ee8db5d0f1435e8ab4f5cdb80931" dependencies = [ "backtrace", "bytes", @@ -2391,23 +2736,35 @@ version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls", + "rustls 0.21.10", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" +dependencies = [ + "rustls 0.22.2", + "rustls-pki-types", "tokio", ] [[package]] name = "tokio-tungstenite" -version = "0.20.1" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" +checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" dependencies = [ "futures-util", "log", - "rustls", + "rustls 0.22.2", + "rustls-pki-types", "tokio", - "tokio-rustls", + "tokio-rustls 0.25.0", "tungstenite", - "webpki-roots", + "webpki-roots 0.26.1", ] [[package]] @@ -2426,9 +2783,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.8" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35" +checksum = "9a9aad4a3066010876e8dcf5a8a06e70a558751117a145c6ce2b82c2e2054290" dependencies = [ "indexmap", "serde", @@ -2448,9 +2805,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.21.0" +version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" +checksum = "0c9ffdf896f8daaabf9b66ba8e77ea1ed5ed0f72821b398aba62352e95062951" dependencies = [ "indexmap", "serde", @@ -2459,6 +2816,28 @@ dependencies = [ "winnow", ] +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + [[package]] name = "tower-service" version = "0.3.2" @@ -2471,6 +2850,7 @@ version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -2534,18 +2914,19 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tungstenite" -version = "0.20.1" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" +checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" dependencies = [ "byteorder 1.5.0", "bytes", "data-encoding", - "http", + "http 1.0.0", "httparse", "log", "rand", - "rustls", + "rustls 0.22.2", + "rustls-pki-types", "sha1 0.10.6", "thiserror", "url", @@ -2570,9 +2951,9 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-bidi" -version = "0.3.14" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f2528f27a9eb2b21e69c95319b30bd0efd85d09c379741b0f78ea1d86be2416" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" @@ -2591,9 +2972,9 @@ dependencies = [ [[package]] name = "unicode-segmentation" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "unicode-width" @@ -2687,9 +3068,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.90" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" +checksum = "c1e124130aee3fb58c5bdd6b639a0509486b0338acaaae0c84a5124b0f588b7f" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2697,9 +3078,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.90" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" +checksum = "c9e7e1900c352b609c8488ad12639a311045f40a35491fb69ba8c12f758af70b" dependencies = [ "bumpalo", "log", @@ -2712,9 +3093,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.40" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bde2032aeb86bdfaecc8b261eef3cba735cc426c1f3a3416d1e0791be95fc461" +checksum = "877b9c3f61ceea0e56331985743b13f3d25c406a7098d45180fb5f09bc19ed97" dependencies = [ "cfg-if", "js-sys", @@ -2724,9 +3105,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.90" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" +checksum = "b30af9e2d358182b5c7449424f017eba305ed32a7010509ede96cdc4696c46ed" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2734,9 +3115,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.90" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" +checksum = "642f325be6301eb8107a83d12a8ac6c1e1c54345a7ef1a9261962dfefda09e66" dependencies = [ "proc-macro2", "quote", @@ -2747,15 +3128,15 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.90" +version = "0.2.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" +checksum = "4f186bd2dcf04330886ce82d6f33dd75a7bfcf69ecf5763b89fcde53b6ac9838" [[package]] name = "web-sys" -version = "0.3.67" +version = "0.3.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed" +checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446" dependencies = [ "js-sys", "wasm-bindgen", @@ -2763,9 +3144,18 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.25.3" +version = "0.25.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + +[[package]] +name = "webpki-roots" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009" +dependencies = [ + "rustls-pki-types", +] [[package]] name = "winapi" @@ -2941,9 +3331,9 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] name = "winnow" -version = "0.5.34" +version = "0.5.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7cf47b659b318dccbd69cc4797a39ae128f533dce7902a1096044d1967b9c16" +checksum = "5389a154b01683d28c77f8f68f49dea75f0a4da32557a58f68ee51ebba472d29" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index 9a07893..f1ed8ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,11 +79,19 @@ urlencoding = "2.1" ### RUNTIME +blocking = "1.5" tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } -mlua = { version = "0.9.1", features = ["luau", "luau-jit", "serialize"] } tokio = { version = "1.24", features = ["full", "tracing"] } -os_str_bytes = { version = "6.4", features = ["conversions"] } +os_str_bytes = { version = "7.0", features = ["conversions"] } + +mlua-luau-scheduler = { version = "0.0.2" } +mlua = { version = "0.9.6", features = [ + "luau", + "luau-jit", + "async", + "serialize", +] } ### SERDE @@ -101,12 +109,17 @@ toml = { version = "0.8", features = ["preserve_order"] } ### NET -hyper = { version = "0.14", features = ["full"] } -hyper-tungstenite = { version = "0.11" } +hyper = { version = "1.1", features = ["full"] } +hyper-util = { version = "0.1", features = ["full"] } +http = "1.0" +http-body-util = { version = "0.1" } +hyper-tungstenite = { version = "0.13" } + reqwest = { version = "0.11", default-features = false, features = [ "rustls-tls", ] } -tokio-tungstenite = { version = "0.20", features = ["rustls-tls-webpki-roots"] } + +tokio-tungstenite = { version = "0.21", features = ["rustls-tls-webpki-roots"] } ### DATETIME chrono = "0.4" @@ -115,7 +128,7 @@ chrono_lc = "0.1" ### CLI anyhow = { optional = true, version = "1.0" } -env_logger = { optional = true, version = "0.10" } +env_logger = { optional = true, version = "0.11" } itertools = { optional = true, version = "0.12" } clap = { optional = true, version = "4.1", features = ["derive"] } include_dir = { optional = true, version = "0.7", features = ["glob"] } diff --git a/src/lune/builtins/fs/mod.rs b/src/lune/builtins/fs/mod.rs index 210608d..2c7b9fb 100644 --- a/src/lune/builtins/fs/mod.rs +++ b/src/lune/builtins/fs/mod.rs @@ -14,7 +14,7 @@ use copy::copy; use metadata::FsMetadata; use options::FsWriteOptions; -pub fn create(lua: &'static Lua) -> LuaResult { +pub fn create(lua: &Lua) -> LuaResult { TableBuilder::new(lua)? .with_async_function("readFile", fs_read_file)? .with_async_function("readDir", fs_read_dir)? diff --git a/src/lune/builtins/mod.rs b/src/lune/builtins/mod.rs index f9aa101..51d0290 100644 --- a/src/lune/builtins/mod.rs +++ b/src/lune/builtins/mod.rs @@ -28,10 +28,7 @@ pub enum LuneBuiltin { Roblox, } -impl<'lua> LuneBuiltin -where - 'lua: 'static, // FIXME: Remove static lifetime bound here when builtin libraries no longer need it -{ +impl LuneBuiltin { pub fn name(&self) -> &'static str { match self { Self::DateTime => "datetime", @@ -47,7 +44,7 @@ where } } - pub fn create(&self, lua: &'lua Lua) -> LuaResult> { + pub fn create<'lua>(&self, lua: &'lua Lua) -> LuaResult> { let res = match self { Self::DateTime => datetime::create(lua), Self::Fs => fs::create(lua), diff --git a/src/lune/builtins/net/client.rs b/src/lune/builtins/net/client.rs index 23636ff..5eb2527 100644 --- a/src/lune/builtins/net/client.rs +++ b/src/lune/builtins/net/client.rs @@ -2,8 +2,14 @@ use std::str::FromStr; use mlua::prelude::*; -use hyper::{header::HeaderName, http::HeaderValue, HeaderMap}; -use reqwest::{IntoUrl, Method, RequestBuilder}; +use reqwest::header::{HeaderMap, HeaderName, HeaderValue, CONTENT_ENCODING}; + +use crate::lune::{ + builtins::serde::compress_decompress::{decompress, CompressDecompressFormat}, + util::TableBuilder, +}; + +use super::{config::RequestConfig, util::header_map_to_table}; const REGISTRY_KEY: &str = "NetClient"; @@ -35,16 +41,19 @@ impl NetClientBuilder { pub fn build(self) -> LuaResult { let client = self.builder.build().into_lua_err()?; - Ok(NetClient(client)) + Ok(NetClient { inner: client }) } } #[derive(Debug, Clone)] -pub struct NetClient(reqwest::Client); +pub struct NetClient { + inner: reqwest::Client, +} impl NetClient { - pub fn request(&self, method: Method, url: U) -> RequestBuilder { - self.0.request(method, url) + pub fn from_registry(lua: &Lua) -> Self { + lua.named_registry_value(REGISTRY_KEY) + .expect("Failed to get NetClient from lua registry") } pub fn into_registry(self, lua: &Lua) { @@ -52,16 +61,68 @@ impl NetClient { .expect("Failed to store NetClient in lua registry"); } - pub fn from_registry(lua: &Lua) -> Self { - lua.named_registry_value(REGISTRY_KEY) - .expect("Failed to get NetClient from lua registry") + pub async fn request(&self, config: RequestConfig) -> LuaResult { + // Create and send the request + let mut request = self.inner.request(config.method, config.url); + for (query, values) in config.query { + request = request.query( + &values + .iter() + .map(|v| (query.as_str(), v)) + .collect::>(), + ); + } + for (header, values) in config.headers { + for value in values { + request = request.header(header.as_str(), value); + } + } + let res = request + .body(config.body.unwrap_or_default()) + .send() + .await + .into_lua_err()?; + + // Extract status, headers + let res_status = res.status().as_u16(); + let res_status_text = res.status().canonical_reason(); + let res_headers = res.headers().clone(); + + // Read response bytes + let mut res_bytes = res.bytes().await.into_lua_err()?.to_vec(); + let mut res_decompressed = false; + + // Check for extra options, decompression + if config.options.decompress { + let decompress_format = res_headers + .iter() + .find(|(name, _)| { + name.as_str() + .eq_ignore_ascii_case(CONTENT_ENCODING.as_str()) + }) + .and_then(|(_, value)| value.to_str().ok()) + .and_then(CompressDecompressFormat::detect_from_header_str); + if let Some(format) = decompress_format { + res_bytes = decompress(format, res_bytes).await?; + res_decompressed = true; + } + } + + Ok(NetClientResponse { + ok: (200..300).contains(&res_status), + status_code: res_status, + status_message: res_status_text.unwrap_or_default().to_string(), + headers: res_headers, + body: res_bytes, + body_decompressed: res_decompressed, + }) } } impl LuaUserData for NetClient {} -impl<'lua> FromLua<'lua> for NetClient { - fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult { +impl FromLua<'_> for NetClient { + fn from_lua(value: LuaValue, _: &Lua) -> LuaResult { if let LuaValue::UserData(ud) = value { if let Ok(ctx) = ud.borrow::() { return Ok(ctx.clone()); @@ -71,10 +132,34 @@ impl<'lua> FromLua<'lua> for NetClient { } } -impl<'lua> From<&'lua Lua> for NetClient { - fn from(value: &'lua Lua) -> Self { +impl From<&Lua> for NetClient { + fn from(value: &Lua) -> Self { value .named_registry_value(REGISTRY_KEY) .expect("Missing require context in lua registry") } } + +pub struct NetClientResponse { + ok: bool, + status_code: u16, + status_message: String, + headers: HeaderMap, + body: Vec, + body_decompressed: bool, +} + +impl NetClientResponse { + pub fn into_lua_table(self, lua: &Lua) -> LuaResult { + TableBuilder::new(lua)? + .with_value("ok", self.ok)? + .with_value("statusCode", self.status_code)? + .with_value("statusMessage", self.status_message)? + .with_value( + "headers", + header_map_to_table(lua, self.headers, self.body_decompressed)?, + )? + .with_value("body", lua.create_string(&self.body)?)? + .build_readonly() + } +} diff --git a/src/lune/builtins/net/config.rs b/src/lune/builtins/net/config.rs index 030288e..5d02d2b 100644 --- a/src/lune/builtins/net/config.rs +++ b/src/lune/builtins/net/config.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::{collections::HashMap, net::Ipv4Addr}; use mlua::prelude::*; @@ -6,6 +6,18 @@ use reqwest::Method; use super::util::table_to_hash_map; +const DEFAULT_IP_ADDRESS: Ipv4Addr = Ipv4Addr::new(127, 0, 0, 1); + +const WEB_SOCKET_UPDGRADE_REQUEST_HANDLER: &str = r#" +return { + status = 426, + body = "Upgrade Required", + headers = { + Upgrade = "websocket", + }, +} +"#; + // Net request config #[derive(Debug, Clone)] @@ -21,28 +33,29 @@ impl Default for RequestConfigOptions { impl<'lua> FromLua<'lua> for RequestConfigOptions { fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult { - // Nil means default options, table means custom options if let LuaValue::Nil = value { - return Ok(Self::default()); + // Nil means default options + Ok(Self::default()) } else if let LuaValue::Table(tab) = value { - // Extract flags - let decompress = match tab.raw_get::<_, Option>("decompress") { + // Table means custom options + let decompress = match tab.get::<_, Option>("decompress") { Ok(decomp) => Ok(decomp.unwrap_or(true)), Err(_) => Err(LuaError::RuntimeError( "Invalid option value for 'decompress' in request config options".to_string(), )), }?; - return Ok(Self { decompress }); + Ok(Self { decompress }) + } else { + // Anything else is invalid + Err(LuaError::FromLuaConversionError { + from: value.type_name(), + to: "RequestConfigOptions", + message: Some(format!( + "Invalid request config options - expected table or nil, got {}", + value.type_name() + )), + }) } - // Anything else is invalid - Err(LuaError::FromLuaConversionError { - from: value.type_name(), - to: "RequestConfigOptions", - message: Some(format!( - "Invalid request config options - expected table or nil, got {}", - value.type_name() - )), - }) } } @@ -60,39 +73,38 @@ impl FromLua<'_> for RequestConfig { fn from_lua(value: LuaValue, lua: &Lua) -> LuaResult { // If we just got a string we assume its a GET request to a given url if let LuaValue::String(s) = value { - return Ok(Self { + Ok(Self { url: s.to_string_lossy().to_string(), method: Method::GET, query: HashMap::new(), headers: HashMap::new(), body: None, options: Default::default(), - }); - } - // If we got a table we are able to configure the entire request - if let LuaValue::Table(tab) = value { + }) + } else if let LuaValue::Table(tab) = value { + // If we got a table we are able to configure the entire request // Extract url - let url = match tab.raw_get::<_, LuaString>("url") { + let url = match tab.get::<_, LuaString>("url") { Ok(config_url) => Ok(config_url.to_string_lossy().to_string()), Err(_) => Err(LuaError::runtime("Missing 'url' in request config")), }?; // Extract method - let method = match tab.raw_get::<_, LuaString>("method") { + let method = match tab.get::<_, LuaString>("method") { Ok(config_method) => config_method.to_string_lossy().trim().to_ascii_uppercase(), Err(_) => "GET".to_string(), }; // Extract query - let query = match tab.raw_get::<_, LuaTable>("query") { + let query = match tab.get::<_, LuaTable>("query") { Ok(tab) => table_to_hash_map(tab, "query")?, Err(_) => HashMap::new(), }; // Extract headers - let headers = match tab.raw_get::<_, LuaTable>("headers") { + let headers = match tab.get::<_, LuaTable>("headers") { Ok(tab) => table_to_hash_map(tab, "headers")?, Err(_) => HashMap::new(), }; // Extract body - let body = match tab.raw_get::<_, LuaString>("body") { + let body = match tab.get::<_, LuaString>("body") { Ok(config_body) => Some(config_body.as_bytes().to_owned()), Err(_) => None, }; @@ -112,29 +124,30 @@ impl FromLua<'_> for RequestConfig { ))), }?; // Parse any extra options given - let options = match tab.raw_get::<_, LuaValue>("options") { + let options = match tab.get::<_, LuaValue>("options") { Ok(opts) => RequestConfigOptions::from_lua(opts, lua)?, Err(_) => RequestConfigOptions::default(), }; // All good, validated and we got what we need - return Ok(Self { + Ok(Self { url, method, query, headers, body, options, - }); - }; - // Anything else is invalid - Err(LuaError::FromLuaConversionError { - from: value.type_name(), - to: "RequestConfig", - message: Some(format!( - "Invalid request config - expected string or table, got {}", - value.type_name() - )), - }) + }) + } else { + // Anything else is invalid + Err(LuaError::FromLuaConversionError { + from: value.type_name(), + to: "RequestConfig", + message: Some(format!( + "Invalid request config - expected string or table, got {}", + value.type_name() + )), + }) + } } } @@ -142,54 +155,72 @@ impl FromLua<'_> for RequestConfig { #[derive(Debug)] pub struct ServeConfig<'a> { + pub address: Ipv4Addr, pub handle_request: LuaFunction<'a>, pub handle_web_socket: Option>, - pub address: Option>, } impl<'lua> FromLua<'lua> for ServeConfig<'lua> { fn from_lua(value: LuaValue<'lua>, lua: &'lua Lua) -> LuaResult { - let message = match &value { - LuaValue::Function(f) => { - return Ok(ServeConfig { - handle_request: f.clone(), - handle_web_socket: None, - address: None, + if let LuaValue::Function(f) = &value { + // Single function = request handler, rest is default + Ok(ServeConfig { + handle_request: f.clone(), + handle_web_socket: None, + address: DEFAULT_IP_ADDRESS, + }) + } else if let LuaValue::Table(t) = &value { + // Table means custom options + let address: Option = t.get("address")?; + let handle_request: Option = t.get("handleRequest")?; + let handle_web_socket: Option = t.get("handleWebSocket")?; + if handle_request.is_some() || handle_web_socket.is_some() { + let address: Ipv4Addr = match &address { + Some(addr) => { + let addr_str = addr.to_str()?; + + addr_str + .trim_start_matches("http://") + .trim_start_matches("https://") + .parse() + .map_err(|_e| LuaError::FromLuaConversionError { + from: value.type_name(), + to: "ServeConfig", + message: Some(format!( + "IP address format is incorrect - \ + expected an IP in the form 'http://0.0.0.0' or '0.0.0.0', \ + got '{addr_str}'" + )), + })? + } + None => DEFAULT_IP_ADDRESS, + }; + + Ok(Self { + address, + handle_request: handle_request.unwrap_or_else(|| { + lua.load(WEB_SOCKET_UPDGRADE_REQUEST_HANDLER) + .into_function() + .expect("Failed to create default http responder function") + }), + handle_web_socket, + }) + } else { + Err(LuaError::FromLuaConversionError { + from: value.type_name(), + to: "ServeConfig", + message: Some(String::from( + "Invalid serve config - expected table with 'handleRequest' or 'handleWebSocket' function", + )), }) } - LuaValue::Table(t) => { - let handle_request: Option = t.raw_get("handleRequest")?; - let handle_web_socket: Option = t.raw_get("handleWebSocket")?; - let address: Option = t.raw_get("address")?; - if handle_request.is_some() || handle_web_socket.is_some() { - return Ok(ServeConfig { - handle_request: handle_request.unwrap_or_else(|| { - let chunk = r#" - return { - status = 426, - body = "Upgrade Required", - headers = { - Upgrade = "websocket", - }, - } - "#; - lua.load(chunk) - .into_function() - .expect("Failed to create default http responder function") - }), - handle_web_socket, - address, - }); - } else { - Some("Missing handleRequest and / or handleWebSocket".to_string()) - } - } - _ => None, - }; - Err(LuaError::FromLuaConversionError { - from: value.type_name(), - to: "ServeConfig", - message, - }) + } else { + // Anything else is invalid + Err(LuaError::FromLuaConversionError { + from: value.type_name(), + to: "ServeConfig", + message: None, + }) + } } } diff --git a/src/lune/builtins/net/mod.rs b/src/lune/builtins/net/mod.rs index 78c3397..6253dec 100644 --- a/src/lune/builtins/net/mod.rs +++ b/src/lune/builtins/net/mod.rs @@ -1,36 +1,27 @@ -use std::net::Ipv4Addr; +#![allow(unused_variables)] use mlua::prelude::*; - -use hyper::header::CONTENT_ENCODING; - -use crate::lune::{scheduler::Scheduler, util::TableBuilder}; - -use self::{ - server::{bind_to_addr, create_server}, - util::header_map_to_table, -}; - -use super::serde::{ - compress_decompress::{decompress, CompressDecompressFormat}, - encode_decode::{EncodeDecodeConfig, EncodeDecodeFormat}, -}; +use mlua_luau_scheduler::LuaSpawnExt; mod client; mod config; -mod processing; -mod response; mod server; mod util; mod websocket; -use client::{NetClient, NetClientBuilder}; -use config::{RequestConfig, ServeConfig}; -use websocket::NetWebSocket; +use crate::lune::util::TableBuilder; -const DEFAULT_IP_ADDRESS: Ipv4Addr = Ipv4Addr::new(127, 0, 0, 1); +use self::{ + client::{NetClient, NetClientBuilder}, + config::{RequestConfig, ServeConfig}, + server::serve, + util::create_user_agent_header, + websocket::NetWebSocket, +}; -pub fn create(lua: &'static Lua) -> LuaResult { +use super::serde::encode_decode::{EncodeDecodeConfig, EncodeDecodeFormat}; + +pub fn create(lua: &Lua) -> LuaResult { NetClientBuilder::new() .headers(&[("User-Agent", create_user_agent_header())])? .build()? @@ -46,14 +37,6 @@ pub fn create(lua: &'static Lua) -> LuaResult { .build_readonly() } -fn create_user_agent_header() -> String { - let (github_owner, github_repo) = env!("CARGO_PKG_REPOSITORY") - .trim_start_matches("https://github.com/") - .split_once('/') - .unwrap(); - format!("{github_owner}-{github_repo}-cli") -} - fn net_json_encode<'lua>( lua: &'lua Lua, (val, pretty): (LuaValue<'lua>, Option), @@ -66,68 +49,14 @@ fn net_json_decode<'lua>(lua: &'lua Lua, json: LuaString<'lua>) -> LuaResult(lua: &'lua Lua, config: RequestConfig) -> LuaResult> -where - 'lua: 'static, // FIXME: Get rid of static lifetime bound here -{ - // Create and send the request +async fn net_request(lua: &Lua, config: RequestConfig) -> LuaResult { let client = NetClient::from_registry(lua); - let mut request = client.request(config.method, &config.url); - for (query, values) in config.query { - request = request.query( - &values - .iter() - .map(|v| (query.as_str(), v)) - .collect::>(), - ); - } - for (header, values) in config.headers { - for value in values { - request = request.header(header.as_str(), value); - } - } - let res = request - .body(config.body.unwrap_or_default()) - .send() - .await - .into_lua_err()?; - // Extract status, headers - let res_status = res.status().as_u16(); - let res_status_text = res.status().canonical_reason(); - let res_headers = res.headers().clone(); - // Read response bytes - let mut res_bytes = res.bytes().await.into_lua_err()?.to_vec(); - let mut res_decompressed = false; - // Check for extra options, decompression - if config.options.decompress { - let decompress_format = res_headers - .iter() - .find(|(name, _)| { - name.as_str() - .eq_ignore_ascii_case(CONTENT_ENCODING.as_str()) - }) - .and_then(|(_, value)| value.to_str().ok()) - .and_then(CompressDecompressFormat::detect_from_header_str); - if let Some(format) = decompress_format { - res_bytes = decompress(format, res_bytes).await?; - res_decompressed = true; - } - } - // Construct and return a readonly lua table with results - let res_headers_lua = header_map_to_table(lua, res_headers, res_decompressed)?; - TableBuilder::new(lua)? - .with_value("ok", (200..300).contains(&res_status))? - .with_value("statusCode", res_status)? - .with_value("statusMessage", res_status_text)? - .with_value("headers", res_headers_lua)? - .with_value("body", lua.create_string(&res_bytes)?)? - .build_readonly() + // NOTE: We spawn the request as a background task to free up resources in lua + let res = lua.spawn(async move { client.request(config).await }); + res.await?.into_lua_table(lua) } -async fn net_socket<'lua>(lua: &'lua Lua, url: String) -> LuaResult -where - 'lua: 'static, // FIXME: Get rid of static lifetime bound here -{ +async fn net_socket(lua: &Lua, url: String) -> LuaResult { let (ws, _) = tokio_tungstenite::connect_async(url).await.into_lua_err()?; NetWebSocket::new(ws).into_lua_table(lua) } @@ -135,32 +64,8 @@ where async fn net_serve<'lua>( lua: &'lua Lua, (port, config): (u16, ServeConfig<'lua>), -) -> LuaResult> -where - 'lua: 'static, // FIXME: Get rid of static lifetime bound here -{ - let sched = lua - .app_data_ref::<&Scheduler>() - .expect("Lua struct is missing scheduler"); - - let address: Ipv4Addr = match &config.address { - Some(addr) => { - let addr_str = addr.to_str()?; - - addr_str - .trim_start_matches("http://") - .trim_start_matches("https://") - .parse() - .map_err(|_e| LuaError::RuntimeError(format!( - "IP address format is incorrect (expected an IP in the form 'http://0.0.0.0' or '0.0.0.0', got '{addr_str}')" - )))? - } - None => DEFAULT_IP_ADDRESS, - }; - - let builder = bind_to_addr(address, port)?; - - create_server(lua, &sched, config, builder) +) -> LuaResult> { + serve(lua, port, config).await } fn net_url_encode<'lua>( diff --git a/src/lune/builtins/net/processing.rs b/src/lune/builtins/net/processing.rs deleted file mode 100644 index 837fd3e..0000000 --- a/src/lune/builtins/net/processing.rs +++ /dev/null @@ -1,101 +0,0 @@ -use std::sync::atomic::{AtomicUsize, Ordering}; - -use hyper::{body::to_bytes, Body, Request}; - -use mlua::prelude::*; - -use crate::lune::util::TableBuilder; - -static ID_COUNTER: AtomicUsize = AtomicUsize::new(0); - -#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] -pub(super) struct ProcessedRequestId(usize); - -impl ProcessedRequestId { - pub fn new() -> Self { - // NOTE: This may overflow after a couple billion requests, - // but that's completely fine... unless a request is still - // alive after billions more arrive and need to be handled - Self(ID_COUNTER.fetch_add(1, Ordering::Relaxed)) - } -} - -pub(super) struct ProcessedRequest { - pub id: ProcessedRequestId, - method: String, - path: String, - query: Vec<(String, String)>, - headers: Vec<(String, Vec)>, - body: Vec, -} - -impl ProcessedRequest { - pub async fn from_request(req: Request) -> LuaResult { - let (head, body) = req.into_parts(); - - // FUTURE: We can do extra processing like async decompression here - let body = match to_bytes(body).await { - Err(_) => return Err(LuaError::runtime("Failed to read request body bytes")), - Ok(b) => b.to_vec(), - }; - - let method = head.method.to_string().to_ascii_uppercase(); - - let mut path = head.uri.path().to_string(); - if path.is_empty() { - path = "/".to_string(); - } - - let query = head - .uri - .query() - .unwrap_or_default() - .split('&') - .filter_map(|q| q.split_once('=')) - .map(|(k, v)| (k.to_string(), v.to_string())) - .collect(); - - let mut headers = Vec::new(); - let mut header_name = String::new(); - for (name_opt, value) in head.headers.into_iter() { - if let Some(name) = name_opt { - header_name = name.to_string(); - } - headers.push((header_name.clone(), value.as_bytes().to_vec())) - } - - let id = ProcessedRequestId::new(); - - Ok(Self { - id, - method, - path, - query, - headers, - body, - }) - } - - pub fn into_lua_table(self, lua: &Lua) -> LuaResult { - // FUTURE: Make inner tables for query keys that have multiple values? - let query = lua.create_table_with_capacity(0, self.query.len())?; - for (key, value) in self.query.into_iter() { - query.set(key, value)?; - } - - let headers = lua.create_table_with_capacity(0, self.headers.len())?; - for (key, value) in self.headers.into_iter() { - headers.set(key, lua.create_string(value)?)?; - } - - let body = lua.create_string(self.body)?; - - TableBuilder::new(lua)? - .with_value("method", self.method)? - .with_value("path", self.path)? - .with_value("query", query)? - .with_value("headers", headers)? - .with_value("body", body)? - .build_readonly() - } -} diff --git a/src/lune/builtins/net/server.rs b/src/lune/builtins/net/server.rs deleted file mode 100644 index 8fb4612..0000000 --- a/src/lune/builtins/net/server.rs +++ /dev/null @@ -1,223 +0,0 @@ -use std::{ - collections::HashMap, - convert::Infallible, - net::{Ipv4Addr, SocketAddr}, - sync::Arc, -}; - -use hyper::{ - server::{conn::AddrIncoming, Builder}, - service::{make_service_fn, service_fn}, - Server, -}; - -use hyper_tungstenite::{is_upgrade_request, upgrade, HyperWebsocket}; -use mlua::prelude::*; -use tokio::sync::{mpsc, oneshot, Mutex}; - -use crate::lune::{ - scheduler::Scheduler, - util::{futures::yield_forever, traits::LuaEmitErrorExt, TableBuilder}, -}; - -use super::{ - config::ServeConfig, processing::ProcessedRequest, response::NetServeResponse, - websocket::NetWebSocket, -}; - -pub(super) fn bind_to_addr(address: Ipv4Addr, port: u16) -> LuaResult> { - let addr = SocketAddr::from((address, port)); - - match Server::try_bind(&addr) { - Ok(b) => Ok(b), - Err(e) => Err(LuaError::external(format!( - "Failed to bind to {addr}\n{}", - e.to_string() - .replace("error creating server listener: ", "> ") - ))), - } -} - -pub(super) fn create_server<'lua>( - lua: &'lua Lua, - sched: &'lua Scheduler, - config: ServeConfig<'lua>, - builder: Builder, -) -> LuaResult> -where - 'lua: 'static, // FIXME: Get rid of static lifetime bound here -{ - // Note that we need to use a mpsc here and not - // a oneshot channel since we move the sender - // into our table with the stop function - let (shutdown_tx, mut shutdown_rx) = mpsc::channel::<()>(1); - - // Communicate between background thread(s) and main lua thread using mpsc and oneshot - let (tx_request, mut rx_request) = mpsc::channel::(64); - let (tx_websocket, mut rx_websocket) = mpsc::channel::(64); - let tx_request_arc = Arc::new(tx_request); - let tx_websocket_arc = Arc::new(tx_websocket); - - let response_senders = Arc::new(Mutex::new(HashMap::new())); - let response_senders_bg = Arc::clone(&response_senders); - let response_senders_lua = Arc::clone(&response_senders_bg); - - // Create our background service which will accept - // requests, do some processing, then forward to lua - let has_websocket_handler = config.handle_web_socket.is_some(); - let hyper_make_service = make_service_fn(move |_| { - let tx_request = Arc::clone(&tx_request_arc); - let tx_websocket = Arc::clone(&tx_websocket_arc); - let response_senders = Arc::clone(&response_senders_bg); - - let handler = service_fn(move |mut req| { - let tx_request = Arc::clone(&tx_request); - let tx_websocket = Arc::clone(&tx_websocket); - let response_senders = Arc::clone(&response_senders); - async move { - // FUTURE: Improve error messages when lua is busy and queue is full - if has_websocket_handler && is_upgrade_request(&req) { - let (response, ws) = match upgrade(&mut req, None) { - Err(_) => return Err(LuaError::runtime("Failed to upgrade websocket")), - Ok(v) => v, - }; - if (tx_websocket.send(ws).await).is_err() { - return Err(LuaError::runtime("Lua handler is busy")); - } - Ok(response) - } else { - let processed = ProcessedRequest::from_request(req).await?; - let request_id = processed.id; - if (tx_request.send(processed).await).is_err() { - return Err(LuaError::runtime("Lua handler is busy")); - } - let (response_tx, response_rx) = oneshot::channel::(); - response_senders - .lock() - .await - .insert(request_id, response_tx); - match response_rx.await { - Err(_) => Err(LuaError::runtime("Internal Server Error")), - Ok(r) => r.into_response(), - } - } - } - }); - - async move { Ok::<_, Infallible>(handler) } - }); - - // Start up our service - sched.spawn(async move { - let result = builder - .http1_only(true) // Web sockets can only use http1 - .http1_keepalive(true) // Web sockets must be kept alive - .serve(hyper_make_service) - .with_graceful_shutdown(async move { - if shutdown_rx.recv().await.is_none() { - // The channel was closed, meaning the serve handle - // was garbage collected by lua without being used - yield_forever().await; - } - }); - if let Err(e) = result.await { - eprintln!("Net serve error: {e}") - } - }); - - // Spawn a local thread with access to lua and the same lifetime - sched.spawn_local(async move { - loop { - // Wait for either a request or a websocket to handle, - // if we got neither it means both channels were dropped - // and our server has stopped, either gracefully or panic - let (req, sock) = tokio::select! { - req = rx_request.recv() => (req, None), - sock = rx_websocket.recv() => (None, sock), - }; - if req.is_none() && sock.is_none() { - break; - } - - // NOTE: The closure here is not really necessary, we - // make the closure so that we can use the `?` operator - // and make a catch-all for errors in spawn_local below - let handle_request = config.handle_request.clone(); - let handle_web_socket = config.handle_web_socket.clone(); - let response_senders = Arc::clone(&response_senders_lua); - let response_fut = async move { - match (req, sock) { - (Some(req), _) => { - let req_id = req.id; - let req_table = req.into_lua_table(lua)?; - - let thread_id = sched.push_back(lua, handle_request, req_table)?; - let thread_res = sched.wait_for_thread(lua, thread_id).await?; - - let response = NetServeResponse::from_lua_multi(thread_res, lua)?; - let response_sender = response_senders - .lock() - .await - .remove(&req_id) - .expect("Response channel was removed unexpectedly"); - - // NOTE: We ignore the error here, if the sender is no longer - // being listened to its because our client disconnected during - // handler being called, which is fine and should not emit errors - response_sender.send(response).ok(); - - Ok(()) - } - (_, Some(sock)) => { - let sock = sock.await.into_lua_err()?; - - let sock_handler = handle_web_socket - .as_ref() - .cloned() - .expect("Got web socket but web socket handler is missing"); - let sock_table = NetWebSocket::new(sock).into_lua_table(lua)?; - - // NOTE: Web socket handler does not need to send any - // response back, the websocket upgrade response is - // automatically sent above in the background thread(s) - let thread_id = sched.push_back(lua, sock_handler, sock_table)?; - let _thread_res = sched.wait_for_thread(lua, thread_id).await?; - - Ok(()) - } - _ => unreachable!(), - } - }; - - /* - NOTE: It is currently not possible to spawn new background tasks from within - another background task with the Lune scheduler since they are locked behind a - mutex and we also need that mutex locked to be able to run a background task... - - We need to do some work to make it so our unordered futures queues do - not require locking and then we can replace the following bit of code: - - sched.spawn_local(async { - if let Err(e) = response_fut.await { - lua.emit_error(e); - } - }); - */ - if let Err(e) = response_fut.await { - lua.emit_error(e); - } - } - }); - - // Create a new read-only table that contains methods - // for manipulating server behavior and shutting it down - let handle_stop = move |_, _: ()| match shutdown_tx.try_send(()) { - Ok(_) => Ok(()), - Err(_) => Err(LuaError::RuntimeError( - "Server has already been stopped".to_string(), - )), - }; - TableBuilder::new(lua)? - .with_function("stop", handle_stop)? - .build_readonly() -} diff --git a/src/lune/builtins/net/server/keys.rs b/src/lune/builtins/net/server/keys.rs new file mode 100644 index 0000000..9dac06a --- /dev/null +++ b/src/lune/builtins/net/server/keys.rs @@ -0,0 +1,61 @@ +use std::sync::atomic::{AtomicUsize, Ordering}; + +use mlua::prelude::*; + +#[derive(Debug, Clone, Copy)] +pub(super) struct SvcKeys { + key_request: &'static str, + key_websocket: Option<&'static str>, +} + +impl SvcKeys { + pub(super) fn new<'lua>( + lua: &'lua Lua, + handle_request: LuaFunction<'lua>, + handle_websocket: Option>, + ) -> LuaResult { + static SERVE_COUNTER: AtomicUsize = AtomicUsize::new(0); + let count = SERVE_COUNTER.fetch_add(1, Ordering::Relaxed); + + // NOTE: We leak strings here, but this is an acceptable tradeoff since programs + // generally only start one or a couple of servers and they are usually never dropped. + // Leaking here lets us keep this struct Copy and access the request handler callbacks + // very performantly, significantly reducing the per-request overhead of the server. + let key_request: &'static str = + Box::leak(format!("__net_serve_request_{count}").into_boxed_str()); + let key_websocket: Option<&'static str> = if handle_websocket.is_some() { + Some(Box::leak( + format!("__net_serve_websocket_{count}").into_boxed_str(), + )) + } else { + None + }; + + lua.set_named_registry_value(key_request, handle_request)?; + if let Some(key) = key_websocket { + lua.set_named_registry_value(key, handle_websocket.unwrap())?; + } + + Ok(Self { + key_request, + key_websocket, + }) + } + + pub(super) fn has_websocket_handler(&self) -> bool { + self.key_websocket.is_some() + } + + pub(super) fn request_handler<'lua>(&self, lua: &'lua Lua) -> LuaResult> { + lua.named_registry_value(self.key_request) + } + + pub(super) fn websocket_handler<'lua>( + &self, + lua: &'lua Lua, + ) -> LuaResult>> { + self.key_websocket + .map(|key| lua.named_registry_value(key)) + .transpose() + } +} diff --git a/src/lune/builtins/net/server/mod.rs b/src/lune/builtins/net/server/mod.rs new file mode 100644 index 0000000..5639bcb --- /dev/null +++ b/src/lune/builtins/net/server/mod.rs @@ -0,0 +1,105 @@ +use std::{ + net::SocketAddr, + rc::{Rc, Weak}, +}; + +use hyper::server::conn::http1; +use hyper_util::rt::TokioIo; +use tokio::{net::TcpListener, pin}; + +use mlua::prelude::*; +use mlua_luau_scheduler::LuaSpawnExt; + +use crate::lune::util::TableBuilder; + +use super::config::ServeConfig; + +mod keys; +mod request; +mod response; +mod service; + +use keys::SvcKeys; +use service::Svc; + +pub async fn serve<'lua>( + lua: &'lua Lua, + port: u16, + config: ServeConfig<'lua>, +) -> LuaResult> { + let addr: SocketAddr = (config.address, port).into(); + let listener = TcpListener::bind(addr).await?; + + let (lua_svc, lua_inner) = { + let rc = lua + .app_data_ref::>() + .expect("Missing weak lua ref") + .upgrade() + .expect("Lua was dropped unexpectedly"); + (Rc::clone(&rc), rc) + }; + + let keys = SvcKeys::new(lua, config.handle_request, config.handle_web_socket)?; + let svc = Svc { + lua: lua_svc, + addr, + keys, + }; + + let (shutdown_tx, shutdown_rx) = tokio::sync::watch::channel(false); + lua.spawn_local(async move { + let mut shutdown_rx_outer = shutdown_rx.clone(); + loop { + // Create futures for accepting new connections and shutting down + let fut_shutdown = shutdown_rx_outer.changed(); + let fut_accept = async { + let stream = match listener.accept().await { + Err(_) => return, + Ok((s, _)) => s, + }; + + let io = TokioIo::new(stream); + let svc = svc.clone(); + let mut shutdown_rx_inner = shutdown_rx.clone(); + + lua_inner.spawn_local(async move { + let conn = http1::Builder::new() + .keep_alive(true) // Web sockets need this + .serve_connection(io, svc) + .with_upgrades(); + // NOTE: Because we need to use keep_alive for websockets, we need to + // also manually poll this future and handle the shutdown signal here + pin!(conn); + tokio::select! { + _ = conn.as_mut() => {} + _ = shutdown_rx_inner.changed() => { + conn.as_mut().graceful_shutdown(); + } + } + }); + }; + + // Wait for either a new connection or a shutdown signal + tokio::select! { + _ = fut_accept => {} + res = fut_shutdown => { + // NOTE: We will only get a RecvError here if the serve handle is dropped, + // this means lua has garbage collected it and the user does not want + // to manually stop the server using the serve handle. Run forever. + if res.is_ok() { + break; + } + } + } + } + }); + + TableBuilder::new(lua)? + .with_value("ip", addr.ip().to_string())? + .with_value("port", addr.port())? + .with_function("stop", move |lua, _: ()| match shutdown_tx.send(true) { + Ok(_) => Ok(()), + Err(_) => Err(LuaError::runtime("Server already stopped")), + })? + .build_readonly() +} diff --git a/src/lune/builtins/net/server/request.rs b/src/lune/builtins/net/server/request.rs new file mode 100644 index 0000000..2eea689 --- /dev/null +++ b/src/lune/builtins/net/server/request.rs @@ -0,0 +1,46 @@ +use std::{collections::HashMap, net::SocketAddr}; + +use http::request::Parts; + +use mlua::prelude::*; + +pub(super) struct LuaRequest { + pub(super) _remote_addr: SocketAddr, + pub(super) head: Parts, + pub(super) body: Vec, +} + +impl LuaUserData for LuaRequest { + fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) { + fields.add_field_method_get("method", |_, this| { + Ok(this.head.method.as_str().to_string()) + }); + + fields.add_field_method_get("path", |_, this| Ok(this.head.uri.path().to_string())); + + fields.add_field_method_get("query", |_, this| { + let query: HashMap = this + .head + .uri + .query() + .unwrap_or_default() + .split('&') + .filter_map(|q| q.split_once('=')) + .map(|(k, v)| (k.to_string(), v.to_string())) + .collect(); + Ok(query) + }); + + fields.add_field_method_get("headers", |_, this| { + let headers: HashMap> = this + .head + .headers + .iter() + .map(|(k, v)| (k.as_str().to_string(), v.as_bytes().to_vec())) + .collect(); + Ok(headers) + }); + + fields.add_field_method_get("body", |lua, this| lua.create_string(&this.body)); + } +} diff --git a/src/lune/builtins/net/response.rs b/src/lune/builtins/net/server/response.rs similarity index 53% rename from src/lune/builtins/net/response.rs rename to src/lune/builtins/net/server/response.rs index d14646a..f575990 100644 --- a/src/lune/builtins/net/response.rs +++ b/src/lune/builtins/net/server/response.rs @@ -1,52 +1,55 @@ -use std::collections::HashMap; +use std::str::FromStr; + +use http_body_util::Full; +use hyper::{ + body::Bytes, + header::{HeaderName, HeaderValue}, + HeaderMap, Response, +}; -use hyper::{Body, Response}; use mlua::prelude::*; #[derive(Debug, Clone, Copy)] -pub enum NetServeResponseKind { +pub(super) enum LuaResponseKind { PlainText, Table, } -#[derive(Debug)] -pub struct NetServeResponse { - kind: NetServeResponseKind, - status: u16, - headers: HashMap>, - body: Option>, +pub(super) struct LuaResponse { + pub(super) kind: LuaResponseKind, + pub(super) status: u16, + pub(super) headers: HeaderMap, + pub(super) body: Option>, } -impl NetServeResponse { - pub fn into_response(self) -> LuaResult> { +impl LuaResponse { + pub(super) fn into_response(self) -> LuaResult>> { Ok(match self.kind { - NetServeResponseKind::PlainText => Response::builder() + LuaResponseKind::PlainText => Response::builder() .status(200) .header("Content-Type", "text/plain") - .body(Body::from(self.body.unwrap())) + .body(Full::new(Bytes::from(self.body.unwrap()))) .into_lua_err()?, - NetServeResponseKind::Table => { - let mut response = Response::builder(); - for (key, value) in self.headers { - response = response.header(&key, value); - } - response + LuaResponseKind::Table => { + let mut response = Response::builder() .status(self.status) - .body(Body::from(self.body.unwrap_or_default())) - .into_lua_err()? + .body(Full::new(Bytes::from(self.body.unwrap_or_default()))) + .into_lua_err()?; + response.headers_mut().extend(self.headers); + response } }) } } -impl<'lua> FromLua<'lua> for NetServeResponse { - fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult { +impl FromLua<'_> for LuaResponse { + fn from_lua(value: LuaValue, _: &Lua) -> LuaResult { match value { // Plain strings from the handler are plaintext responses LuaValue::String(s) => Ok(Self { - kind: NetServeResponseKind::PlainText, + kind: LuaResponseKind::PlainText, status: 200, - headers: HashMap::new(), + headers: HeaderMap::new(), body: Some(s.as_bytes().to_vec()), }), // Tables are more detailed responses with potential status, headers, body @@ -55,18 +58,20 @@ impl<'lua> FromLua<'lua> for NetServeResponse { let headers: Option = t.get("headers")?; let body: Option = t.get("body")?; - let mut headers_map = HashMap::new(); + let mut headers_map = HeaderMap::new(); if let Some(headers) = headers { for pair in headers.pairs::() { let (h, v) = pair?; - headers_map.insert(h, v.as_bytes().to_vec()); + let name = HeaderName::from_str(&h).into_lua_err()?; + let value = HeaderValue::from_bytes(v.as_bytes()).into_lua_err()?; + headers_map.insert(name, value); } } let body_bytes = body.map(|s| s.as_bytes().to_vec()); Ok(Self { - kind: NetServeResponseKind::Table, + kind: LuaResponseKind::Table, status: status.unwrap_or(200), headers: headers_map, body: body_bytes, diff --git a/src/lune/builtins/net/server/service.rs b/src/lune/builtins/net/server/service.rs new file mode 100644 index 0000000..a37d669 --- /dev/null +++ b/src/lune/builtins/net/server/service.rs @@ -0,0 +1,81 @@ +use std::{future::Future, net::SocketAddr, pin::Pin, rc::Rc}; + +use http_body_util::{BodyExt, Full}; +use hyper::{ + body::{Bytes, Incoming}, + service::Service, + Request, Response, +}; +use hyper_tungstenite::{is_upgrade_request, upgrade}; + +use mlua::prelude::*; +use mlua_luau_scheduler::{LuaSchedulerExt, LuaSpawnExt}; + +use super::{ + super::websocket::NetWebSocket, keys::SvcKeys, request::LuaRequest, response::LuaResponse, +}; + +#[derive(Debug, Clone)] +pub(super) struct Svc { + pub(super) lua: Rc, + pub(super) addr: SocketAddr, + pub(super) keys: SvcKeys, +} + +impl Service> for Svc { + type Response = Response>; + type Error = LuaError; + type Future = Pin>>>; + + fn call(&self, req: Request) -> Self::Future { + let lua = self.lua.clone(); + let addr = self.addr; + let keys = self.keys; + + if keys.has_websocket_handler() && is_upgrade_request(&req) { + Box::pin(async move { + let (res, sock) = upgrade(req, None).into_lua_err()?; + + let lua_inner = lua.clone(); + lua.spawn_local(async move { + let sock = sock.await.unwrap(); + let lua_sock = NetWebSocket::new(sock); + let lua_tab = lua_sock.into_lua_table(&lua_inner).unwrap(); + + let handler_websocket: LuaFunction = + keys.websocket_handler(&lua_inner).unwrap().unwrap(); + + lua_inner + .push_thread_back(handler_websocket, lua_tab) + .unwrap(); + }); + + Ok(res) + }) + } else { + let (head, body) = req.into_parts(); + + Box::pin(async move { + let handler_request: LuaFunction = keys.request_handler(&lua).unwrap(); + + let body = body.collect().await.into_lua_err()?; + let body = body.to_bytes().to_vec(); + + let lua_req = LuaRequest { + _remote_addr: addr, + head, + body, + }; + + let thread_id = lua.push_thread_back(handler_request, lua_req)?; + lua.track_thread(thread_id); + lua.wait_for_thread(thread_id).await; + let thread_res = lua + .get_thread_result(thread_id) + .expect("Missing handler thread result")?; + + LuaResponse::from_lua_multi(thread_res, &lua)?.into_response() + }) + } + } +} diff --git a/src/lune/builtins/net/util.rs b/src/lune/builtins/net/util.rs index fa1e2ad..4603547 100644 --- a/src/lune/builtins/net/util.rs +++ b/src/lune/builtins/net/util.rs @@ -1,14 +1,20 @@ use std::collections::HashMap; -use hyper::{ - header::{CONTENT_ENCODING, CONTENT_LENGTH}, - HeaderMap, -}; +use hyper::header::{CONTENT_ENCODING, CONTENT_LENGTH}; +use reqwest::header::HeaderMap; use mlua::prelude::*; use crate::lune::util::TableBuilder; +pub fn create_user_agent_header() -> String { + let (github_owner, github_repo) = env!("CARGO_PKG_REPOSITORY") + .trim_start_matches("https://github.com/") + .split_once('/') + .unwrap(); + format!("{github_owner}-{github_repo}-cli") +} + pub fn header_map_to_table( lua: &Lua, headers: HeaderMap, diff --git a/src/lune/builtins/net/websocket.rs b/src/lune/builtins/net/websocket.rs index aa01c13..fb7233c 100644 --- a/src/lune/builtins/net/websocket.rs +++ b/src/lune/builtins/net/websocket.rs @@ -1,6 +1,8 @@ -use std::sync::Arc; +use std::sync::{ + atomic::{AtomicBool, AtomicU16, Ordering}, + Arc, +}; -use hyper::upgrade::Upgraded; use mlua::prelude::*; use futures_util::{ @@ -9,7 +11,6 @@ use futures_util::{ }; use tokio::{ io::{AsyncRead, AsyncWrite}, - net::TcpStream, sync::Mutex as AsyncMutex, }; @@ -20,25 +21,25 @@ use hyper_tungstenite::{ }, WebSocketStream, }; -use tokio_tungstenite::MaybeTlsStream; use crate::lune::util::TableBuilder; +// Wrapper implementation for compatibility and changing colon syntax to dot syntax const WEB_SOCKET_IMPL_LUA: &str = r#" return freeze(setmetatable({ close = function(...) - return close(websocket, ...) + return websocket:close(...) end, send = function(...) - return send(websocket, ...) + return websocket:send(...) end, next = function(...) - return next(websocket, ...) + return websocket:next(...) end, }, { __index = function(self, key) if key == "closeCode" then - return close_code(websocket) + return websocket.closeCode end end, })) @@ -46,7 +47,8 @@ return freeze(setmetatable({ #[derive(Debug)] pub struct NetWebSocket { - close_code: Arc>>, + close_code_exists: Arc, + close_code_value: Arc, read_stream: Arc>>>, write_stream: Arc, WsMessage>>>, } @@ -54,7 +56,8 @@ pub struct NetWebSocket { impl Clone for NetWebSocket { fn clone(&self) -> Self { Self { - close_code: Arc::clone(&self.close_code), + close_code_exists: Arc::clone(&self.close_code_exists), + close_code_value: Arc::clone(&self.close_code_value), read_stream: Arc::clone(&self.read_stream), write_stream: Arc::clone(&self.write_stream), } @@ -63,22 +66,78 @@ impl Clone for NetWebSocket { impl NetWebSocket where - T: AsyncRead + AsyncWrite + Unpin, + T: AsyncRead + AsyncWrite + Unpin + 'static, { pub fn new(value: WebSocketStream) -> Self { let (write, read) = value.split(); Self { - close_code: Arc::new(AsyncMutex::new(None)), + close_code_exists: Arc::new(AtomicBool::new(false)), + close_code_value: Arc::new(AtomicU16::new(0)), read_stream: Arc::new(AsyncMutex::new(read)), write_stream: Arc::new(AsyncMutex::new(write)), } } - fn into_lua_table_with_env<'lua>( - lua: &'lua Lua, - env: LuaTable<'lua>, - ) -> LuaResult> { + fn get_close_code(&self) -> Option { + if self.close_code_exists.load(Ordering::Relaxed) { + Some(self.close_code_value.load(Ordering::Relaxed)) + } else { + None + } + } + + fn set_close_code(&self, code: u16) { + self.close_code_exists.store(true, Ordering::Relaxed); + self.close_code_value.store(code, Ordering::Relaxed); + } + + pub async fn send(&self, msg: WsMessage) -> LuaResult<()> { + let mut ws = self.write_stream.lock().await; + ws.send(msg).await.into_lua_err() + } + + pub async fn next(&self) -> LuaResult> { + let mut ws = self.read_stream.lock().await; + ws.next().await.transpose().into_lua_err() + } + + pub async fn close(&self, code: Option) -> LuaResult<()> { + if self.close_code_exists.load(Ordering::Relaxed) { + return Err(LuaError::runtime("Socket has already been closed")); + } + + self.send(WsMessage::Close(Some(WsCloseFrame { + code: match code { + Some(code) if (1000..=4999).contains(&code) => WsCloseCode::from(code), + Some(code) => { + return Err(LuaError::runtime(format!( + "Close code must be between 1000 and 4999, got {code}" + ))) + } + None => WsCloseCode::Normal, + }, + reason: "".into(), + }))) + .await?; + + let mut ws = self.write_stream.lock().await; + ws.close().await.into_lua_err() + } + + pub fn into_lua_table(self, lua: &Lua) -> LuaResult { + let setmetatable = lua.globals().get::<_, LuaFunction>("setmetatable")?; + let table_freeze = lua + .globals() + .get::<_, LuaTable>("table")? + .get::<_, LuaFunction>("freeze")?; + + let env = TableBuilder::new(lua)? + .with_value("websocket", self.clone())? + .with_value("setmetatable", setmetatable)? + .with_value("freeze", table_freeze)? + .build_readonly()?; + lua.load(WEB_SOCKET_IMPL_LUA) .set_name("websocket") .set_environment(env) @@ -86,149 +145,46 @@ where } } -type NetWebSocketStreamClient = MaybeTlsStream; -impl NetWebSocket { - pub fn into_lua_table(self, lua: &'static Lua) -> LuaResult { - let setmetatable = lua.globals().get::<_, LuaFunction>("setmetatable")?; - let table_freeze = lua - .globals() - .get::<_, LuaTable>("table")? - .get::<_, LuaFunction>("freeze")?; - let socket_env = TableBuilder::new(lua)? - .with_value("websocket", self)? - .with_function("close_code", close_code::)? - .with_async_function("close", close::)? - .with_async_function("send", send::)? - .with_async_function("next", next::)? - .with_value("setmetatable", setmetatable)? - .with_value("freeze", table_freeze)? - .build_readonly()?; - Self::into_lua_table_with_env(lua, socket_env) - } -} - -type NetWebSocketStreamServer = Upgraded; -impl NetWebSocket { - pub fn into_lua_table(self, lua: &'static Lua) -> LuaResult { - let setmetatable = lua.globals().get::<_, LuaFunction>("setmetatable")?; - let table_freeze = lua - .globals() - .get::<_, LuaTable>("table")? - .get::<_, LuaFunction>("freeze")?; - let socket_env = TableBuilder::new(lua)? - .with_value("websocket", self)? - .with_function("close_code", close_code::)? - .with_async_function("close", close::)? - .with_async_function("send", send::)? - .with_async_function("next", next::)? - .with_value("setmetatable", setmetatable)? - .with_value("freeze", table_freeze)? - .build_readonly()?; - Self::into_lua_table_with_env(lua, socket_env) - } -} - -impl LuaUserData for NetWebSocket {} - -fn close_code<'lua, T>( - _lua: &'lua Lua, - socket: LuaUserDataRef<'lua, NetWebSocket>, -) -> LuaResult> +impl LuaUserData for NetWebSocket where - T: AsyncRead + AsyncWrite + Unpin, + T: AsyncRead + AsyncWrite + Unpin + 'static, { - Ok( - match *socket - .close_code - .try_lock() - .expect("Failed to lock close code") - { - Some(code) => LuaValue::Number(code as f64), - None => LuaValue::Nil, - }, - ) -} + fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) { + fields.add_field_method_get("closeCode", |_, this| Ok(this.get_close_code())); + } -async fn close<'lua, T>( - _lua: &'lua Lua, - (socket, code): (LuaUserDataRef<'lua, NetWebSocket>, Option), -) -> LuaResult<()> -where - T: AsyncRead + AsyncWrite + Unpin, -{ - let mut ws = socket.write_stream.lock().await; + fn add_methods<'lua, M: LuaUserDataMethods<'lua, Self>>(methods: &mut M) { + methods.add_async_method("close", |lua, this, code: Option| async move { + this.close(code).await + }); - ws.send(WsMessage::Close(Some(WsCloseFrame { - code: match code { - Some(code) if (1000..=4999).contains(&code) => WsCloseCode::from(code), - Some(code) => { - return Err(LuaError::RuntimeError(format!( - "Close code must be between 1000 and 4999, got {code}" - ))) + methods.add_async_method( + "send", + |_, this, (string, as_binary): (LuaString, Option)| async move { + this.send(if as_binary.unwrap_or_default() { + WsMessage::Binary(string.as_bytes().to_vec()) + } else { + let s = string.to_str().into_lua_err()?; + WsMessage::Text(s.to_string()) + }) + .await + }, + ); + + methods.add_async_method("next", |lua, this, _: ()| async move { + let msg = this.next().await?; + + if let Some(WsMessage::Close(Some(frame))) = msg.as_ref() { + this.set_close_code(frame.code.into()); } - None => WsCloseCode::Normal, - }, - reason: "".into(), - }))) - .await - .into_lua_err()?; - let res = ws.close(); - res.await.into_lua_err() -} - -async fn send<'lua, T>( - _lua: &'lua Lua, - (socket, string, as_binary): ( - LuaUserDataRef<'lua, NetWebSocket>, - LuaString<'lua>, - Option, - ), -) -> LuaResult<()> -where - T: AsyncRead + AsyncWrite + Unpin, -{ - let msg = if matches!(as_binary, Some(true)) { - WsMessage::Binary(string.as_bytes().to_vec()) - } else { - 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.into_lua_err() -} - -async fn next<'lua, T>( - lua: &'lua Lua, - socket: LuaUserDataRef<'lua, NetWebSocket>, -) -> LuaResult> -where - T: AsyncRead + AsyncWrite + Unpin, -{ - let mut ws = socket.read_stream.lock().await; - let item = ws.next().await.transpose().into_lua_err(); - let msg = match item { - Ok(Some(WsMessage::Close(msg))) => { - if let Some(msg) = &msg { - let mut code = socket.close_code.lock().await; - *code = Some(msg.code.into()); - } - Ok(Some(WsMessage::Close(msg))) - } - val => val, - }?; - while let Some(msg) = &msg { - let msg_string_opt = match msg { - WsMessage::Binary(bin) => Some(lua.create_string(bin)?), - WsMessage::Text(txt) => Some(lua.create_string(txt)?), - // Stop waiting for next message if we get a close message - WsMessage::Close(_) => return Ok(LuaValue::Nil), - // Ignore ping/pong/frame messages, they are handled by tungstenite - _ => None, - }; - if let Some(msg_string) = msg_string_opt { - return Ok(LuaValue::String(msg_string)); - } + Ok(match msg { + Some(WsMessage::Binary(bin)) => LuaValue::String(lua.create_string(bin)?), + Some(WsMessage::Text(txt)) => LuaValue::String(lua.create_string(txt)?), + Some(WsMessage::Close(_)) | None => LuaValue::Nil, + // Ignore ping/pong/frame messages, they are handled by tungstenite + msg => unreachable!("Unhandled message: {:?}", msg), + }) + }); } - Ok(LuaValue::Nil) } diff --git a/src/lune/builtins/process/mod.rs b/src/lune/builtins/process/mod.rs index 15b1833..b64e9aa 100644 --- a/src/lune/builtins/process/mod.rs +++ b/src/lune/builtins/process/mod.rs @@ -5,13 +5,11 @@ use std::{ }; use mlua::prelude::*; +use mlua_luau_scheduler::{Functions, LuaSpawnExt}; use os_str_bytes::RawOsString; use tokio::io::AsyncWriteExt; -use crate::lune::{ - scheduler::Scheduler, - util::{paths::CWD, TableBuilder}, -}; +use crate::lune::util::{paths::CWD, TableBuilder}; mod tee_writer; @@ -21,12 +19,7 @@ use options::ProcessSpawnOptions; mod wait_for_child; use wait_for_child::{wait_for_child, WaitForChildResult}; -const PROCESS_EXIT_IMPL_LUA: &str = r#" -exit(...) -yield() -"#; - -pub fn create(lua: &'static Lua) -> LuaResult { +pub fn create(lua: &Lua) -> LuaResult { let cwd_str = { let cwd_str = CWD.to_string_lossy().to_string(); if !cwd_str.ends_with(path::MAIN_SEPARATOR) { @@ -56,30 +49,9 @@ pub fn create(lua: &'static Lua) -> LuaResult { .build_readonly()?, )? .build_readonly()?; - // Create our process exit function, this is a bit involved since - // we have no way to yield from c / rust, we need to load a lua - // chunk that will set the exit code and yield for us instead - let coroutine_yield = lua - .globals() - .get::<_, LuaTable>("coroutine")? - .get::<_, LuaFunction>("yield")?; - let set_scheduler_exit_code = lua.create_function(|lua, code: Option| { - let sched = lua - .app_data_ref::<&Scheduler>() - .expect("Lua struct is missing scheduler"); - sched.set_exit_code(code.unwrap_or_default()); - Ok(()) - })?; - let process_exit = lua - .load(PROCESS_EXIT_IMPL_LUA) - .set_name("=process.exit") - .set_environment( - TableBuilder::new(lua)? - .with_value("yield", coroutine_yield)? - .with_value("exit", set_scheduler_exit_code)? - .build_readonly()?, - ) - .into_function()?; + // Create our process exit function, the scheduler crate provides this + let fns = Functions::new(lua)?; + let process_exit = fns.exit; // Create the full process table TableBuilder::new(lua)? .with_value("os", os)? @@ -165,22 +137,10 @@ async fn process_spawn( lua: &Lua, (program, args, options): (String, Option>, ProcessSpawnOptions), ) -> LuaResult { - /* - Spawn the new process in the background, letting the tokio - runtime place it on a different thread if possible / necessary - - Note that we have to use our scheduler here, we can't - be using tokio::task::spawn directly because our lua - scheduler would not drive those futures to completion - */ - let sched = lua - .app_data_ref::<&Scheduler>() - .expect("Lua struct is missing scheduler"); - - let res = sched + let res = lua .spawn(spawn_command(program, args, options)) .await - .expect("Failed to receive result of spawned process")?; + .expect("Failed to receive result of spawned process"); /* NOTE: If an exit code was not given by the child process, diff --git a/src/lune/builtins/roblox/mod.rs b/src/lune/builtins/roblox/mod.rs index 1e54355..308c148 100644 --- a/src/lune/builtins/roblox/mod.rs +++ b/src/lune/builtins/roblox/mod.rs @@ -1,4 +1,5 @@ use mlua::prelude::*; +use mlua_luau_scheduler::LuaSpawnExt; use once_cell::sync::OnceCell; use crate::{ @@ -11,11 +12,9 @@ use crate::{ }, }; -use tokio::task; - static REFLECTION_DATABASE: OnceCell = OnceCell::new(); -pub fn create(lua: &'static Lua) -> LuaResult { +pub fn create(lua: &Lua) -> LuaResult { let mut roblox_constants = Vec::new(); let roblox_module = roblox::module(lua)?; @@ -41,12 +40,12 @@ async fn deserialize_place<'lua>( contents: LuaString<'lua>, ) -> LuaResult> { let bytes = contents.as_bytes().to_vec(); - let fut = task::spawn_blocking(move || { + let fut = lua.spawn_blocking(move || { let doc = Document::from_bytes(bytes, DocumentKind::Place)?; let data_model = doc.into_data_model_instance()?; Ok::<_, DocumentError>(data_model) }); - fut.await.into_lua_err()??.into_lua(lua) + fut.await.into_lua_err()?.into_lua(lua) } async fn deserialize_model<'lua>( @@ -54,12 +53,12 @@ async fn deserialize_model<'lua>( contents: LuaString<'lua>, ) -> LuaResult> { let bytes = contents.as_bytes().to_vec(); - let fut = task::spawn_blocking(move || { + let fut = lua.spawn_blocking(move || { let doc = Document::from_bytes(bytes, DocumentKind::Model)?; let instance_array = doc.into_instance_array()?; Ok::<_, DocumentError>(instance_array) }); - fut.await.into_lua_err()??.into_lua(lua) + fut.await.into_lua_err()?.into_lua(lua) } async fn serialize_place<'lua>( @@ -67,7 +66,7 @@ async fn serialize_place<'lua>( (data_model, as_xml): (LuaUserDataRef<'lua, Instance>, Option), ) -> LuaResult> { let data_model = (*data_model).clone(); - let fut = task::spawn_blocking(move || { + let fut = lua.spawn_blocking(move || { let doc = Document::from_data_model_instance(data_model)?; let bytes = doc.to_bytes_with_format(match as_xml { Some(true) => DocumentFormat::Xml, @@ -75,7 +74,7 @@ async fn serialize_place<'lua>( })?; Ok::<_, DocumentError>(bytes) }); - let bytes = fut.await.into_lua_err()??; + let bytes = fut.await.into_lua_err()?; lua.create_string(bytes) } @@ -84,7 +83,7 @@ async fn serialize_model<'lua>( (instances, as_xml): (Vec>, Option), ) -> LuaResult> { let instances = instances.iter().map(|i| (*i).clone()).collect(); - let fut = task::spawn_blocking(move || { + let fut = lua.spawn_blocking(move || { let doc = Document::from_instance_array(instances)?; let bytes = doc.to_bytes_with_format(match as_xml { Some(true) => DocumentFormat::Xml, @@ -92,7 +91,7 @@ async fn serialize_model<'lua>( })?; Ok::<_, DocumentError>(bytes) }); - let bytes = fut.await.into_lua_err()??; + let bytes = fut.await.into_lua_err()?; lua.create_string(bytes) } diff --git a/src/lune/builtins/serde/compress_decompress.rs b/src/lune/builtins/serde/compress_decompress.rs index 85e7ac5..dac6ceb 100644 --- a/src/lune/builtins/serde/compress_decompress.rs +++ b/src/lune/builtins/serde/compress_decompress.rs @@ -1,9 +1,7 @@ -use lz4_flex::{compress_prepend_size, decompress_size_prepended}; use mlua::prelude::*; -use tokio::{ - io::{copy, BufReader}, - task, -}; + +use lz4_flex::{compress_prepend_size, decompress_size_prepended}; +use tokio::io::{copy, BufReader}; use async_compression::{ tokio::bufread::{ @@ -100,9 +98,7 @@ pub async fn compress<'lua>( ) -> LuaResult> { if let CompressDecompressFormat::LZ4 = format { let source = source.as_ref().to_vec(); - return task::spawn_blocking(move || compress_prepend_size(&source)) - .await - .into_lua_err(); + return Ok(blocking::unblock(move || compress_prepend_size(&source)).await); } let mut bytes = Vec::new(); @@ -133,9 +129,8 @@ pub async fn decompress<'lua>( ) -> LuaResult> { if let CompressDecompressFormat::LZ4 = format { let source = source.as_ref().to_vec(); - return task::spawn_blocking(move || decompress_size_prepended(&source)) + return blocking::unblock(move || decompress_size_prepended(&source)) .await - .into_lua_err()? .into_lua_err(); } diff --git a/src/lune/builtins/serde/mod.rs b/src/lune/builtins/serde/mod.rs index 4e76bce..c149467 100644 --- a/src/lune/builtins/serde/mod.rs +++ b/src/lune/builtins/serde/mod.rs @@ -8,7 +8,7 @@ use encode_decode::{EncodeDecodeConfig, EncodeDecodeFormat}; use crate::lune::util::TableBuilder; -pub fn create(lua: &'static Lua) -> LuaResult { +pub fn create(lua: &Lua) -> LuaResult { TableBuilder::new(lua)? .with_function("encode", serde_encode)? .with_function("decode", serde_decode)? diff --git a/src/lune/builtins/stdio/mod.rs b/src/lune/builtins/stdio/mod.rs index b572251..14927f2 100644 --- a/src/lune/builtins/stdio/mod.rs +++ b/src/lune/builtins/stdio/mod.rs @@ -1,10 +1,8 @@ use mlua::prelude::*; use dialoguer::{theme::ColorfulTheme, Confirm, Input, MultiSelect, Select}; -use tokio::{ - io::{self, AsyncWriteExt}, - task, -}; +use mlua_luau_scheduler::LuaSpawnExt; +use tokio::io::{self, AsyncWriteExt}; use crate::lune::util::{ formatting::{ @@ -16,7 +14,7 @@ use crate::lune::util::{ mod prompt; use prompt::{PromptKind, PromptOptions, PromptResult}; -pub fn create(lua: &'static Lua) -> LuaResult> { +pub fn create(lua: &Lua) -> LuaResult> { TableBuilder::new(lua)? .with_function("color", stdio_color)? .with_function("style", stdio_style)? @@ -55,10 +53,10 @@ async fn stdio_ewrite(_: &Lua, s: LuaString<'_>) -> LuaResult<()> { Ok(()) } -async fn stdio_prompt(_: &Lua, options: PromptOptions) -> LuaResult { - task::spawn_blocking(move || prompt(options)) +async fn stdio_prompt(lua: &Lua, options: PromptOptions) -> LuaResult { + lua.spawn_blocking(move || prompt(options)) .await - .into_lua_err()? + .into_lua_err() } fn prompt(options: PromptOptions) -> LuaResult { diff --git a/src/lune/builtins/task/mod.rs b/src/lune/builtins/task/mod.rs index 189df32..94a4da0 100644 --- a/src/lune/builtins/task/mod.rs +++ b/src/lune/builtins/task/mod.rs @@ -2,120 +2,51 @@ use std::time::Duration; use mlua::prelude::*; +use mlua_luau_scheduler::Functions; use tokio::time::{self, Instant}; -use crate::lune::{scheduler::Scheduler, util::TableBuilder}; +use crate::lune::util::TableBuilder; -mod tof; -use tof::LuaThreadOrFunction; - -/* - The spawn function needs special treatment, - we need to yield right away to allow the - spawned task to run until first yield - - 1. Schedule this current thread at the front - 2. Schedule given thread/function at the front, - the previous schedule now comes right after - 3. Give control over to the scheduler, which will - resume the above tasks in order when its ready -*/ -const SPAWN_IMPL_LUA: &str = r#" -push(currentThread()) -local thread = push(...) -yield() -return thread +const DELAY_IMPL_LUA: &str = r#" +return defer(function(...) + wait(select(1, ...)) + spawn(select(2, ...)) +end, ...) "#; -pub fn create(lua: &'static Lua) -> LuaResult> { - let coroutine_running = lua - .globals() - .get::<_, LuaTable>("coroutine")? - .get::<_, LuaFunction>("running")?; - let coroutine_yield = lua - .globals() - .get::<_, LuaTable>("coroutine")? - .get::<_, LuaFunction>("yield")?; - let push_front = - lua.create_function(|lua, (tof, args): (LuaThreadOrFunction, LuaMultiValue)| { - let thread = tof.into_thread(lua)?; - let sched = lua - .app_data_ref::<&Scheduler>() - .expect("Lua struct is missing scheduler"); - sched.push_front(lua, thread.clone(), args)?; - Ok(thread) - })?; - let task_spawn_env = TableBuilder::new(lua)? - .with_value("currentThread", coroutine_running)? - .with_value("yield", coroutine_yield)? - .with_value("push", push_front)? +pub fn create(lua: &Lua) -> LuaResult> { + let fns = Functions::new(lua)?; + + // Create wait & delay functions + let task_wait = lua.create_async_function(wait)?; + let task_delay_env = TableBuilder::new(lua)? + .with_value("select", lua.globals().get::<_, LuaFunction>("select")?)? + .with_value("spawn", fns.spawn.clone())? + .with_value("defer", fns.defer.clone())? + .with_value("wait", task_wait.clone())? .build_readonly()?; - let task_spawn = lua - .load(SPAWN_IMPL_LUA) - .set_name("task.spawn") - .set_environment(task_spawn_env) + let task_delay = lua + .load(DELAY_IMPL_LUA) + .set_name("task.delay") + .set_environment(task_delay_env) .into_function()?; + // Overwrite resume & wrap functions on the coroutine global + // with ones that are compatible with our scheduler + let co = lua.globals().get::<_, LuaTable>("coroutine")?; + co.set("resume", fns.resume.clone())?; + co.set("wrap", fns.wrap.clone())?; + TableBuilder::new(lua)? - .with_function("cancel", task_cancel)? - .with_function("defer", task_defer)? - .with_function("delay", task_delay)? - .with_value("spawn", task_spawn)? - .with_async_function("wait", task_wait)? + .with_value("cancel", fns.cancel)? + .with_value("defer", fns.defer)? + .with_value("delay", task_delay)? + .with_value("spawn", fns.spawn)? + .with_value("wait", task_wait)? .build_readonly() } -fn task_cancel(lua: &Lua, thread: LuaThread) -> LuaResult<()> { - let close = lua - .globals() - .get::<_, LuaTable>("coroutine")? - .get::<_, LuaFunction>("close")?; - match close.call(thread) { - Err(LuaError::CoroutineInactive) => Ok(()), - Err(e) => Err(e), - Ok(()) => Ok(()), - } -} - -fn task_defer<'lua>( - lua: &'lua Lua, - (tof, args): (LuaThreadOrFunction<'lua>, LuaMultiValue<'_>), -) -> LuaResult> { - let thread = tof.into_thread(lua)?; - let sched = lua - .app_data_ref::<&Scheduler>() - .expect("Lua struct is missing scheduler"); - sched.push_back(lua, thread.clone(), args)?; - Ok(thread) -} - -// FIXME: `self` escapes outside of method because we are borrowing `tof` and -// `args` when we call `schedule_future_thread` in the lua function body below -// For now we solve this by using the 'static lifetime bound in the impl -fn task_delay<'lua>( - lua: &'lua Lua, - (secs, tof, args): (f64, LuaThreadOrFunction<'lua>, LuaMultiValue<'lua>), -) -> LuaResult> -where - 'lua: 'static, -{ - let thread = tof.into_thread(lua)?; - let sched = lua - .app_data_ref::<&Scheduler>() - .expect("Lua struct is missing scheduler"); - - let thread2 = thread.clone(); - sched.spawn_thread(lua, thread.clone(), async move { - let duration = Duration::from_secs_f64(secs); - time::sleep(duration).await; - sched.push_back(lua, thread2, args)?; - Ok(()) - })?; - - Ok(thread) -} - -async fn task_wait(_: &Lua, secs: Option) -> LuaResult { +async fn wait(_: &Lua, secs: Option) -> LuaResult { let duration = Duration::from_secs_f64(secs.unwrap_or_default()); let before = Instant::now(); diff --git a/src/lune/builtins/task/tof.rs b/src/lune/builtins/task/tof.rs deleted file mode 100644 index e63cd2b..0000000 --- a/src/lune/builtins/task/tof.rs +++ /dev/null @@ -1,30 +0,0 @@ -use mlua::prelude::*; - -#[derive(Clone)] -pub(super) enum LuaThreadOrFunction<'lua> { - Thread(LuaThread<'lua>), - Function(LuaFunction<'lua>), -} - -impl<'lua> LuaThreadOrFunction<'lua> { - pub(super) fn into_thread(self, lua: &'lua Lua) -> LuaResult> { - match self { - Self::Thread(t) => Ok(t), - Self::Function(f) => lua.create_thread(f), - } - } -} - -impl<'lua> FromLua<'lua> for LuaThreadOrFunction<'lua> { - fn from_lua(value: LuaValue<'lua>, _: &'lua Lua) -> LuaResult { - match value { - LuaValue::Thread(t) => Ok(Self::Thread(t)), - LuaValue::Function(f) => Ok(Self::Function(f)), - value => Err(LuaError::FromLuaConversionError { - from: value.type_name(), - to: "LuaThreadOrFunction", - message: Some("Expected thread or function".to_string()), - }), - } - } -} diff --git a/src/lune/globals/mod.rs b/src/lune/globals/mod.rs index 9605aa0..1d8700c 100644 --- a/src/lune/globals/mod.rs +++ b/src/lune/globals/mod.rs @@ -8,7 +8,7 @@ mod require; mod version; mod warn; -pub fn inject_all(lua: &'static Lua) -> LuaResult<()> { +pub fn inject_all(lua: &Lua) -> LuaResult<()> { let all = TableBuilder::new(lua)? .with_value("_G", g_table::create(lua)?)? .with_value("_VERSION", version::create(lua)?)? diff --git a/src/lune/globals/require/alias.rs b/src/lune/globals/require/alias.rs index a4cf199..df8c998 100644 --- a/src/lune/globals/require/alias.rs +++ b/src/lune/globals/require/alias.rs @@ -9,7 +9,8 @@ use crate::lune::util::{ use super::context::*; pub(super) async fn require<'lua, 'ctx>( - ctx: &'ctx RequireContext<'lua>, + lua: &'lua Lua, + ctx: &'ctx RequireContext, source: &str, alias: &str, path: &str, @@ -71,5 +72,5 @@ where LuaError::runtime(format!("failed to find relative path for alias '{alias}'")) })?; - super::path::require_abs_rel(ctx, abs_path, rel_path).await + super::path::require_abs_rel(lua, ctx, abs_path, rel_path).await } diff --git a/src/lune/globals/require/builtin.rs b/src/lune/globals/require/builtin.rs index c4550dd..42302cf 100644 --- a/src/lune/globals/require/builtin.rs +++ b/src/lune/globals/require/builtin.rs @@ -3,12 +3,12 @@ use mlua::prelude::*; use super::context::*; pub(super) async fn require<'lua, 'ctx>( - ctx: &'ctx RequireContext<'lua>, + lua: &'lua Lua, + ctx: &'ctx RequireContext, name: &str, ) -> LuaResult> where 'lua: 'ctx, - 'lua: 'static, // FIXME: Remove static lifetime bound here when builtin libraries no longer need it { - ctx.load_builtin(name) + ctx.load_builtin(lua, name) } diff --git a/src/lune/globals/require/context.rs b/src/lune/globals/require/context.rs index 17522e1..0c95d80 100644 --- a/src/lune/globals/require/context.rs +++ b/src/lune/globals/require/context.rs @@ -5,6 +5,7 @@ use std::{ }; use mlua::prelude::*; +use mlua_luau_scheduler::LuaSchedulerExt; use tokio::{ fs, sync::{ @@ -13,11 +14,7 @@ use tokio::{ }, }; -use crate::lune::{ - builtins::LuneBuiltin, - scheduler::{IntoLuaThread, Scheduler}, - util::paths::CWD, -}; +use crate::lune::{builtins::LuneBuiltin, util::paths::CWD}; /** Context containing cached results for all `require` operations. @@ -26,14 +23,13 @@ use crate::lune::{ path will first be transformed into an absolute path. */ #[derive(Debug, Clone)] -pub(super) struct RequireContext<'lua> { - lua: &'lua Lua, +pub(super) struct RequireContext { cache_builtins: Arc>>>, cache_results: Arc>>>, cache_pending: Arc>>>, } -impl<'lua> RequireContext<'lua> { +impl RequireContext { /** Creates a new require context for the given [`Lua`] struct. @@ -41,9 +37,8 @@ impl<'lua> RequireContext<'lua> { context should be created per [`Lua`] struct, creating more than one context may lead to undefined require-behavior. */ - pub fn new(lua: &'lua Lua) -> Self { + pub fn new() -> Self { Self { - lua, cache_builtins: Arc::new(AsyncMutex::new(HashMap::new())), cache_results: Arc::new(AsyncMutex::new(HashMap::new())), cache_pending: Arc::new(AsyncMutex::new(HashMap::new())), @@ -107,7 +102,11 @@ impl<'lua> RequireContext<'lua> { Will panic if the path has not been cached, use [`is_cached`] first. */ - pub fn get_from_cache(&self, abs_path: impl AsRef) -> LuaResult> { + pub fn get_from_cache<'lua>( + &self, + lua: &'lua Lua, + abs_path: impl AsRef, + ) -> LuaResult> { let results = self .cache_results .try_lock() @@ -119,8 +118,7 @@ impl<'lua> RequireContext<'lua> { match cached { Err(e) => Err(e.clone()), Ok(k) => { - let multi_vec = self - .lua + let multi_vec = lua .registry_value::>(k) .expect("Missing require result in lua registry"); Ok(LuaMultiValue::from_vec(multi_vec)) @@ -133,8 +131,9 @@ impl<'lua> RequireContext<'lua> { Will panic if the path has not been cached, use [`is_cached`] first. */ - pub async fn wait_for_cache( + pub async fn wait_for_cache<'lua>( &self, + lua: &'lua Lua, abs_path: impl AsRef, ) -> LuaResult> { let mut thread_recv = { @@ -150,43 +149,37 @@ impl<'lua> RequireContext<'lua> { thread_recv.recv().await.into_lua_err()?; - self.get_from_cache(abs_path.as_ref()) + self.get_from_cache(lua, abs_path.as_ref()) } - async fn load( + async fn load<'lua>( &self, + lua: &'lua Lua, abs_path: impl AsRef, rel_path: impl AsRef, ) -> LuaResult { let abs_path = abs_path.as_ref(); let rel_path = rel_path.as_ref(); - let sched = self - .lua - .app_data_ref::<&Scheduler>() - .expect("Lua struct is missing scheduler"); - // Read the file at the given path, try to parse and // load it into a new lua thread that we can schedule let file_contents = fs::read(&abs_path).await?; - let file_thread = self - .lua + let file_thread = lua .load(file_contents) - .set_name(rel_path.to_string_lossy().to_string()) - .into_function()? - .into_lua_thread(self.lua)?; + .set_name(rel_path.to_string_lossy().to_string()); // Schedule the thread to run, wait for it to finish running - let thread_id = sched.push_back(self.lua, file_thread, ())?; - let thread_res = sched.wait_for_thread(self.lua, thread_id).await; + let thread_id = lua.push_thread_back(file_thread, ())?; + lua.track_thread(thread_id); + lua.wait_for_thread(thread_id).await; + let thread_res = lua.get_thread_result(thread_id).unwrap(); // Return the result of the thread, storing any lua value(s) in the registry match thread_res { Err(e) => Err(e), Ok(v) => { let multi_vec = v.into_vec(); - let multi_key = self - .lua + let multi_key = lua .create_registry_value(multi_vec) .expect("Failed to store require result in registry - out of memory"); Ok(multi_key) @@ -197,8 +190,9 @@ impl<'lua> RequireContext<'lua> { /** Loads (requires) the file at the given path. */ - pub async fn load_with_caching( + pub async fn load_with_caching<'lua>( &self, + lua: &'lua Lua, abs_path: impl AsRef, rel_path: impl AsRef, ) -> LuaResult> { @@ -213,12 +207,11 @@ impl<'lua> RequireContext<'lua> { .insert(abs_path.to_path_buf(), broadcast_tx); // Try to load at this abs path - let load_res = self.load(abs_path, rel_path).await; + let load_res = self.load(lua, abs_path, rel_path).await; let load_val = match &load_res { Err(e) => Err(e.clone()), Ok(k) => { - let multi_vec = self - .lua + let multi_vec = lua .registry_value::>(k) .expect("Failed to fetch require result from registry"); Ok(LuaMultiValue::from_vec(multi_vec)) @@ -250,10 +243,11 @@ impl<'lua> RequireContext<'lua> { /** Loads (requires) the builtin with the given name. */ - pub fn load_builtin(&self, name: impl AsRef) -> LuaResult> - where - 'lua: 'static, // FIXME: Remove static lifetime bound here when builtin libraries no longer need it - { + pub fn load_builtin<'lua>( + &self, + lua: &'lua Lua, + name: impl AsRef, + ) -> LuaResult> { let builtin: LuneBuiltin = match name.as_ref().parse() { Err(e) => return Err(LuaError::runtime(e)), Ok(b) => b, @@ -268,8 +262,7 @@ impl<'lua> RequireContext<'lua> { return match res { Err(e) => return Err(e.clone()), Ok(key) => { - let multi_vec = self - .lua + let multi_vec = lua .registry_value::>(key) .expect("Missing builtin result in lua registry"); Ok(LuaMultiValue::from_vec(multi_vec)) @@ -277,7 +270,7 @@ impl<'lua> RequireContext<'lua> { }; }; - let result = builtin.create(self.lua); + let result = builtin.create(lua); cache.insert( builtin, @@ -285,8 +278,7 @@ impl<'lua> RequireContext<'lua> { Err(e) => Err(e), Ok(multi) => { let multi_vec = multi.into_vec(); - let multi_key = self - .lua + let multi_key = lua .create_registry_value(multi_vec) .expect("Failed to store require result in registry - out of memory"); Ok(multi_key) diff --git a/src/lune/globals/require/mod.rs b/src/lune/globals/require/mod.rs index 3ba89a0..1a83e58 100644 --- a/src/lune/globals/require/mod.rs +++ b/src/lune/globals/require/mod.rs @@ -1,6 +1,6 @@ use mlua::prelude::*; -use crate::lune::{scheduler::LuaSchedulerExt, util::TableBuilder}; +use crate::lune::util::TableBuilder; mod context; use context::RequireContext; @@ -13,8 +13,8 @@ const REQUIRE_IMPL: &str = r#" return require(source(), ...) "#; -pub fn create(lua: &'static Lua) -> LuaResult> { - lua.set_app_data(RequireContext::new(lua)); +pub fn create(lua: &Lua) -> LuaResult> { + lua.set_app_data(RequireContext::new()); /* Require implementation needs a few workarounds: @@ -62,10 +62,7 @@ pub fn create(lua: &'static Lua) -> LuaResult> { async fn require<'lua>( lua: &'lua Lua, (source, path): (LuaString<'lua>, LuaString<'lua>), -) -> LuaResult> -where - 'lua: 'static, // FIXME: Remove static lifetime bound here when builtin libraries no longer need it -{ +) -> LuaResult> { let source = source .to_str() .into_lua_err() @@ -86,13 +83,13 @@ where .strip_prefix("@lune/") .map(|name| name.to_ascii_lowercase()) { - builtin::require(&context, &builtin_name).await + builtin::require(lua, &context, &builtin_name).await } else if let Some(aliased_path) = path.strip_prefix('@') { let (alias, path) = aliased_path.split_once('/').ok_or(LuaError::runtime( "Require with custom alias must contain '/' delimiter", ))?; - alias::require(&context, &source, alias, path).await + alias::require(lua, &context, &source, alias, path).await } else { - path::require(&context, &source, &path).await + path::require(lua, &context, &source, &path).await } } diff --git a/src/lune/globals/require/path.rs b/src/lune/globals/require/path.rs index 7333a5b..3777b7e 100644 --- a/src/lune/globals/require/path.rs +++ b/src/lune/globals/require/path.rs @@ -5,7 +5,8 @@ use mlua::prelude::*; use super::context::*; pub(super) async fn require<'lua, 'ctx>( - ctx: &'ctx RequireContext<'lua>, + lua: &'lua Lua, + ctx: &'ctx RequireContext, source: &str, path: &str, ) -> LuaResult> @@ -13,11 +14,12 @@ where 'lua: 'ctx, { let (abs_path, rel_path) = ctx.resolve_paths(source, path)?; - require_abs_rel(ctx, abs_path, rel_path).await + require_abs_rel(lua, ctx, abs_path, rel_path).await } pub(super) async fn require_abs_rel<'lua, 'ctx>( - ctx: &'ctx RequireContext<'lua>, + lua: &'lua Lua, + ctx: &'ctx RequireContext, abs_path: PathBuf, // Absolute to filesystem rel_path: PathBuf, // Relative to CWD (for displaying) ) -> LuaResult> @@ -25,7 +27,7 @@ where 'lua: 'ctx, { // 1. Try to require the exact path - if let Ok(res) = require_inner(ctx, &abs_path, &rel_path).await { + if let Ok(res) = require_inner(lua, ctx, &abs_path, &rel_path).await { return Ok(res); } @@ -34,7 +36,7 @@ where append_extension(&abs_path, "luau"), append_extension(&rel_path, "luau"), ); - if let Ok(res) = require_inner(ctx, &luau_abs_path, &luau_rel_path).await { + if let Ok(res) = require_inner(lua, ctx, &luau_abs_path, &luau_rel_path).await { return Ok(res); } @@ -43,7 +45,7 @@ where append_extension(&abs_path, "lua"), append_extension(&rel_path, "lua"), ); - if let Ok(res) = require_inner(ctx, &lua_abs_path, &lua_rel_path).await { + if let Ok(res) = require_inner(lua, ctx, &lua_abs_path, &lua_rel_path).await { return Ok(res); } @@ -57,7 +59,7 @@ where append_extension(&abs_init, "luau"), append_extension(&rel_init, "luau"), ); - if let Ok(res) = require_inner(ctx, &luau_abs_init, &luau_rel_init).await { + if let Ok(res) = require_inner(lua, ctx, &luau_abs_init, &luau_rel_init).await { return Ok(res); } @@ -66,7 +68,7 @@ where append_extension(&abs_init, "lua"), append_extension(&rel_init, "lua"), ); - if let Ok(res) = require_inner(ctx, &lua_abs_init, &lua_rel_init).await { + if let Ok(res) = require_inner(lua, ctx, &lua_abs_init, &lua_rel_init).await { return Ok(res); } @@ -78,7 +80,8 @@ where } async fn require_inner<'lua, 'ctx>( - ctx: &'ctx RequireContext<'lua>, + lua: &'lua Lua, + ctx: &'ctx RequireContext, abs_path: impl AsRef, rel_path: impl AsRef, ) -> LuaResult> @@ -89,11 +92,11 @@ where let rel_path = rel_path.as_ref(); if ctx.is_cached(abs_path)? { - ctx.get_from_cache(abs_path) + ctx.get_from_cache(lua, abs_path) } else if ctx.is_pending(abs_path)? { - ctx.wait_for_cache(&abs_path).await + ctx.wait_for_cache(lua, &abs_path).await } else { - ctx.load_with_caching(&abs_path, &rel_path).await + ctx.load_with_caching(lua, &abs_path, &rel_path).await } } diff --git a/src/lune/mod.rs b/src/lune/mod.rs index 6eda448..aa2b4b8 100644 --- a/src/lune/mod.rs +++ b/src/lune/mod.rs @@ -1,47 +1,42 @@ -use std::process::ExitCode; +use std::{ + process::ExitCode, + rc::Rc, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, +}; use mlua::Lua; +use mlua_luau_scheduler::Scheduler; mod builtins; mod error; mod globals; -mod scheduler; pub(crate) mod util; -use self::scheduler::{LuaSchedulerExt, Scheduler}; - pub use error::RuntimeError; -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct Runtime { - lua: &'static Lua, - scheduler: &'static Scheduler<'static>, + lua: Rc, args: Vec, } impl Runtime { /** - Creates a new Lune runtime, with a new Luau VM and task scheduler. + Creates a new Lune runtime, with a new Luau VM. */ #[allow(clippy::new_without_default)] pub fn new() -> Self { - /* - FUTURE: Stop leaking these when we have removed the lifetime - on the scheduler and can place them in lua app data using arc + let lua = Rc::new(Lua::new()); - See the scheduler struct for more notes - */ - let lua = Lua::new().into_static(); - let scheduler = Scheduler::new().into_static(); - - lua.set_scheduler(scheduler); + lua.set_app_data(Rc::downgrade(&lua)); lua.set_app_data(Vec::::new()); - globals::inject_all(lua).expect("Failed to inject lua globals"); Self { lua, - scheduler, args: Vec::new(), } } @@ -68,13 +63,35 @@ impl Runtime { script_name: impl AsRef, script_contents: impl AsRef<[u8]>, ) -> Result { + // Create a new scheduler for this run + let sched = Scheduler::new(&self.lua); + globals::inject_all(&self.lua)?; + + // Add error callback to format errors nicely + store status + let got_any_error = Arc::new(AtomicBool::new(false)); + let got_any_inner = Arc::clone(&got_any_error); + sched.set_error_callback(move |e| { + got_any_inner.store(true, Ordering::SeqCst); + eprintln!("{}", RuntimeError::from(e)); + }); + + // Load our "main" thread let main = self .lua .load(script_contents.as_ref()) .set_name(script_name.as_ref()); - self.scheduler.push_back(self.lua, main, ())?; + // Run it on our scheduler until it and any other spawned threads complete + sched.push_thread_back(main, ())?; + sched.run().await; - Ok(self.scheduler.run_to_completion(self.lua).await) + // Return the exit code - default to FAILURE if we got any errors + Ok(sched.get_exit_code().unwrap_or({ + if got_any_error.load(Ordering::SeqCst) { + ExitCode::FAILURE + } else { + ExitCode::SUCCESS + } + })) } } diff --git a/src/lune/scheduler/impl_async.rs b/src/lune/scheduler/impl_async.rs deleted file mode 100644 index 019b743..0000000 --- a/src/lune/scheduler/impl_async.rs +++ /dev/null @@ -1,138 +0,0 @@ -use futures_util::Future; -use mlua::prelude::*; -use tokio::{ - sync::oneshot::{self, Receiver}, - task, -}; - -use super::{IntoLuaThread, Scheduler}; - -impl<'fut> Scheduler<'fut> { - /** - Checks if there are any futures to run, for - lua futures and background futures respectively. - */ - pub(super) fn has_futures(&self) -> (bool, bool) { - ( - self.futures_lua - .try_lock() - .expect("Failed to lock lua futures for check") - .len() - > 0, - self.futures_background - .try_lock() - .expect("Failed to lock background futures for check") - .len() - > 0, - ) - } - - /** - Schedules a plain future to run in the background. - - This will potentially spawn the future on a different thread, using - [`task::spawn`], meaning the provided future must implement [`Send`]. - - Returns a [`Receiver`] which may be `await`-ed - to retrieve the result of the spawned future. - - This [`Receiver`] may be safely ignored if the result of the - spawned future is not needed, the future will run either way. - */ - pub fn spawn(&self, fut: F) -> Receiver - where - F: Future + Send + 'static, - F::Output: Send + 'static, - { - let (tx, rx) = oneshot::channel(); - - let handle = task::spawn(async move { - let res = fut.await; - tx.send(res).ok(); - }); - - // NOTE: We must spawn a future on our scheduler which awaits - // the handle from tokio to start driving our future properly - let futs = self - .futures_background - .try_lock() - .expect("Failed to lock futures queue for background tasks"); - futs.push(Box::pin(async move { - handle.await.ok(); - })); - - // NOTE: We might be resuming lua futures, need to signal that a - // new background future is ready to break out of futures resumption - self.state.message_sender().send_spawned_background_future(); - - rx - } - - /** - Equivalent to [`spawn`], except the future is only - spawned on the Lune scheduler, and on the main thread. - */ - pub fn spawn_local(&self, fut: F) -> Receiver - where - F: Future + 'static, - F::Output: 'static, - { - let (tx, rx) = oneshot::channel(); - - let futs = self - .futures_background - .try_lock() - .expect("Failed to lock futures queue for background tasks"); - futs.push(Box::pin(async move { - let res = fut.await; - tx.send(res).ok(); - })); - - // NOTE: We might be resuming lua futures, need to signal that a - // new background future is ready to break out of futures resumption - self.state.message_sender().send_spawned_background_future(); - - rx - } - - /** - Schedules the given `thread` to run when the given `fut` completes. - - If the given future returns a [`LuaError`], that error will be passed to the given `thread`. - */ - pub fn spawn_thread( - &'fut self, - lua: &'fut Lua, - thread: impl IntoLuaThread<'fut>, - fut: F, - ) -> LuaResult<()> - where - FR: IntoLuaMulti<'fut>, - F: Future> + 'fut, - { - let thread = thread.into_lua_thread(lua)?; - let futs = self.futures_lua.try_lock().expect( - "Failed to lock futures queue - \ - can't schedule future lua threads during futures resumption", - ); - - futs.push(Box::pin(async move { - match fut.await.and_then(|rets| rets.into_lua_multi(lua)) { - Err(e) => { - self.push_err(lua, thread, e) - .expect("Failed to schedule future err thread"); - } - Ok(v) => { - self.push_back(lua, thread, v) - .expect("Failed to schedule future thread"); - } - } - })); - - // NOTE: We might be resuming background futures, need to signal that a - // new background future is ready to break out of futures resumption - self.state.message_sender().send_spawned_lua_future(); - - Ok(()) - } -} diff --git a/src/lune/scheduler/impl_runner.rs b/src/lune/scheduler/impl_runner.rs deleted file mode 100644 index 55505a0..0000000 --- a/src/lune/scheduler/impl_runner.rs +++ /dev/null @@ -1,265 +0,0 @@ -use std::{process::ExitCode, sync::Arc}; - -use futures_util::StreamExt; -use mlua::prelude::*; - -use tokio::task::LocalSet; -use tracing::debug; - -use crate::lune::util::traits::LuaEmitErrorExt; - -use super::Scheduler; - -impl<'fut> Scheduler<'fut> { - /** - Runs all lua threads to completion. - */ - fn run_lua_threads(&self, lua: &Lua) { - if self.state.has_exit_code() { - return; - } - - let mut count = 0; - - // Pop threads from the scheduler until there are none left - while let Some(thread) = self - .pop_thread() - .expect("Failed to pop thread from scheduler") - { - // Deconstruct the scheduler thread into its parts - let thread_id = thread.id(); - let (thread, args) = thread.into_inner(lua); - - // Make sure this thread is still resumable, it might have - // been resumed somewhere else or even have been cancelled - if thread.status() != LuaThreadStatus::Resumable { - continue; - } - - // Resume the thread, ensuring that the schedulers - // current thread id is set correctly for error catching - self.state.set_current_thread_id(Some(thread_id)); - let res = thread.resume::<_, LuaMultiValue>(args); - self.state.set_current_thread_id(None); - - count += 1; - - // If we got any resumption (lua-side) error, increment - // the error count of the scheduler so we can exit with - // a non-zero exit code, and print it out to stderr - if let Err(err) = &res { - self.state.increment_error_count(); - lua.emit_error(err.clone()); - } - - // If the thread has finished running completely, - // send results of final resume to any listeners - if thread.status() != LuaThreadStatus::Resumable { - // NOTE: Threads that were spawned to resume - // with an error will not have a result sender - if let Some(sender) = self - .thread_senders - .try_lock() - .expect("Failed to get thread senders") - .remove(&thread_id) - { - if sender.receiver_count() > 0 { - let stored = match res { - Err(e) => Err(e), - Ok(v) => Ok(Arc::new(lua.create_registry_value(v.into_vec()).expect( - "Failed to store thread results in registry - out of memory", - ))), - }; - sender - .send(stored) - .expect("Failed to broadcast thread results"); - } - } - } - - if self.state.has_exit_code() { - break; - } - } - - if count > 0 { - debug! { - %count, - "resumed lua" - } - } - } - - /** - Runs the next lua future to completion. - - Panics if no lua future is queued. - */ - async fn run_future_lua(&self) { - let mut futs = self - .futures_lua - .try_lock() - .expect("Failed to lock lua futures for resumption"); - assert!(futs.len() > 0, "No lua futures are queued"); - futs.next().await; - } - - /** - Runs the next background future to completion. - - Panics if no background future is queued. - */ - async fn run_future_background(&self) { - let mut futs = self - .futures_background - .try_lock() - .expect("Failed to lock background futures for resumption"); - assert!(futs.len() > 0, "No background futures are queued"); - futs.next().await; - } - - /** - Runs as many futures as possible, until a new lua thread - is ready, or an exit code has been set for the scheduler. - - ### Implementation details - - Running futures on our scheduler consists of a couple moving parts: - - 1. An unordered futures queue for lua (main thread, local) futures - 2. An unordered futures queue for background (multithreaded, 'static lifetime) futures - 3. A signal for breaking out of futures resumption - - The two unordered futures queues need to run concurrently, - but since `FuturesUnordered` returns instantly if it does - not currently have any futures queued on it, we need to do - this branching loop, checking if each queue has futures first. - - We also need to listen for our signal, to see if we should break out of resumption: - - * Always break out of resumption if a new lua thread is ready - * Always break out of resumption if an exit code has been set - * Break out of lua futures resumption if we have a new background future - * Break out of background futures resumption if we have a new lua future - - We need to listen for both future queues concurrently, - and break out whenever the other corresponding queue has - a new future, since the other queue may resume sooner. - */ - async fn run_futures(&self) { - let (mut has_lua, mut has_background) = self.has_futures(); - if !has_lua && !has_background { - return; - } - - let mut rx = self.state.message_receiver(); - let mut count = 0; - - while has_lua || has_background { - if has_lua && has_background { - tokio::select! { - _ = self.run_future_lua() => {}, - _ = self.run_future_background() => {}, - msg = rx.recv() => { - if let Some(msg) = msg { - if msg.should_break_futures() { - break; - } - } - } - } - count += 1; - } else if has_lua { - tokio::select! { - _ = self.run_future_lua() => {}, - msg = rx.recv() => { - if let Some(msg) = msg { - if msg.should_break_lua_futures() { - break; - } - } - } - } - count += 1; - } else if has_background { - tokio::select! { - _ = self.run_future_background() => {}, - msg = rx.recv() => { - if let Some(msg) = msg { - if msg.should_break_background_futures() { - break; - } - } - } - } - count += 1; - } - (has_lua, has_background) = self.has_futures(); - } - - if count > 0 { - debug! { - %count, - "resumed lua futures" - } - } - } - - /** - Runs the scheduler to completion in a [`LocalSet`], - both normal lua threads and futures, prioritizing - lua threads over completion of any pending futures. - - Will emit lua output and errors to stdout and stderr. - */ - pub async fn run_to_completion(&self, lua: &Lua) -> ExitCode { - if let Some(code) = self.state.exit_code() { - return ExitCode::from(code); - } - - let set = LocalSet::new(); - let _guard = set.enter(); - - loop { - // 1. Run lua threads until exit or there are none left - self.run_lua_threads(lua); - - // 2. If we got a manual exit code from lua we should - // not try to wait for any pending futures to complete - if self.state.has_exit_code() { - break; - } - - // 3. Keep resuming futures until there are no futures left to - // resume, or until we manually break out of resumption for any - // reason, this may be because a future spawned a new lua thread - self.run_futures().await; - - // 4. Once again, check for an exit code, in case a future sets one - if self.state.has_exit_code() { - break; - } - - // 5. If we have no lua threads or futures remaining, - // we have now run the scheduler until completion - let (has_future_lua, has_future_background) = self.has_futures(); - if !has_future_lua && !has_future_background && !self.has_thread() { - break; - } - } - - if let Some(code) = self.state.exit_code() { - debug! { - %code, - "scheduler ran to completion" - }; - ExitCode::from(code) - } else if self.state.has_errored() { - debug!("scheduler ran to completion, with failure"); - ExitCode::FAILURE - } else { - debug!("scheduler ran to completion, with success"); - ExitCode::SUCCESS - } - } -} diff --git a/src/lune/scheduler/impl_threads.rs b/src/lune/scheduler/impl_threads.rs deleted file mode 100644 index 8f3d174..0000000 --- a/src/lune/scheduler/impl_threads.rs +++ /dev/null @@ -1,185 +0,0 @@ -use std::sync::Arc; - -use mlua::prelude::*; - -use super::{ - thread::{SchedulerThread, SchedulerThreadId, SchedulerThreadSender}, - IntoLuaThread, Scheduler, -}; - -impl<'fut> Scheduler<'fut> { - /** - Checks if there are any lua threads to run. - */ - pub(super) fn has_thread(&self) -> bool { - !self - .threads - .try_lock() - .expect("Failed to lock threads vec") - .is_empty() - } - - /** - Pops the next thread to run, from the front of the scheduler. - - Returns `None` if there are no threads left to run. - */ - pub(super) fn pop_thread(&self) -> LuaResult> { - match self - .threads - .try_lock() - .into_lua_err() - .context("Failed to lock threads vec")? - .pop_front() - { - Some(thread) => Ok(Some(thread)), - None => Ok(None), - } - } - - /** - Schedules the `thread` to be resumed with the given [`LuaError`]. - */ - pub fn push_err<'a>( - &self, - lua: &'a Lua, - thread: impl IntoLuaThread<'a>, - err: LuaError, - ) -> LuaResult<()> { - let thread = thread.into_lua_thread(lua)?; - let args = LuaMultiValue::new(); // Will be resumed with error, don't need real args - - let thread = SchedulerThread::new(lua, thread, args); - let thread_id = thread.id(); - - self.state.set_thread_error(thread_id, err); - self.threads - .try_lock() - .into_lua_err() - .context("Failed to lock threads vec")? - .push_front(thread); - - // NOTE: We might be resuming futures, need to signal that a - // new lua thread is ready to break out of futures resumption - self.state.message_sender().send_pushed_lua_thread(); - - Ok(()) - } - - /** - Schedules the `thread` to be resumed with the given `args` - right away, before any other currently scheduled threads. - */ - pub fn push_front<'a>( - &self, - lua: &'a Lua, - thread: impl IntoLuaThread<'a>, - args: impl IntoLuaMulti<'a>, - ) -> LuaResult { - let thread = thread.into_lua_thread(lua)?; - let args = args.into_lua_multi(lua)?; - - let thread = SchedulerThread::new(lua, thread, args); - let thread_id = thread.id(); - - self.threads - .try_lock() - .into_lua_err() - .context("Failed to lock threads vec")? - .push_front(thread); - - // NOTE: We might be resuming the same thread several times and - // pushing it to the scheduler several times before it is done, - // and we should only ever create one result sender per thread - self.thread_senders - .try_lock() - .into_lua_err() - .context("Failed to lock thread senders vec")? - .entry(thread_id) - .or_insert_with(|| SchedulerThreadSender::new(1)); - - // NOTE: We might be resuming futures, need to signal that a - // new lua thread is ready to break out of futures resumption - self.state.message_sender().send_pushed_lua_thread(); - - Ok(thread_id) - } - - /** - Schedules the `thread` to be resumed with the given `args` - after all other current threads have been resumed. - */ - pub fn push_back<'a>( - &self, - lua: &'a Lua, - thread: impl IntoLuaThread<'a>, - args: impl IntoLuaMulti<'a>, - ) -> LuaResult { - let thread = thread.into_lua_thread(lua)?; - let args = args.into_lua_multi(lua)?; - - let thread = SchedulerThread::new(lua, thread, args); - let thread_id = thread.id(); - - self.threads - .try_lock() - .into_lua_err() - .context("Failed to lock threads vec")? - .push_back(thread); - - // NOTE: We might be resuming the same thread several times and - // pushing it to the scheduler several times before it is done, - // and we should only ever create one result sender per thread - self.thread_senders - .try_lock() - .into_lua_err() - .context("Failed to lock thread senders vec")? - .entry(thread_id) - .or_insert_with(|| SchedulerThreadSender::new(1)); - - // NOTE: We might be resuming futures, need to signal that a - // new lua thread is ready to break out of futures resumption - self.state.message_sender().send_pushed_lua_thread(); - - Ok(thread_id) - } - - /** - Waits for the given thread to finish running, and returns its result. - */ - pub async fn wait_for_thread<'a>( - &self, - lua: &'a Lua, - thread_id: SchedulerThreadId, - ) -> LuaResult> { - let mut recv = { - let senders = self.thread_senders.lock().await; - let sender = senders - .get(&thread_id) - .expect("Tried to wait for thread that is not queued"); - sender.subscribe() - }; - let res = match recv.recv().await { - Err(_) => panic!("Sender was dropped while waiting for {thread_id:?}"), - Ok(r) => r, - }; - match res { - Err(e) => Err(e), - Ok(k) => { - let vals = lua - .registry_value::>(&k) - .expect("Received invalid registry key for thread"); - - // NOTE: This is not strictly necessary, mlua can clean - // up registry values on its own, but doing this will add - // some extra safety and clean up registry values faster - if let Some(key) = Arc::into_inner(k) { - lua.remove_registry_value(key) - .expect("Failed to remove registry key for thread"); - } - - Ok(LuaMultiValue::from_vec(vals)) - } - } - } -} diff --git a/src/lune/scheduler/message.rs b/src/lune/scheduler/message.rs deleted file mode 100644 index 4d05343..0000000 --- a/src/lune/scheduler/message.rs +++ /dev/null @@ -1,98 +0,0 @@ -use std::sync::{MutexGuard, TryLockError}; - -use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender}; - -use super::state::SchedulerState; - -#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] -pub(crate) enum SchedulerMessage { - ExitCodeSet, - PushedLuaThread, - SpawnedLuaFuture, - SpawnedBackgroundFuture, -} - -impl SchedulerMessage { - pub fn should_break_futures(self) -> bool { - matches!(self, Self::ExitCodeSet | Self::PushedLuaThread) - } - - pub fn should_break_lua_futures(self) -> bool { - self.should_break_futures() || matches!(self, Self::SpawnedBackgroundFuture) - } - - pub fn should_break_background_futures(self) -> bool { - self.should_break_futures() || matches!(self, Self::SpawnedLuaFuture) - } -} - -/** - A message sender for the scheduler. - - As long as this sender is not dropped, the scheduler - will be kept alive, waiting for more messages to arrive. -*/ -pub(crate) struct SchedulerMessageSender(UnboundedSender); - -impl SchedulerMessageSender { - /** - Creates a new message sender for the scheduler. - */ - pub fn new(state: &SchedulerState) -> Self { - Self( - state - .message_sender - .lock() - .expect("Scheduler state was poisoned") - .clone(), - ) - } - - pub fn send_exit_code_set(&self) { - self.0.send(SchedulerMessage::ExitCodeSet).ok(); - } - - pub fn send_pushed_lua_thread(&self) { - self.0.send(SchedulerMessage::PushedLuaThread).ok(); - } - - pub fn send_spawned_lua_future(&self) { - self.0.send(SchedulerMessage::SpawnedLuaFuture).ok(); - } - - pub fn send_spawned_background_future(&self) { - self.0.send(SchedulerMessage::SpawnedBackgroundFuture).ok(); - } -} - -/** - A message receiver for the scheduler. - - Only one message receiver may exist per scheduler. -*/ -pub(crate) struct SchedulerMessageReceiver<'a>(MutexGuard<'a, UnboundedReceiver>); - -impl<'a> SchedulerMessageReceiver<'a> { - /** - Creates a new message receiver for the scheduler. - - Panics if the message receiver is already being used. - */ - pub fn new(state: &'a SchedulerState) -> Self { - Self(match state.message_receiver.try_lock() { - Err(TryLockError::Poisoned(_)) => panic!("Sheduler state was poisoned"), - Err(TryLockError::WouldBlock) => { - panic!("Message receiver may only be borrowed once at a time") - } - Ok(guard) => guard, - }) - } - - // NOTE: Holding this lock across await points is fine, since we - // can only ever create lock exactly one SchedulerMessageReceiver - // See above constructor for details on this - #[allow(clippy::await_holding_lock)] - pub async fn recv(&mut self) -> Option { - self.0.recv().await - } -} diff --git a/src/lune/scheduler/mod.rs b/src/lune/scheduler/mod.rs deleted file mode 100644 index 67c7799..0000000 --- a/src/lune/scheduler/mod.rs +++ /dev/null @@ -1,120 +0,0 @@ -use std::{ - collections::{HashMap, VecDeque}, - pin::Pin, - sync::Arc, -}; - -use futures_util::{stream::FuturesUnordered, Future}; -use mlua::prelude::*; -use tokio::sync::Mutex as AsyncMutex; - -mod message; -mod state; -mod thread; -mod traits; - -mod impl_async; -mod impl_runner; -mod impl_threads; - -pub use self::thread::SchedulerThreadId; -pub use self::traits::*; - -use self::{ - state::SchedulerState, - thread::{SchedulerThread, SchedulerThreadSender}, -}; - -type SchedulerFuture<'fut> = Pin + 'fut>>; - -/** - Scheduler for Lua threads and futures. - - This scheduler can be cheaply cloned and the underlying state - and data will remain unchanged and accessible from all clones. -*/ -#[derive(Debug, Clone)] -pub(crate) struct Scheduler<'fut> { - state: Arc, - threads: Arc>>, - thread_senders: Arc>>, - /* - FUTURE: Get rid of these, let the tokio runtime handle running - and resumption of futures completely, just use our scheduler - state and receiver to know when we have run to completion. - If we have no senders left, we have run to completion. - - We should also investigate using smol / async-executor and its - LocalExecutor struct which does not impose the 'static lifetime - restriction on all of the futures spawned on it, unlike tokio. - - If we no longer store futures directly in our scheduler, we - can get rid of the lifetime on it, store it in our lua app - data as a Weak, together with a Weak. - - In our lua async functions we can then get a reference to this, - upgrade it to an Arc and Arc to extend lifetimes, - and hopefully get rid of Box::leak and 'static lifetimes for good. - - Relevant comment on the mlua repository: - https://github.com/khvzak/mlua/issues/169#issuecomment-1138863979 - */ - futures_lua: Arc>>>, - futures_background: Arc>>>, -} - -impl<'fut> Scheduler<'fut> { - /** - Creates a new scheduler. - */ - #[allow(clippy::arc_with_non_send_sync)] // FIXME: Clippy lints our tokio mutexes that are definitely Send + Sync - pub fn new() -> Self { - Self { - state: Arc::new(SchedulerState::new()), - threads: Arc::new(AsyncMutex::new(VecDeque::new())), - thread_senders: Arc::new(AsyncMutex::new(HashMap::new())), - futures_lua: Arc::new(AsyncMutex::new(FuturesUnordered::new())), - futures_background: Arc::new(AsyncMutex::new(FuturesUnordered::new())), - } - } - - /** - Sets the luau interrupt for this scheduler. - - This will propagate errors from any lua-spawned - futures back to the lua threads that spawned them. - */ - pub fn set_interrupt_for(&self, lua: &Lua) { - // Propagate errors given to the scheduler back to their lua threads - // FUTURE: Do profiling and anything else we need inside of this interrupt - let state = self.state.clone(); - lua.set_interrupt(move |_| { - if let Some(id) = state.get_current_thread_id() { - if let Some(err) = state.get_thread_error(id) { - return Err(err); - } - } - Ok(LuaVmState::Continue) - }); - } - - /** - Sets the exit code for the scheduler. - - This will stop the scheduler from resuming any more lua threads or futures. - - Panics if the exit code is set more than once. - */ - pub fn set_exit_code(&self, code: impl Into) { - assert!( - self.state.exit_code().is_none(), - "Exit code may only be set exactly once" - ); - self.state.set_exit_code(code.into()); - } - - #[doc(hidden)] - pub fn into_static(self) -> &'static Self { - Box::leak(Box::new(self)) - } -} diff --git a/src/lune/scheduler/state.rs b/src/lune/scheduler/state.rs deleted file mode 100644 index d6682f1..0000000 --- a/src/lune/scheduler/state.rs +++ /dev/null @@ -1,176 +0,0 @@ -use std::{ - collections::HashMap, - sync::{ - atomic::{AtomicBool, AtomicU8, AtomicUsize, Ordering}, - Arc, Mutex, - }, -}; - -use mlua::Error as LuaError; - -use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; - -use super::{ - message::{SchedulerMessage, SchedulerMessageReceiver, SchedulerMessageSender}, - SchedulerThreadId, -}; - -/** - Internal state for a [`Scheduler`]. - - This scheduler state uses atomic operations for everything - except lua error storage, and is completely thread safe. -*/ -#[derive(Debug)] -pub(crate) struct SchedulerState { - exit_state: AtomicBool, - exit_code: AtomicU8, - num_resumptions: AtomicUsize, - num_errors: AtomicUsize, - thread_id: Arc>>, - thread_errors: Arc>>, - pub(super) message_sender: Arc>>, - pub(super) message_receiver: Arc>>, -} - -impl SchedulerState { - /** - Creates a new scheduler state. - */ - pub fn new() -> Self { - let (message_sender, message_receiver) = unbounded_channel(); - - Self { - exit_state: AtomicBool::new(false), - exit_code: AtomicU8::new(0), - num_resumptions: AtomicUsize::new(0), - num_errors: AtomicUsize::new(0), - thread_id: Arc::new(Mutex::new(None)), - thread_errors: Arc::new(Mutex::new(HashMap::new())), - message_sender: Arc::new(Mutex::new(message_sender)), - message_receiver: Arc::new(Mutex::new(message_receiver)), - } - } - - /** - Increments the total lua error count for the scheduler. - - This is used to determine if the scheduler should exit with - a non-zero exit code, when no exit code is explicitly set. - */ - pub fn increment_error_count(&self) { - self.num_errors.fetch_add(1, Ordering::Relaxed); - } - - /** - Checks if there have been any lua errors. - - This is used to determine if the scheduler should exit with - a non-zero exit code, when no exit code is explicitly set. - */ - pub fn has_errored(&self) -> bool { - self.num_errors.load(Ordering::SeqCst) > 0 - } - - /** - Gets the currently set exit code for the scheduler, if any. - */ - pub fn exit_code(&self) -> Option { - if self.exit_state.load(Ordering::SeqCst) { - Some(self.exit_code.load(Ordering::SeqCst)) - } else { - None - } - } - - /** - Checks if the scheduler has an explicit exit code set. - */ - pub fn has_exit_code(&self) -> bool { - self.exit_state.load(Ordering::SeqCst) - } - - /** - Sets the explicit exit code for the scheduler. - */ - pub fn set_exit_code(&self, code: impl Into) { - self.exit_state.store(true, Ordering::SeqCst); - self.exit_code.store(code.into(), Ordering::SeqCst); - self.message_sender().send_exit_code_set(); - } - - /** - Gets the currently running lua scheduler thread id, if any. - */ - pub fn get_current_thread_id(&self) -> Option { - *self - .thread_id - .lock() - .expect("Failed to lock current thread id") - } - - /** - Sets the currently running lua scheduler thread id. - - This must be set to `Some(id)` just before resuming a lua thread, - and `None` while no lua thread is being resumed. If set to `Some` - while the current thread id is also `Some`, this will panic. - - Must only be set once per thread id, although this - is not checked at runtime for performance reasons. - */ - pub fn set_current_thread_id(&self, id: Option) { - self.num_resumptions.fetch_add(1, Ordering::Relaxed); - let mut thread_id = self - .thread_id - .lock() - .expect("Failed to lock current thread id"); - assert!( - id.is_none() || thread_id.is_none(), - "Current thread id can not be overwritten" - ); - *thread_id = id; - } - - /** - Gets the [`LuaError`] (if any) for the given `id`. - - Note that this removes the error from the scheduler state completely. - */ - pub fn get_thread_error(&self, id: SchedulerThreadId) -> Option { - let mut thread_errors = self - .thread_errors - .lock() - .expect("Failed to lock thread errors"); - thread_errors.remove(&id) - } - - /** - Sets a [`LuaError`] for the given `id`. - - Note that this will replace any already existing [`LuaError`]. - */ - pub fn set_thread_error(&self, id: SchedulerThreadId, err: LuaError) { - let mut thread_errors = self - .thread_errors - .lock() - .expect("Failed to lock thread errors"); - thread_errors.insert(id, err); - } - - /** - Creates a new message sender for the scheduler. - */ - pub fn message_sender(&self) -> SchedulerMessageSender { - SchedulerMessageSender::new(self) - } - - /** - Tries to borrow the message receiver for the scheduler. - - Panics if the message receiver is already being used. - */ - pub fn message_receiver(&self) -> SchedulerMessageReceiver { - SchedulerMessageReceiver::new(self) - } -} diff --git a/src/lune/scheduler/thread.rs b/src/lune/scheduler/thread.rs deleted file mode 100644 index ed8d083..0000000 --- a/src/lune/scheduler/thread.rs +++ /dev/null @@ -1,105 +0,0 @@ -use std::sync::Arc; - -use mlua::prelude::*; -use tokio::sync::broadcast::Sender; - -/** - Type alias for a broadcast [`Sender`], which will - broadcast the result and return values of a lua thread. - - The return values are stored in the lua registry as a - `Vec>`, and the registry key pointing to - those values will be sent using the broadcast sender. -*/ -pub type SchedulerThreadSender = Sender>>; - -/** - Unique, randomly generated id for a scheduler thread. -*/ -#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] -pub struct SchedulerThreadId(usize); - -impl From<&LuaThread<'_>> for SchedulerThreadId { - fn from(value: &LuaThread) -> Self { - // HACK: We rely on the debug format of mlua - // thread refs here, but currently this is the - // only way to get a proper unique id using mlua - let addr_string = format!("{value:?}"); - let addr = addr_string - .strip_prefix("Thread(Ref(0x") - .expect("Invalid thread address format - unknown prefix") - .split_once(')') - .map(|(s, _)| s) - .expect("Invalid thread address format - missing ')'"); - let id = usize::from_str_radix(addr, 16) - .expect("Failed to parse thread address as hexadecimal into usize"); - Self(id) - } -} - -/** - Container for registry keys that point to a thread and thread arguments. -*/ -#[derive(Debug)] -pub(super) struct SchedulerThread { - thread_id: SchedulerThreadId, - key_thread: LuaRegistryKey, - key_args: LuaRegistryKey, -} - -impl SchedulerThread { - /** - Creates a new scheduler thread container from the given thread and arguments. - - May fail if an allocation error occurs, is not fallible otherwise. - */ - pub(super) fn new<'lua>( - lua: &'lua Lua, - thread: LuaThread<'lua>, - args: LuaMultiValue<'lua>, - ) -> Self { - let args_vec = args.into_vec(); - let thread_id = SchedulerThreadId::from(&thread); - - let key_thread = lua - .create_registry_value(thread) - .expect("Failed to store thread in registry - out of memory"); - let key_args = lua - .create_registry_value(args_vec) - .expect("Failed to store thread args in registry - out of memory"); - - Self { - thread_id, - key_thread, - key_args, - } - } - - /** - Extracts the inner thread and args from the container. - */ - pub(super) fn into_inner(self, lua: &Lua) -> (LuaThread<'_>, LuaMultiValue<'_>) { - let thread = lua - .registry_value(&self.key_thread) - .expect("Failed to get thread from registry"); - let args_vec = lua - .registry_value(&self.key_args) - .expect("Failed to get thread args from registry"); - - let args = LuaMultiValue::from_vec(args_vec); - - lua.remove_registry_value(self.key_thread) - .expect("Failed to remove thread from registry"); - lua.remove_registry_value(self.key_args) - .expect("Failed to remove thread args from registry"); - - (thread, args) - } - - /** - Retrieves the unique, randomly generated id for this scheduler thread. - */ - pub(super) fn id(&self) -> SchedulerThreadId { - self.thread_id - } -} diff --git a/src/lune/scheduler/traits.rs b/src/lune/scheduler/traits.rs deleted file mode 100644 index f95c253..0000000 --- a/src/lune/scheduler/traits.rs +++ /dev/null @@ -1,118 +0,0 @@ -use futures_util::Future; -use mlua::prelude::*; - -use super::Scheduler; - -const ASYNC_IMPL_LUA: &str = r#" -schedule(...) -return yield() -"#; - -/** - Trait for extensions to the [`Lua`] struct, allowing - for access to the scheduler without having to import - it or handle registry / app data references manually. -*/ -pub(crate) trait LuaSchedulerExt<'lua> { - /** - Sets the scheduler for the [`Lua`] struct. - */ - fn set_scheduler(&'lua self, scheduler: &'lua Scheduler); - - /** - Creates a function callable from Lua that runs an async - closure and returns the results of it to the call site. - */ - fn create_async_function(&'lua self, func: F) -> LuaResult> - where - A: FromLuaMulti<'lua>, - R: IntoLuaMulti<'lua>, - F: Fn(&'lua Lua, A) -> FR + 'lua, - FR: Future> + 'lua; -} - -// FIXME: `self` escapes outside of method because we are borrowing `func` -// when we call `schedule_future_thread` in the lua function body below -// For now we solve this by using the 'static lifetime bound in the impl -impl<'lua> LuaSchedulerExt<'lua> for Lua -where - 'lua: 'static, -{ - fn set_scheduler(&'lua self, scheduler: &'lua Scheduler) { - self.set_app_data(scheduler); - scheduler.set_interrupt_for(self); - } - - fn create_async_function(&'lua self, func: F) -> LuaResult> - where - A: FromLuaMulti<'lua>, - R: IntoLuaMulti<'lua>, - F: Fn(&'lua Lua, A) -> FR + 'lua, - FR: Future> + 'lua, - { - self.app_data_ref::<&Scheduler>() - .expect("Lua must have a scheduler to create async functions"); - - let async_env = self.create_table_with_capacity(0, 2)?; - - async_env.set( - "yield", - self.globals() - .get::<_, LuaTable>("coroutine")? - .get::<_, LuaFunction>("yield")?, - )?; - - async_env.set( - "schedule", - LuaFunction::wrap(move |lua: &Lua, args: A| { - let thread = lua.current_thread(); - let future = func(lua, args); - let sched = lua - .app_data_ref::<&Scheduler>() - .expect("Lua struct is missing scheduler"); - sched.spawn_thread(lua, thread, future)?; - Ok(()) - }), - )?; - - let async_func = self - .load(ASYNC_IMPL_LUA) - .set_name("async") - .set_environment(async_env) - .into_function()?; - Ok(async_func) - } -} - -/** - Trait for any struct that can be turned into an [`LuaThread`] - and given to the scheduler, implemented for the following types: - - - Lua threads ([`LuaThread`]) - - Lua functions ([`LuaFunction`]) - - Lua chunks ([`LuaChunk`]) -*/ -pub trait IntoLuaThread<'lua> { - /** - Converts the value into a lua thread. - */ - fn into_lua_thread(self, lua: &'lua Lua) -> LuaResult>; -} - -impl<'lua> IntoLuaThread<'lua> for LuaThread<'lua> { - fn into_lua_thread(self, _: &'lua Lua) -> LuaResult> { - Ok(self) - } -} - -impl<'lua> IntoLuaThread<'lua> for LuaFunction<'lua> { - fn into_lua_thread(self, lua: &'lua Lua) -> LuaResult> { - lua.create_thread(self) - } -} - -impl<'lua, 'a> IntoLuaThread<'lua> for LuaChunk<'lua, 'a> { - fn into_lua_thread(self, lua: &'lua Lua) -> LuaResult> { - lua.create_thread(self.into_function()?) - } -} diff --git a/src/lune/util/futures.rs b/src/lune/util/futures.rs deleted file mode 100644 index 40163c1..0000000 --- a/src/lune/util/futures.rs +++ /dev/null @@ -1,18 +0,0 @@ -use std::future::Future; -use std::pin::Pin; -use std::task::{Context, Poll}; - -#[derive(Debug, Clone, Copy)] -pub struct YieldForever; - -impl Future for YieldForever { - type Output = (); - - fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { - Poll::Pending - } -} - -pub fn yield_forever() -> YieldForever { - YieldForever -} diff --git a/src/lune/util/mod.rs b/src/lune/util/mod.rs index 45e7512..1683428 100644 --- a/src/lune/util/mod.rs +++ b/src/lune/util/mod.rs @@ -1,7 +1,6 @@ mod table_builder; pub mod formatting; -pub mod futures; pub mod luaurc; pub mod paths; pub mod traits; diff --git a/src/lune/util/table_builder.rs b/src/lune/util/table_builder.rs index 25ded8e..88b9e4f 100644 --- a/src/lune/util/table_builder.rs +++ b/src/lune/util/table_builder.rs @@ -4,8 +4,6 @@ use std::future::Future; use mlua::prelude::*; -use crate::lune::scheduler::LuaSchedulerExt; - pub struct TableBuilder<'lua> { lua: &'lua Lua, tab: LuaTable<'lua>, @@ -79,20 +77,13 @@ impl<'lua> TableBuilder<'lua> { pub fn build(self) -> LuaResult> { Ok(self.tab) } -} -// FIXME: Remove static lifetime bound here when `create_async_function` -// no longer needs it to compile, then move this into the above impl -impl<'lua> TableBuilder<'lua> -where - 'lua: 'static, -{ pub fn with_async_function(self, key: K, func: F) -> LuaResult where K: IntoLua<'lua>, A: FromLuaMulti<'lua>, R: IntoLuaMulti<'lua>, - F: Fn(&'lua Lua, A) -> FR + 'lua, + F: Fn(&'lua Lua, A) -> FR + 'static, FR: Future> + 'lua, { let f = self.lua.create_async_function(func)?; diff --git a/src/tests.rs b/src/tests.rs index 8f08cde..f4af887 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -68,6 +68,7 @@ create_tests! { net_url_decode: "net/url/decode", net_serve_requests: "net/serve/requests", net_serve_websockets: "net/serve/websockets", + net_socket_basic: "net/socket/basic", net_socket_wss: "net/socket/wss", net_socket_wss_rw: "net/socket/wss_rw", @@ -84,7 +85,6 @@ create_tests! { require_aliases: "require/tests/aliases", require_async: "require/tests/async", - require_async_background: "require/tests/async_background", require_async_concurrent: "require/tests/async_concurrent", require_async_sequential: "require/tests/async_sequential", require_builtins: "require/tests/builtins", diff --git a/tests/net/socket/basic.luau b/tests/net/socket/basic.luau new file mode 100644 index 0000000..2eb4a3b --- /dev/null +++ b/tests/net/socket/basic.luau @@ -0,0 +1,28 @@ +local net = require("@lune/net") + +-- We're going to use Discord's WebSocket gateway server for testing +local socket = net.socket("wss://gateway.discord.gg/?v=10&encoding=json") + +assert(type(socket.next) == "function", "next must be a function") +assert(type(socket.send) == "function", "send must be a function") +assert(type(socket.close) == "function", "close must be a function") + +-- Request to close the socket +socket.close() + +-- Drain remaining messages, until we got our close message +while socket.next() do +end + +assert(type(socket.closeCode) == "number", "closeCode should exist after closing") +assert(socket.closeCode == 1000, "closeCode should be 1000 after closing") + +local success, message = pcall(function() + socket.send("Hello, world!") +end) + +assert(not success, "send should fail after closing") +assert( + string.find(tostring(message), "closed") or string.find(tostring(message), "closing"), + "send should fail with a message that the socket was closed" +) diff --git a/tests/require/tests/async_background.luau b/tests/require/tests/async_background.luau deleted file mode 100644 index ce35227..0000000 --- a/tests/require/tests/async_background.luau +++ /dev/null @@ -1,51 +0,0 @@ -local net = require("@lune/net") -local process = require("@lune/process") -local stdio = require("@lune/stdio") -local task = require("@lune/task") - --- Spawn an asynchronous background task (eg. web server) - -local PORT = 8082 - -task.delay(3, function() - stdio.ewrite("Test did not complete in time\n") - task.wait(1) - process.exit(1) -end) - -local handle = net.serve(PORT, function(request) - return "" -end) - --- Require modules same way we did in the async_concurrent and async_sequential tests - -local module3 -local module4 - -task.defer(function() - module4 = require("./modules/async") -end) - -task.spawn(function() - module3 = require("./modules/async") -end) - -local _module1 = require("./modules/async") -local _module2 = require("./modules/async") - -task.wait(1) - -assert(type(module3) == "table", "Required module3 did not return a table") -assert(module3.Foo == "Bar", "Required module3 did not contain correct values") -assert(module3.Hello == "World", "Required module3 did not contain correct values") - -assert(type(module4) == "table", "Required module4 did not return a table") -assert(module4.Foo == "Bar", "Required module4 did not contain correct values") -assert(module4.Hello == "World", "Required module4 did not contain correct values") - -assert(module3 == module4, "Required modules should point to the same return value") - --- Stop the server and exit successfully - -handle.stop() -process.exit(0)