summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Cargo.lock1605
-rw-r--r--Cargo.toml15
-rw-r--r--platforms/tortuise-script-compiled/Cargo.lock1516
-rw-r--r--platforms/tortuise-script-compiled/Cargo.toml16
-rw-r--r--platforms/tortuise-script-compiled/src/buffer.rs296
-rw-r--r--platforms/tortuise-script-compiled/src/entity.rs73
-rw-r--r--platforms/tortuise-script-compiled/src/input.rs260
-rw-r--r--platforms/tortuise-script-compiled/src/lib.rs119
-rw-r--r--platforms/tortuise-script-compiled/src/process.rs10
-rw-r--r--platforms/tortuise-script-compiled/src/random.rs70
-rw-r--r--platforms/tortuise-script-compiled/src/scene.rs17
-rw-r--r--platforms/tortuise-script-compiled/src/script.rs29
-rw-r--r--platforms/tortuise-script-compiled/src/state.rs65
-rw-r--r--platforms/tortuise-script-compiled/src/storage.rs181
-rw-r--r--platforms/tortuise-script-compiled/src/time.rs132
-rw-r--r--platforms/tortuise-script-wasm/.cargo/config.toml (renamed from .cargo/config.toml)0
-rw-r--r--platforms/tortuise-script-wasm/Cargo.lock176
-rw-r--r--platforms/tortuise-script-wasm/Cargo.toml13
-rw-r--r--platforms/tortuise-script-wasm/config.toml (renamed from config.toml)0
-rw-r--r--platforms/tortuise-script-wasm/rustfmt.toml3
-rw-r--r--platforms/tortuise-script-wasm/src/allocator.rs (renamed from src/allocator.rs)2
-rw-r--r--platforms/tortuise-script-wasm/src/buffer.rs (renamed from src/buffer.rs)9
-rw-r--r--platforms/tortuise-script-wasm/src/entity.rs (renamed from src/entity.rs)60
-rw-r--r--platforms/tortuise-script-wasm/src/input.rs264
-rw-r--r--platforms/tortuise-script-wasm/src/lib.rs239
-rw-r--r--platforms/tortuise-script-wasm/src/log.rs (renamed from src/log.rs)0
-rw-r--r--platforms/tortuise-script-wasm/src/process.rs (renamed from src/process.rs)6
-rw-r--r--platforms/tortuise-script-wasm/src/random.rs (renamed from src/random.rs)0
-rw-r--r--platforms/tortuise-script-wasm/src/scene.rs22
-rw-r--r--platforms/tortuise-script-wasm/src/script.rs48
-rw-r--r--platforms/tortuise-script-wasm/src/state.rs (renamed from src/state.rs)5
-rw-r--r--platforms/tortuise-script-wasm/src/storage.rs194
-rw-r--r--platforms/tortuise-script-wasm/src/time.rs (renamed from src/time.rs)2
-rw-r--r--src/input.rs84
-rw-r--r--src/lib.rs31
-rw-r--r--src/scene.rs32
-rw-r--r--src/script.rs44
-rw-r--r--src/storage.rs84
39 files changed, 5416 insertions, 307 deletions
diff --git a/.gitignore b/.gitignore
index ea8c4bf..74f7873 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
/target
+**/target
diff --git a/Cargo.lock b/Cargo.lock
index 45fd3d4..1dd8ae1 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -9,12 +9,743 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c880a97d28a3681c0267bd29cff89621202715b065127cd445fa0f0fe0aa2880"
[[package]]
+name = "android_system_properties"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.104"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
+
+[[package]]
+name = "async-channel"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2"
+dependencies = [
+ "concurrent-queue",
+ "event-listener-strategy",
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-executor"
+version = "1.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a"
+dependencies = [
+ "async-task",
+ "concurrent-queue",
+ "fastrand",
+ "futures-lite",
+ "pin-project-lite",
+ "slab",
+]
+
+[[package]]
+name = "async-fs"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8034a681df4aed8b8edbd7fbe472401ecf009251c8b40556b304567052e294c5"
+dependencies = [
+ "async-lock",
+ "blocking",
+ "futures-lite",
+]
+
+[[package]]
+name = "async-io"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc"
+dependencies = [
+ "autocfg",
+ "cfg-if",
+ "concurrent-queue",
+ "futures-io",
+ "futures-lite",
+ "parking",
+ "polling",
+ "rustix",
+ "slab",
+ "windows-sys",
+]
+
+[[package]]
+name = "async-lock"
+version = "3.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311"
+dependencies = [
+ "event-listener",
+ "event-listener-strategy",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-net"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7"
+dependencies = [
+ "async-io",
+ "blocking",
+ "futures-lite",
+]
+
+[[package]]
+name = "async-process"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75"
+dependencies = [
+ "async-channel",
+ "async-io",
+ "async-lock",
+ "async-signal",
+ "async-task",
+ "blocking",
+ "cfg-if",
+ "event-listener",
+ "futures-lite",
+ "rustix",
+]
+
+[[package]]
+name = "async-signal"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485"
+dependencies = [
+ "async-io",
+ "async-lock",
+ "atomic-waker",
+ "cfg-if",
+ "futures-core",
+ "futures-io",
+ "rustix",
+ "signal-hook-registry",
+ "slab",
+ "windows-sys",
+]
+
+[[package]]
+name = "async-task"
+version = "4.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
+
+[[package]]
+name = "atomic-polyfill"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4"
+dependencies = [
+ "critical-section",
+]
+
+[[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.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
[[package]]
+name = "bincode"
+version = "1.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "bitflags"
+version = "2.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
+
+[[package]]
+name = "blocking"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a70e4329df6cb94385eed412ec92375c3cdd8a6e502493d1229b6414e4036dfa"
+dependencies = [
+ "async-channel",
+ "async-task",
+ "futures-io",
+ "futures-lite",
+ "piper",
+]
+
+[[package]]
+name = "bpaf"
+version = "0.9.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c670d65eea33846872d5ccf668c00a94c5c67dbdcc0289ea1c362671ce1ddb82"
+dependencies = [
+ "bpaf_derive",
+]
+
+[[package]]
+name = "bpaf_derive"
+version = "0.5.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f7e98cee839b19076cb3ce1afdb62bb182e04ff5f71f70188827002fae91094"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.118",
+]
+
+[[package]]
+name = "bstr"
+version = "1.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f"
+dependencies = [
+ "memchr",
+ "serde_core",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.20.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "cc"
+version = "1.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "005ec2760ca554fae18df7a11195552ec576cd665632a881bc011d5bb2fd4d80"
+dependencies = [
+ "find-msvc-tools",
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+
+[[package]]
+name = "cfg_aliases"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527"
+
+[[package]]
+name = "chacha20"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "rand_core 0.10.1",
+]
+
+[[package]]
+name = "chrono"
+version = "0.4.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"
+dependencies = [
+ "iana-time-zone",
+ "js-sys",
+ "num-traits",
+ "wasm-bindgen",
+ "windows-link",
+]
+
+[[package]]
+name = "cobs"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1"
+dependencies = [
+ "thiserror",
+]
+
+[[package]]
+name = "concurrent-queue"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "convert_case"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9"
+dependencies = [
+ "unicode-segmentation",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "cpufeatures"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "critical-section"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6"
+
+[[package]]
+name = "crossterm"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b"
+dependencies = [
+ "bitflags",
+ "crossterm_winapi",
+ "derive_more",
+ "document-features",
+ "mio",
+ "parking_lot",
+ "rustix",
+ "signal-hook",
+ "signal-hook-mio",
+ "winapi",
+]
+
+[[package]]
+name = "crossterm_winapi"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "derive_more"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134"
+dependencies = [
+ "derive_more-impl",
+]
+
+[[package]]
+name = "derive_more-impl"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb"
+dependencies = [
+ "convert_case",
+ "proc-macro2",
+ "quote",
+ "rustc_version",
+ "syn 2.0.118",
+]
+
+[[package]]
+name = "document-features"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61"
+dependencies = [
+ "litrs",
+]
+
+[[package]]
+name = "either"
+version = "1.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34"
+
+[[package]]
+name = "embedded-io"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced"
+
+[[package]]
+name = "embedded-io"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"
+
+[[package]]
+name = "equivalent"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+
+[[package]]
+name = "erased-serde"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec"
+dependencies = [
+ "serde",
+ "serde_core",
+ "typeid",
+]
+
+[[package]]
+name = "errno"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
+dependencies = [
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
+name = "etcetera"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de48cc4d1c1d97a20fd819def54b890cadde72ed3ad0c614822a0a433361be96"
+dependencies = [
+ "cfg-if",
+ "windows-sys",
+]
+
+[[package]]
+name = "event-listener"
+version = "5.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2"
+dependencies = [
+ "parking",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "event-listener-strategy"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93"
+dependencies = [
+ "event-listener",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
+
+[[package]]
+name = "find-msvc-tools"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d"
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "futures-core"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
+
+[[package]]
+name = "futures-io"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed"
+
+[[package]]
+name = "futures-lite"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad"
+dependencies = [
+ "fastrand",
+ "futures-core",
+ "futures-io",
+ "parking",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "futures-task"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
+
+[[package]]
+name = "futures-util"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "pin-project-lite",
+ "slab",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi",
+ "rand_core 0.10.1",
+]
+
+[[package]]
+name = "gilrs"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "902fb00d3f6398e635be22e5c837b303c501835cca7ac11a47bba138f7aafdd8"
+dependencies = [
+ "fnv",
+ "gilrs-core",
+ "log",
+ "uuid",
+ "vec_map",
+]
+
+[[package]]
+name = "gilrs-core"
+version = "0.6.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc7f0ce6237abcc0523f2a5502b1e3fe5802daaae47ac14e166fe49551301ea9"
+dependencies = [
+ "inotify",
+ "js-sys",
+ "libc",
+ "libudev-sys",
+ "log",
+ "nix",
+ "objc2-core-foundation",
+ "objc2-io-kit",
+ "uuid",
+ "vec_map",
+ "wasm-bindgen",
+ "web-sys",
+ "windows",
+]
+
+[[package]]
+name = "hash32"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67"
+dependencies = [
+ "byteorder",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
+
+[[package]]
+name = "heapless"
+version = "0.7.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f"
+dependencies = [
+ "atomic-polyfill",
+ "hash32",
+ "rustc_version",
+ "serde",
+ "spin 0.9.9",
+ "stable_deref_trait",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284"
+
+[[package]]
+name = "hex_color"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d37f101bf4c633f7ca2e4b5e136050314503dd198e78e325ea602c327c484ef0"
+dependencies = [
+ "rand 0.8.8",
+]
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.65"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "log",
+ "wasm-bindgen",
+ "windows-core",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "indexmap"
+version = "2.14.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
+dependencies = [
+ "equivalent",
+ "hashbrown",
+]
+
+[[package]]
+name = "inotify"
+version = "0.11.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cc00ea907cab49550b7da656f80ebb97be1b997d931fbcd28d39734e17ce592"
+dependencies = [
+ "bitflags",
+ "inotify-sys",
+ "libc",
+]
+
+[[package]]
+name = "inotify-sys"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c033f80b2c113cdf91ab7a33faa9cbc014726dcad99880c8609af2a370edf37d"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "itertools"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.105"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e"
+dependencies = [
+ "cfg-if",
+ "futures-util",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.189"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
+
+[[package]]
+name = "libm"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
+
+[[package]]
+name = "libudev-sys"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324"
+dependencies = [
+ "libc",
+ "pkg-config",
+]
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
+
+[[package]]
+name = "litrs"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
+
+[[package]]
name = "lock_api"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -30,6 +761,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
[[package]]
+name = "lua-src"
+version = "551.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d400ffef0e3d4d29287092bdc5a276d0bf468b2c69709d5bab0d469312d9f947"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "luajit-src"
+version = "210.7.3+1ee778a"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "869665372263eb337b14f480cfb864b89f12eade4eb42cb415f71517b4a67572"
+dependencies = [
+ "cc",
+ "which",
+]
+
+[[package]]
+name = "lz4_flex"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ef0d4ed8669f8f8826eb00dc878084aa8f253506c4fd5e8f58f5bce72ddb97e"
+
+[[package]]
+name = "memchr"
+version = "2.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
+
+[[package]]
name = "messagepack-core"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -50,6 +812,62 @@ dependencies = [
]
[[package]]
+name = "mio"
+version = "1.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8"
+dependencies = [
+ "libc",
+ "log",
+ "wasi",
+ "windows-sys",
+]
+
+[[package]]
+name = "mlua"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d96e5d00f19d8c46c71ceaced99593b90c31c57aa1fe2cb3e93a8b1698eedba9"
+dependencies = [
+ "bstr",
+ "either",
+ "erased-serde",
+ "libc",
+ "mlua-sys",
+ "num-traits",
+ "parking_lot",
+ "rustc-hash",
+ "serde",
+ "serde-value",
+]
+
+[[package]]
+name = "mlua-sys"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b806d7ade031f5d6607eae3e283fb034cb795a76247dd0d1ba753c8c42debccf"
+dependencies = [
+ "cc",
+ "cfg-if",
+ "libc",
+ "lua-src",
+ "luajit-src",
+ "pkg-config",
+]
+
+[[package]]
+name = "nix"
+version = "0.31.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf20d2fde8ff38632c426f1165ed7436270b44f199fc55284c38276f9db47c3d"
+dependencies = [
+ "bitflags",
+ "cfg-if",
+ "cfg_aliases",
+ "libc",
+]
+
+[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -59,6 +877,129 @@ dependencies = [
]
[[package]]
+name = "objc2-core-foundation"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "objc2-io-kit"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15"
+dependencies = [
+ "bitflags",
+ "libc",
+ "objc2-core-foundation",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
+
+[[package]]
+name = "ordered-float"
+version = "2.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "parking"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
+
+[[package]]
+name = "parking_lot"
+version = "0.12.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-link",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
+
+[[package]]
+name = "piper"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1"
+dependencies = [
+ "atomic-waker",
+ "fastrand",
+ "futures-io",
+]
+
+[[package]]
+name = "pkg-config"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
+
+[[package]]
+name = "polling"
+version = "3.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218"
+dependencies = [
+ "cfg-if",
+ "concurrent-queue",
+ "hermit-abi",
+ "pin-project-lite",
+ "rustix",
+ "windows-sys",
+]
+
+[[package]]
+name = "postcard"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24"
+dependencies = [
+ "cobs",
+ "embedded-io 0.4.0",
+ "embedded-io 0.6.1",
+ "heapless",
+ "serde",
+]
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
+dependencies = [
+ "zerocopy",
+]
+
+[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -68,6 +1009,44 @@ dependencies = [
]
[[package]]
+name = "prologger"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bae8d37b1f9035f0f722b88b69df3322f0f75b9475eeb132a9fb391b67078a6f"
+dependencies = [
+ "log",
+]
+
+[[package]]
+name = "puffin"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84b514d95a258be801fde8a1ff1c974f4a4841d9750f5d1d6690fc07a5ad4049"
+dependencies = [
+ "anyhow",
+ "bincode",
+ "byteorder",
+ "cfg-if",
+ "itertools",
+ "lz4_flex",
+ "parking_lot",
+ "serde",
+]
+
+[[package]]
+name = "puffin_http"
+version = "0.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4f912991aab1adae69d2be9455e8db0f41e5ad3da87706ab2de64908678c2c76"
+dependencies = [
+ "anyhow",
+ "crossbeam-channel",
+ "log",
+ "parking_lot",
+ "puffin",
+]
+
+[[package]]
name = "quote"
version = "1.0.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -77,12 +1056,50 @@ dependencies = [
]
[[package]]
+name = "r-efi"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
+
+[[package]]
+name = "rand"
+version = "0.8.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core 0.6.4",
+]
+
+[[package]]
name = "rand"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80"
dependencies = [
- "rand_core",
+ "chacha20",
+ "getrandom 0.4.3",
+ "rand_core 0.10.1",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.6.4",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom 0.2.17",
]
[[package]]
@@ -92,12 +1109,61 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
[[package]]
+name = "redox_syscall"
+version = "0.5.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "rustc-hash"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
+
+[[package]]
+name = "rustc_version"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "rustix"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
+dependencies = [
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
+
+[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
+name = "semver"
+version = "1.0.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
+
+[[package]]
name = "serde"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -108,6 +1174,16 @@ dependencies = [
]
[[package]]
+name = "serde-value"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c"
+dependencies = [
+ "ordered-float",
+ "serde",
+]
+
+[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -124,10 +1200,109 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.118",
+]
+
+[[package]]
+name = "serde_spanned"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "shlex"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
+
+[[package]]
+name = "signal-hook"
+version = "0.3.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2"
+dependencies = [
+ "libc",
+ "signal-hook-registry",
+]
+
+[[package]]
+name = "signal-hook-mio"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc"
+dependencies = [
+ "libc",
+ "mio",
+ "signal-hook",
+]
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
+dependencies = [
+ "errno",
+ "libc",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
+
+[[package]]
+name = "smallvec"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9be42f50aa861c555654aa3a37f52f4b1074bacf4e48fe0ef7fa584e80f1f0f"
+
+[[package]]
+name = "smol"
+version = "2.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a33bd3e260892199c3ccfc487c88b2da2265080acb316cd920da72fdfd7c599f"
+dependencies = [
+ "async-channel",
+ "async-executor",
+ "async-fs",
+ "async-io",
+ "async-lock",
+ "async-net",
+ "async-process",
+ "blocking",
+ "futures-lite",
+]
+
+[[package]]
+name = "spin"
+version = "0.9.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e"
+dependencies = [
+ "lock_api",
]
[[package]]
+name = "spin"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0134f9043ed38b087ac4f7d4af44c79e2c9e5094421fe3164f435ce585953b10"
+dependencies = [
+ "lock_api",
+]
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
+
+[[package]]
name = "syn"
version = "2.0.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -139,6 +1314,17 @@ dependencies = [
]
[[package]]
+name = "syn"
+version = "3.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
name = "talc"
version = "5.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -149,18 +1335,431 @@ dependencies = [
]
[[package]]
+name = "thiserror"
+version = "2.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.5",
+]
+
+[[package]]
+name = "tinywasm"
+version = "0.11.0-pre.0"
+dependencies = [
+ "libm",
+ "log",
+ "tinywasm-parser",
+ "tinywasm-types",
+]
+
+[[package]]
+name = "tinywasm-parser"
+version = "0.11.0-pre.0"
+dependencies = [
+ "log",
+ "tinywasm-types",
+ "wasmparser",
+]
+
+[[package]]
+name = "tinywasm-types"
+version = "0.11.0-pre.0"
+dependencies = [
+ "log",
+ "postcard",
+ "serde",
+]
+
+[[package]]
+name = "toml"
+version = "1.1.6+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "920602543f0911ab71da12c50d59701da54c196d1a2bf5cb4b75667f137a406a"
+dependencies = [
+ "indexmap",
+ "serde_core",
+ "serde_spanned",
+ "toml_datetime",
+ "toml_parser",
+ "toml_writer",
+ "winnow",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "1.1.1+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "toml_parser"
+version = "1.1.3+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56"
+dependencies = [
+ "winnow",
+]
+
+[[package]]
+name = "toml_writer"
+version = "1.1.2+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2"
+
+[[package]]
+name = "tortuise"
+version = "0.1.0"
+dependencies = [
+ "bpaf",
+ "chrono",
+ "crossterm",
+ "etcetera",
+ "getrandom 0.4.3",
+ "gilrs",
+ "hex_color",
+ "log",
+ "messagepack-serde",
+ "mlua",
+ "postcard",
+ "prologger",
+ "puffin",
+ "puffin_http",
+ "rustc-hash",
+ "serde",
+ "smol",
+ "spin 0.12.3",
+ "tinywasm",
+]
+
+[[package]]
name = "tortuise-script"
version = "0.1.0"
dependencies = [
+ "tortuise-script-compiled",
+ "tortuise-script-wasm",
+]
+
+[[package]]
+name = "tortuise-script-compiled"
+version = "0.1.0"
+dependencies = [
+ "crossterm",
+ "gilrs",
"log",
"messagepack-serde",
- "rand",
+ "puffin",
+ "rand 0.10.2",
"serde",
+ "spin 0.12.3",
+ "toml",
+ "tortuise",
+]
+
+[[package]]
+name = "tortuise-script-wasm"
+version = "0.1.0"
+dependencies = [
+ "log",
+ "messagepack-serde",
+ "rand 0.10.2",
+ "serde",
+ "spin 0.12.3",
"talc",
+ "toml",
]
[[package]]
+name = "typeid"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
+
+[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.13.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
+
+[[package]]
+name = "uuid"
+version = "1.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a0e2d11620c50695fe12bbbfde96b3477fe606e7284c22d505c12ce51214514"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "vec_map"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
+
+[[package]]
+name = "wasi"
+version = "0.11.1+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.128"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.128"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.128"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a"
+dependencies = [
+ "bumpalo",
+ "proc-macro2",
+ "quote",
+ "syn 3.0.5",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.128"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "wasmparser"
+version = "0.255.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8e329ef4b5d46e73b91d3ac6924417cad55a8cbbf869c199283383427c3320b"
+dependencies = [
+ "bitflags",
+ "indexmap",
+]
+
+[[package]]
+name = "web-sys"
+version = "0.3.105"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fbddc4a036f00ec4f18c83445bd3115cb306a91da554919a099d9222fe4a7f8"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "which"
+version = "8.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bae2f2b2b816647a1cab1acc91f5bd20812d53cb344382635ec2181940c8034f"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows"
+version = "0.62.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580"
+dependencies = [
+ "windows-collections",
+ "windows-core",
+ "windows-future",
+ "windows-numerics",
+]
+
+[[package]]
+name = "windows-collections"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610"
+dependencies = [
+ "windows-core",
+]
+
+[[package]]
+name = "windows-core"
+version = "0.62.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-link",
+ "windows-result",
+ "windows-strings",
+]
+
+[[package]]
+name = "windows-future"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb"
+dependencies = [
+ "windows-core",
+ "windows-link",
+ "windows-threading",
+]
+
+[[package]]
+name = "windows-implement"
+version = "0.60.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.118",
+]
+
+[[package]]
+name = "windows-interface"
+version = "0.59.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.118",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+
+[[package]]
+name = "windows-numerics"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26"
+dependencies = [
+ "windows-core",
+ "windows-link",
+]
+
+[[package]]
+name = "windows-result"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-threading"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "winnow"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81"
+
+[[package]]
+name = "zerocopy"
+version = "0.8.57"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d35102a9f36d089ccae9e4c6802bc118be4487b80aaffc0ab4e0cf5ce92d2873"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.8.57"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "146c01f5ab44258da43cf276c74a2763db2ff3969c9c652c3f2de07041d0b2bc"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.118",
+]
diff --git a/Cargo.toml b/Cargo.toml
index 9e3073e..365f32f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -3,9 +3,12 @@ name = "tortuise-script"
version = "0.1.0"
edition = "2024"
-[dependencies]
-log = "0.4"
-messagepack-serde = "0.2"
-rand = { version = "0.10", default-features = false }
-serde = "1"
-talc = "5"
+[workspace]
+members = ["platforms/tortuise-script-compiled", "platforms/tortuise-script-wasm"]
+
+[target.'cfg(target_arch = "wasm32")'.dependencies]
+tortuise-script-wasm = { path = "platforms/tortuise-script-wasm" }
+
+[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
+tortuise-script-compiled = { path = "platforms/tortuise-script-compiled" }
+
diff --git a/platforms/tortuise-script-compiled/Cargo.lock b/platforms/tortuise-script-compiled/Cargo.lock
new file mode 100644
index 0000000..9487d7b
--- /dev/null
+++ b/platforms/tortuise-script-compiled/Cargo.lock
@@ -0,0 +1,1516 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.104"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
+
+[[package]]
+name = "async-channel"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2"
+dependencies = [
+ "concurrent-queue",
+ "event-listener-strategy",
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-executor"
+version = "1.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a"
+dependencies = [
+ "async-task",
+ "concurrent-queue",
+ "fastrand",
+ "futures-lite",
+ "pin-project-lite",
+ "slab",
+]
+
+[[package]]
+name = "async-fs"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8034a681df4aed8b8edbd7fbe472401ecf009251c8b40556b304567052e294c5"
+dependencies = [
+ "async-lock",
+ "blocking",
+ "futures-lite",
+]
+
+[[package]]
+name = "async-io"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc"
+dependencies = [
+ "autocfg",
+ "cfg-if",
+ "concurrent-queue",
+ "futures-io",
+ "futures-lite",
+ "parking",
+ "polling",
+ "rustix",
+ "slab",
+ "windows-sys",
+]
+
+[[package]]
+name = "async-lock"
+version = "3.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311"
+dependencies = [
+ "event-listener",
+ "event-listener-strategy",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-net"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7"
+dependencies = [
+ "async-io",
+ "blocking",
+ "futures-lite",
+]
+
+[[package]]
+name = "async-process"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75"
+dependencies = [
+ "async-channel",
+ "async-io",
+ "async-lock",
+ "async-signal",
+ "async-task",
+ "blocking",
+ "cfg-if",
+ "event-listener",
+ "futures-lite",
+ "rustix",
+]
+
+[[package]]
+name = "async-signal"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52b5aaafa020cf5053a01f2a60e8ff5dccf550f0f77ec54a4e47285ac2bab485"
+dependencies = [
+ "async-io",
+ "async-lock",
+ "atomic-waker",
+ "cfg-if",
+ "futures-core",
+ "futures-io",
+ "rustix",
+ "signal-hook-registry",
+ "slab",
+ "windows-sys",
+]
+
+[[package]]
+name = "async-task"
+version = "4.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
+
+[[package]]
+name = "atomic-polyfill"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4"
+dependencies = [
+ "critical-section",
+]
+
+[[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.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
+
+[[package]]
+name = "bincode"
+version = "1.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "bitflags"
+version = "2.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
+
+[[package]]
+name = "blocking"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a70e4329df6cb94385eed412ec92375c3cdd8a6e502493d1229b6414e4036dfa"
+dependencies = [
+ "async-channel",
+ "async-task",
+ "futures-io",
+ "futures-lite",
+ "piper",
+]
+
+[[package]]
+name = "bpaf"
+version = "0.9.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c670d65eea33846872d5ccf668c00a94c5c67dbdcc0289ea1c362671ce1ddb82"
+dependencies = [
+ "bpaf_derive",
+]
+
+[[package]]
+name = "bpaf_derive"
+version = "0.5.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f7e98cee839b19076cb3ce1afdb62bb182e04ff5f71f70188827002fae91094"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "bstr"
+version = "1.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f"
+dependencies = [
+ "memchr",
+ "serde_core",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.20.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
+
+[[package]]
+name = "bytemuck"
+version = "1.25.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797"
+dependencies = [
+ "bytemuck_derive",
+]
+
+[[package]]
+name = "bytemuck_derive"
+version = "1.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc0e56a716f1e132ff6bf4bdac1c944a3fcdc1cae65f70a4a2a1ac3b401d2d1f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.5",
+]
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "cc"
+version = "1.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "005ec2760ca554fae18df7a11195552ec576cd665632a881bc011d5bb2fd4d80"
+dependencies = [
+ "find-msvc-tools",
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+
+[[package]]
+name = "chrono"
+version = "0.4.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"
+dependencies = [
+ "iana-time-zone",
+ "js-sys",
+ "num-traits",
+ "wasm-bindgen",
+ "windows-link",
+]
+
+[[package]]
+name = "cobs"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1"
+dependencies = [
+ "thiserror",
+]
+
+[[package]]
+name = "concurrent-queue"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "convert_case"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9"
+dependencies = [
+ "unicode-segmentation",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "critical-section"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6"
+
+[[package]]
+name = "crossterm"
+version = "0.29.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b"
+dependencies = [
+ "bitflags",
+ "crossterm_winapi",
+ "derive_more",
+ "document-features",
+ "mio",
+ "parking_lot",
+ "rustix",
+ "signal-hook",
+ "signal-hook-mio",
+ "winapi",
+]
+
+[[package]]
+name = "crossterm_winapi"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "derive_more"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134"
+dependencies = [
+ "derive_more-impl",
+]
+
+[[package]]
+name = "derive_more-impl"
+version = "2.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb"
+dependencies = [
+ "convert_case",
+ "proc-macro2",
+ "quote",
+ "rustc_version",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "document-features"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61"
+dependencies = [
+ "litrs",
+]
+
+[[package]]
+name = "either"
+version = "1.18.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34"
+
+[[package]]
+name = "embedded-io"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced"
+
+[[package]]
+name = "embedded-io"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d"
+
+[[package]]
+name = "equivalent"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
+
+[[package]]
+name = "erased-serde"
+version = "0.4.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec"
+dependencies = [
+ "serde",
+ "serde_core",
+ "typeid",
+]
+
+[[package]]
+name = "errno"
+version = "0.3.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
+dependencies = [
+ "libc",
+ "windows-sys",
+]
+
+[[package]]
+name = "etcetera"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de48cc4d1c1d97a20fd819def54b890cadde72ed3ad0c614822a0a433361be96"
+dependencies = [
+ "cfg-if",
+ "windows-sys",
+]
+
+[[package]]
+name = "event-listener"
+version = "5.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2"
+dependencies = [
+ "parking",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "event-listener-strategy"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93"
+dependencies = [
+ "event-listener",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
+
+[[package]]
+name = "find-msvc-tools"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d"
+
+[[package]]
+name = "futures-core"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
+
+[[package]]
+name = "futures-io"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed"
+
+[[package]]
+name = "futures-lite"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad"
+dependencies = [
+ "fastrand",
+ "futures-core",
+ "futures-io",
+ "parking",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "futures-task"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
+
+[[package]]
+name = "futures-util"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "pin-project-lite",
+ "slab",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi",
+]
+
+[[package]]
+name = "hash32"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67"
+dependencies = [
+ "byteorder",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
+
+[[package]]
+name = "heapless"
+version = "0.7.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f"
+dependencies = [
+ "atomic-polyfill",
+ "hash32",
+ "rustc_version",
+ "serde",
+ "spin 0.9.9",
+ "stable_deref_trait",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284"
+
+[[package]]
+name = "hex_color"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d37f101bf4c633f7ca2e4b5e136050314503dd198e78e325ea602c327c484ef0"
+dependencies = [
+ "rand",
+]
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.65"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "log",
+ "wasm-bindgen",
+ "windows-core",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "indexmap"
+version = "2.14.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855"
+dependencies = [
+ "equivalent",
+ "hashbrown",
+]
+
+[[package]]
+name = "itertools"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.105"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e"
+dependencies = [
+ "cfg-if",
+ "futures-util",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.189"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
+
+[[package]]
+name = "libm"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
+
+[[package]]
+name = "libmimalloc-sys"
+version = "0.1.49"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a45a52f43e1c16f667ccfe4dd8c85b7f7c204fd5e3bf46c5b0db9a5c3c0b8e9"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
+
+[[package]]
+name = "litrs"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092"
+
+[[package]]
+name = "lock_api"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
+dependencies = [
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
+
+[[package]]
+name = "lua-src"
+version = "551.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d400ffef0e3d4d29287092bdc5a276d0bf468b2c69709d5bab0d469312d9f947"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "luajit-src"
+version = "210.7.3+1ee778a"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "869665372263eb337b14f480cfb864b89f12eade4eb42cb415f71517b4a67572"
+dependencies = [
+ "cc",
+ "which",
+]
+
+[[package]]
+name = "lz4_flex"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ef0d4ed8669f8f8826eb00dc878084aa8f253506c4fd5e8f58f5bce72ddb97e"
+
+[[package]]
+name = "memchr"
+version = "2.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
+
+[[package]]
+name = "mimalloc"
+version = "0.1.52"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d4139bb28d14ad1facf21d5eb8825051b326e172d216b39f6d31df53cc97862"
+dependencies = [
+ "libmimalloc-sys",
+]
+
+[[package]]
+name = "mio"
+version = "1.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8"
+dependencies = [
+ "libc",
+ "log",
+ "wasi",
+ "windows-sys",
+]
+
+[[package]]
+name = "mlua"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d96e5d00f19d8c46c71ceaced99593b90c31c57aa1fe2cb3e93a8b1698eedba9"
+dependencies = [
+ "bstr",
+ "either",
+ "erased-serde",
+ "libc",
+ "mlua-sys",
+ "num-traits",
+ "parking_lot",
+ "rustc-hash",
+ "serde",
+ "serde-value",
+]
+
+[[package]]
+name = "mlua-sys"
+version = "0.12.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b806d7ade031f5d6607eae3e283fb034cb795a76247dd0d1ba753c8c42debccf"
+dependencies = [
+ "cc",
+ "cfg-if",
+ "libc",
+ "lua-src",
+ "luajit-src",
+ "pkg-config",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.21.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
+
+[[package]]
+name = "ordered-float"
+version = "2.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "parking"
+version = "2.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
+
+[[package]]
+name = "parking_lot"
+version = "0.12.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-link",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
+
+[[package]]
+name = "piper"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1"
+dependencies = [
+ "atomic-waker",
+ "fastrand",
+ "futures-io",
+]
+
+[[package]]
+name = "pkg-config"
+version = "0.3.34"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
+
+[[package]]
+name = "polling"
+version = "3.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218"
+dependencies = [
+ "cfg-if",
+ "concurrent-queue",
+ "hermit-abi",
+ "pin-project-lite",
+ "rustix",
+ "windows-sys",
+]
+
+[[package]]
+name = "postcard"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24"
+dependencies = [
+ "cobs",
+ "embedded-io 0.4.0",
+ "embedded-io 0.6.1",
+ "heapless",
+ "serde",
+]
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
+dependencies = [
+ "zerocopy",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.107"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "prologger"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bae8d37b1f9035f0f722b88b69df3322f0f75b9475eeb132a9fb391b67078a6f"
+dependencies = [
+ "log",
+]
+
+[[package]]
+name = "puffin"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84b514d95a258be801fde8a1ff1c974f4a4841d9750f5d1d6690fc07a5ad4049"
+dependencies = [
+ "anyhow",
+ "bincode",
+ "byteorder",
+ "cfg-if",
+ "itertools",
+ "lz4_flex",
+ "parking_lot",
+ "serde",
+]
+
+[[package]]
+name = "puffin_http"
+version = "0.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4f912991aab1adae69d2be9455e8db0f41e5ad3da87706ab2de64908678c2c76"
+dependencies = [
+ "anyhow",
+ "crossbeam-channel",
+ "log",
+ "parking_lot",
+ "puffin",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "r-efi"
+version = "6.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
+
+[[package]]
+name = "rand"
+version = "0.8.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom 0.2.17",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.5.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "rustc-hash"
+version = "2.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
+
+[[package]]
+name = "rustc_version"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "rustix"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
+dependencies = [
+ "bitflags",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "semver"
+version = "1.0.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
+
+[[package]]
+name = "serde"
+version = "1.0.229"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
+dependencies = [
+ "serde_core",
+ "serde_derive",
+]
+
+[[package]]
+name = "serde-value"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3a1a3341211875ef120e117ea7fd5228530ae7e7036a779fdc9117be6b3282c"
+dependencies = [
+ "ordered-float",
+ "serde",
+]
+
+[[package]]
+name = "serde_core"
+version = "1.0.229"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.229"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.5",
+]
+
+[[package]]
+name = "serde_spanned"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "shlex"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
+
+[[package]]
+name = "signal-hook"
+version = "0.3.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2"
+dependencies = [
+ "libc",
+ "signal-hook-registry",
+]
+
+[[package]]
+name = "signal-hook-mio"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc"
+dependencies = [
+ "libc",
+ "mio",
+ "signal-hook",
+]
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
+dependencies = [
+ "errno",
+ "libc",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
+
+[[package]]
+name = "smallvec"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9be42f50aa861c555654aa3a37f52f4b1074bacf4e48fe0ef7fa584e80f1f0f"
+
+[[package]]
+name = "smol"
+version = "2.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a33bd3e260892199c3ccfc487c88b2da2265080acb316cd920da72fdfd7c599f"
+dependencies = [
+ "async-channel",
+ "async-executor",
+ "async-fs",
+ "async-io",
+ "async-lock",
+ "async-net",
+ "async-process",
+ "blocking",
+ "futures-lite",
+]
+
+[[package]]
+name = "spin"
+version = "0.9.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e"
+dependencies = [
+ "lock_api",
+]
+
+[[package]]
+name = "spin"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0134f9043ed38b087ac4f7d4af44c79e2c9e5094421fe3164f435ce585953b10"
+dependencies = [
+ "lock_api",
+]
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
+
+[[package]]
+name = "syn"
+version = "2.0.119"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "3.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
+dependencies = [
+ "thiserror-impl",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 3.0.5",
+]
+
+[[package]]
+name = "tinywasm"
+version = "0.11.0-pre.0"
+dependencies = [
+ "libm",
+ "log",
+ "tinywasm-parser",
+ "tinywasm-types",
+]
+
+[[package]]
+name = "tinywasm-parser"
+version = "0.11.0-pre.0"
+dependencies = [
+ "log",
+ "tinywasm-types",
+ "wasmparser",
+]
+
+[[package]]
+name = "tinywasm-types"
+version = "0.11.0-pre.0"
+dependencies = [
+ "log",
+ "postcard",
+ "serde",
+]
+
+[[package]]
+name = "toml"
+version = "1.1.5+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12c0ba9680044b4ce98d391a62094047eada0d64860b80166c39f4a6b5640785"
+dependencies = [
+ "indexmap",
+ "serde_core",
+ "serde_spanned",
+ "toml_datetime",
+ "toml_parser",
+ "toml_writer",
+ "winnow",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "1.1.1+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
+dependencies = [
+ "serde_core",
+]
+
+[[package]]
+name = "toml_parser"
+version = "1.1.3+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56"
+dependencies = [
+ "winnow",
+]
+
+[[package]]
+name = "toml_writer"
+version = "1.1.2+spec-1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2"
+
+[[package]]
+name = "tortuise"
+version = "0.1.0"
+dependencies = [
+ "bpaf",
+ "bytemuck",
+ "chrono",
+ "crossterm",
+ "etcetera",
+ "getrandom 0.4.3",
+ "hex_color",
+ "log",
+ "mimalloc",
+ "mlua",
+ "postcard",
+ "prologger",
+ "puffin",
+ "puffin_http",
+ "rustc-hash",
+ "serde",
+ "smol",
+ "spin 0.12.3",
+ "tinywasm",
+ "toml",
+]
+
+[[package]]
+name = "tortuise-script-compiled"
+version = "0.1.0"
+dependencies = [
+ "spin 0.12.3",
+ "tortuise",
+]
+
+[[package]]
+name = "typeid"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.13.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8"
+
+[[package]]
+name = "wasi"
+version = "0.11.1+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.128"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.128"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.128"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a"
+dependencies = [
+ "bumpalo",
+ "proc-macro2",
+ "quote",
+ "syn 3.0.5",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.128"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "wasmparser"
+version = "0.255.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8e329ef4b5d46e73b91d3ac6924417cad55a8cbbf869c199283383427c3320b"
+dependencies = [
+ "bitflags",
+ "indexmap",
+]
+
+[[package]]
+name = "which"
+version = "8.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bae2f2b2b816647a1cab1acc91f5bd20812d53cb344382635ec2181940c8034f"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-core"
+version = "0.62.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-link",
+ "windows-result",
+ "windows-strings",
+]
+
+[[package]]
+name = "windows-implement"
+version = "0.60.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "windows-interface"
+version = "0.59.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
+
+[[package]]
+name = "windows-link"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
+
+[[package]]
+name = "windows-result"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
+dependencies = [
+ "windows-link",
+]
+
+[[package]]
+name = "winnow"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81"
+
+[[package]]
+name = "zerocopy"
+version = "0.8.57"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d35102a9f36d089ccae9e4c6802bc118be4487b80aaffc0ab4e0cf5ce92d2873"
+dependencies = [
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.8.57"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "146c01f5ab44258da43cf276c74a2763db2ff3969c9c652c3f2de07041d0b2bc"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.119",
+]
diff --git a/platforms/tortuise-script-compiled/Cargo.toml b/platforms/tortuise-script-compiled/Cargo.toml
new file mode 100644
index 0000000..abdb342
--- /dev/null
+++ b/platforms/tortuise-script-compiled/Cargo.toml
@@ -0,0 +1,16 @@
+[package]
+name = "tortuise-script-compiled"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+tortuise = { path = "../../../tortuise" }
+crossterm = "0.29"
+gilrs = "0.11"
+log = "0.4"
+messagepack-serde = "0.2"
+puffin = "0.20"
+rand = "0.10"
+serde = "1"
+spin = { version = "0.12", features=["lock_api"] }
+toml = "1"
diff --git a/platforms/tortuise-script-compiled/src/buffer.rs b/platforms/tortuise-script-compiled/src/buffer.rs
new file mode 100644
index 0000000..c60ee40
--- /dev/null
+++ b/platforms/tortuise-script-compiled/src/buffer.rs
@@ -0,0 +1,296 @@
+use std::time::Duration;
+
+use crossterm::style::ContentStyle;
+use serde::{Deserialize, Serialize};
+use tortuise::script::Operation;
+
+use crate::ScriptContext;
+
+#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Serialize, Deserialize)]
+pub struct Cell {
+ pub character: char,
+ pub foreground_color: Color,
+ pub background_color: Color,
+ pub underline_color: Color,
+ pub attributes: Attributes,
+ pub x: u16,
+ pub y: u16,
+ pub z: u8,
+}
+
+#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Serialize, Deserialize)]
+pub struct WindowSize {
+ pub rows: u16,
+ pub columns: u16,
+}
+
+#[derive(
+ Copy, Clone, Debug, Default, PartialEq, Eq, Ord, PartialOrd, Hash, Serialize, Deserialize,
+)]
+pub enum Color {
+ /// Resets the terminal color.
+ #[default]
+ Reset,
+
+ /// Black color.
+ Black,
+
+ /// Dark grey color.
+ DarkGrey,
+
+ /// Light red color.
+ Red,
+
+ /// Dark red color.
+ DarkRed,
+
+ /// Light green color.
+ Green,
+
+ /// Dark green color.
+ DarkGreen,
+
+ /// Light yellow color.
+ Yellow,
+
+ /// Dark yellow color.
+ DarkYellow,
+
+ /// Light blue color.
+ Blue,
+
+ /// Dark blue color.
+ DarkBlue,
+
+ /// Light magenta color.
+ Magenta,
+
+ /// Dark magenta color.
+ DarkMagenta,
+
+ /// Light cyan color.
+ Cyan,
+
+ /// Dark cyan color.
+ DarkCyan,
+
+ /// White color.
+ White,
+
+ /// Grey color.
+ Grey,
+
+ /// An RGB color. See [RGB color model](https://en.wikipedia.org/wiki/RGB_color_model) for more info.
+ ///
+ /// Most UNIX terminals and Windows 10 supported only.
+ /// See [Platform-specific notes](enum.Color.html#platform-specific-notes) for more info.
+ Rgb { r: u8, g: u8, b: u8 },
+
+ /// An ANSI color. See [256 colors - cheat sheet](https://jonasjacek.github.io/colors/) for more info.
+ ///
+ /// Most UNIX terminals and Windows 10 supported only.
+ /// See [Platform-specific notes](enum.Color.html#platform-specific-notes) for more info.
+ AnsiValue(u8),
+}
+
+#[derive(
+ Copy, Clone, Debug, Default, PartialEq, Eq, Ord, PartialOrd, Hash, Serialize, Deserialize,
+)]
+pub struct Attributes(u32);
+
+pub enum Attribute {
+ /// Increases the text intensity.
+ Bold = 1,
+ /// Decreases the text intensity.
+ Dim = 2,
+ /// Emphasises the text.
+ Italic = 4,
+ /// Underlines the text.
+ Underlined = 8,
+
+ // Other types of underlining
+ /// Double underlines the text.
+ DoubleUnderlined = 16,
+ /// Undercurls the text.
+ Undercurled = 32,
+ /// Underdots the text.
+ Underdotted = 64,
+ /// Underdashes the text.
+ Underdashed = 128,
+
+ /// Makes the text blinking (< 150 per minute).
+ SlowBlink = 256,
+ /// Makes the text blinking (>= 150 per minute).
+ RapidBlink = 512,
+ /// Swaps foreground and background colors.
+ Reverse = 1024,
+ /// Hides the text (also known as Conceal).
+ Hidden = 2048,
+ /// Crosses the text.
+ CrossedOut = 4096,
+ /// Sets the [Fraktur](https://en.wikipedia.org/wiki/Fraktur) typeface.
+ ///
+ /// Mostly used for [mathematical alphanumeric symbols](https://en.wikipedia.org/wiki/Mathematical_Alphanumeric_Symbols).
+ Fraktur = 8192,
+ /// Makes the text framed.
+ Framed = 16384,
+ /// Makes the text encircled.
+ Encircled = 32768,
+ /// Draws a line at the top of the text.
+ OverLined = 65536,
+}
+
+impl Attributes {
+ pub fn none() -> Self {
+ Self(0)
+ }
+
+ pub fn with(self, attribute: Attribute) -> Self {
+ Self(self.0 | attribute as u32)
+ }
+
+ pub fn without(self, attribute: Attribute) -> Self {
+ Self(self.0 & !(attribute as u32))
+ }
+
+ pub fn set(&mut self, attribute: Attribute) {
+ self.0 |= attribute as u32;
+ }
+
+ pub fn unset(&mut self, attribute: Attribute) {
+ self.0 &= !(attribute as u32)
+ }
+
+ pub fn has(self, attribute: Attribute) -> bool {
+ self.0 & (attribute as u32) != 0
+ }
+
+ pub fn extend(&mut self, attributes: Attributes) {
+ self.0 |= attributes.0
+ }
+
+ pub fn is_empty(self) -> bool {
+ self.0 == 0
+ }
+}
+
+impl From<Color> for crossterm::style::Color {
+ fn from(value: Color) -> Self {
+ match value {
+ Color::Reset => crossterm::style::Color::Reset,
+ Color::Black => crossterm::style::Color::Black,
+ Color::DarkGrey => crossterm::style::Color::DarkGrey,
+ Color::Red => crossterm::style::Color::Red,
+ Color::DarkRed => crossterm::style::Color::DarkRed,
+ Color::Green => crossterm::style::Color::Green,
+ Color::DarkGreen => crossterm::style::Color::DarkGreen,
+ Color::Yellow => crossterm::style::Color::Yellow,
+ Color::DarkYellow => crossterm::style::Color::DarkYellow,
+ Color::Blue => crossterm::style::Color::Blue,
+ Color::DarkBlue => crossterm::style::Color::DarkBlue,
+ Color::Magenta => crossterm::style::Color::Magenta,
+ Color::DarkMagenta => crossterm::style::Color::DarkMagenta,
+ Color::Cyan => crossterm::style::Color::Cyan,
+ Color::DarkCyan => crossterm::style::Color::DarkCyan,
+ Color::White => crossterm::style::Color::White,
+ Color::Grey => crossterm::style::Color::Grey,
+ Color::Rgb { r, g, b } => crossterm::style::Color::Rgb { r, g, b },
+ Color::AnsiValue(v) => crossterm::style::Color::AnsiValue(v),
+ }
+ }
+}
+
+impl From<Attributes> for crossterm::style::Attributes {
+ fn from(value: Attributes) -> Self {
+ let mut new = crossterm::style::Attributes::none();
+
+ if value.has(Attribute::Bold) {
+ new.set(crossterm::style::Attribute::Bold);
+ }
+ if value.has(Attribute::Dim) {
+ new.set(crossterm::style::Attribute::Dim);
+ }
+ if value.has(Attribute::Italic) {
+ new.set(crossterm::style::Attribute::Italic);
+ }
+ if value.has(Attribute::Underlined) {
+ new.set(crossterm::style::Attribute::Underlined);
+ }
+ if value.has(Attribute::DoubleUnderlined) {
+ new.set(crossterm::style::Attribute::DoubleUnderlined);
+ }
+ if value.has(Attribute::Undercurled) {
+ new.set(crossterm::style::Attribute::Undercurled);
+ }
+ if value.has(Attribute::Underdotted) {
+ new.set(crossterm::style::Attribute::Underdotted);
+ }
+ if value.has(Attribute::Underdashed) {
+ new.set(crossterm::style::Attribute::Underdashed);
+ }
+ if value.has(Attribute::SlowBlink) {
+ new.set(crossterm::style::Attribute::SlowBlink);
+ }
+ if value.has(Attribute::RapidBlink) {
+ new.set(crossterm::style::Attribute::RapidBlink);
+ }
+ if value.has(Attribute::Reverse) {
+ new.set(crossterm::style::Attribute::Reverse);
+ }
+ if value.has(Attribute::Hidden) {
+ new.set(crossterm::style::Attribute::Hidden);
+ }
+ if value.has(Attribute::CrossedOut) {
+ new.set(crossterm::style::Attribute::CrossedOut);
+ }
+ if value.has(Attribute::Fraktur) {
+ new.set(crossterm::style::Attribute::Fraktur);
+ }
+ if value.has(Attribute::Framed) {
+ new.set(crossterm::style::Attribute::Framed);
+ }
+ if value.has(Attribute::Encircled) {
+ new.set(crossterm::style::Attribute::Encircled);
+ }
+ if value.has(Attribute::OverLined) {
+ new.set(crossterm::style::Attribute::OverLined);
+ }
+
+ new
+ }
+}
+
+impl ScriptContext<'_> {
+ pub fn add_cell(&mut self, cell: Cell) {
+ puffin::profile_function!();
+ self.0
+ .operation_queue
+ .push(Operation::AddCell(tortuise::buffer::Cell {
+ character: cell.character,
+ style: ContentStyle {
+ foreground_color: Some(cell.foreground_color.into()),
+ background_color: Some(cell.background_color.into()),
+ underline_color: Some(cell.underline_color.into()),
+ attributes: cell.attributes.into(),
+ },
+ x: cell.x,
+ y: cell.y,
+ z: cell.z,
+ }));
+ }
+
+ pub fn window_size(&self) -> WindowSize {
+ puffin::profile_function!();
+ let size = &self.0.buffer.window_size;
+ WindowSize {
+ rows: size.rows,
+ columns: size.columns,
+ }
+ }
+
+ pub fn set_framerate_limit(&mut self, limit: Option<u32>) {
+ puffin::profile_function!();
+ let limit = limit.map(|limit| Duration::from_nanos(1_000_000_000 / limit as u64));
+ self.0.operation_queue.push(Operation::SetFrameLimit(limit));
+ }
+}
diff --git a/platforms/tortuise-script-compiled/src/entity.rs b/platforms/tortuise-script-compiled/src/entity.rs
new file mode 100644
index 0000000..507690d
--- /dev/null
+++ b/platforms/tortuise-script-compiled/src/entity.rs
@@ -0,0 +1,73 @@
+use core::ops::{Deref, DerefMut};
+
+use spin::lock_api::{MappedRwLockWriteGuard, RwLock, RwLockWriteGuard};
+
+use crate::ScriptContext;
+
+pub struct Entity<T> {
+ data: RwLock<Option<T>>,
+ default: fn(&ScriptContext) -> T,
+}
+
+pub struct EntityRef<'entity, T> {
+ data: MappedRwLockWriteGuard<'entity, T>,
+}
+
+pub struct EntityMut<'entity, T> {
+ data: MappedRwLockWriteGuard<'entity, T>,
+}
+
+impl<T> Entity<T> {
+ pub const fn new(_name: &'static str, default: fn(&ScriptContext) -> T) -> Self {
+ Self {
+ data: RwLock::new(None),
+ default,
+ }
+ }
+
+ pub const fn from_string(name: String, default: fn(&ScriptContext) -> T) -> Self {
+ core::mem::forget(name);
+ Self {
+ data: RwLock::new(None),
+ default,
+ }
+ }
+
+ pub fn get(&self, context: &ScriptContext) -> EntityRef<'_, T> {
+ puffin::profile_function!();
+ let data = RwLockWriteGuard::map(self.data.write(), |data| {
+ data.get_or_insert_with(|| (self.default)(context))
+ });
+ EntityRef { data }
+ }
+
+ pub fn get_mut(&self, context: &ScriptContext) -> EntityMut<'_, T> {
+ puffin::profile_function!();
+ let data = RwLockWriteGuard::map(self.data.write(), |data| {
+ data.get_or_insert_with(|| (self.default)(context))
+ });
+ EntityMut { data }
+ }
+}
+
+impl<T> Deref for EntityRef<'_, T> {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ &self.data
+ }
+}
+
+impl<T> Deref for EntityMut<'_, T> {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ &self.data
+ }
+}
+
+impl<T> DerefMut for EntityMut<'_, T> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.data
+ }
+}
diff --git a/platforms/tortuise-script-compiled/src/input.rs b/platforms/tortuise-script-compiled/src/input.rs
new file mode 100644
index 0000000..a7d7340
--- /dev/null
+++ b/platforms/tortuise-script-compiled/src/input.rs
@@ -0,0 +1,260 @@
+use serde::{Deserialize, Serialize};
+
+use crate::ScriptContext;
+
+/// Represents a key.
+#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash, Serialize, Deserialize)]
+pub enum KeyCode {
+ /// Backspace key (Delete on macOS, Backspace on other platforms).
+ Backspace,
+ /// Enter key.
+ Enter,
+ /// Left arrow key.
+ Left,
+ /// Right arrow key.
+ Right,
+ /// Up arrow key.
+ Up,
+ /// Down arrow key.
+ Down,
+ /// Home key.
+ Home,
+ /// End key.
+ End,
+ /// Page up key.
+ PageUp,
+ /// Page down key.
+ PageDown,
+ /// Tab key.
+ Tab,
+ /// Shift + Tab key.
+ BackTab,
+ /// Delete key. (Fn+Delete on macOS, Delete on other platforms)
+ Delete,
+ /// Insert key.
+ Insert,
+ /// F key.
+ ///
+ /// `KeyCode::F(1)` represents F1 key, etc.
+ F(u8),
+ /// A character.
+ ///
+ /// `KeyCode::Char('c')` represents `c` character, etc.
+ Char(char),
+ /// Null.
+ Null,
+ /// Escape key.
+ Esc,
+}
+
+pub type Position = tortuise::input::Position;
+pub type Direction = tortuise::input::Direction;
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
+pub enum Button {
+ // Action Pad
+ South = 1,
+ East = 2,
+ North = 4,
+ West = 5,
+ C = 3,
+ Z = 6,
+ // Triggers
+ LeftTrigger = 7,
+ LeftTrigger2 = 9,
+ RightTrigger = 8,
+ RightTrigger2 = 10,
+ // Menu Pad
+ Select = 11,
+ Start = 12,
+ Mode = 13,
+ // Sticks
+ LeftThumb = 14,
+ RightThumb = 15,
+ // D-Pad
+ DPadUp = 16,
+ DPadDown = 17,
+ DPadLeft = 18,
+ DPadRight = 19,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
+pub enum Axis {
+ LeftStickX = 1,
+ LeftStickY = 2,
+ LeftZ = 3,
+ RightStickX = 4,
+ RightStickY = 5,
+ RightZ = 6,
+ DPadX = 7,
+ DPadY = 8,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
+pub enum PowerInfo {
+ Wired,
+ Discharging(u8),
+ Charging(u8),
+ Charged,
+ Unknown,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
+pub struct Gamepad(usize);
+
+impl From<Button> for gilrs::Button {
+ fn from(value: Button) -> Self {
+ match value {
+ Button::South => gilrs::Button::South,
+ Button::East => gilrs::Button::East,
+ Button::North => gilrs::Button::North,
+ Button::West => gilrs::Button::West,
+ Button::C => gilrs::Button::C,
+ Button::Z => gilrs::Button::Z,
+ Button::LeftTrigger => gilrs::Button::LeftTrigger,
+ Button::LeftTrigger2 => gilrs::Button::LeftTrigger2,
+ Button::RightTrigger => gilrs::Button::RightTrigger,
+ Button::RightTrigger2 => gilrs::Button::RightTrigger2,
+ Button::Select => gilrs::Button::Select,
+ Button::Start => gilrs::Button::Start,
+ Button::Mode => gilrs::Button::Mode,
+ Button::LeftThumb => gilrs::Button::LeftThumb,
+ Button::RightThumb => gilrs::Button::RightThumb,
+ Button::DPadUp => gilrs::Button::DPadUp,
+ Button::DPadDown => gilrs::Button::DPadDown,
+ Button::DPadLeft => gilrs::Button::DPadLeft,
+ Button::DPadRight => gilrs::Button::DPadRight,
+ }
+ }
+}
+
+impl From<Axis> for gilrs::Axis {
+ fn from(value: Axis) -> Self {
+ match value {
+ Axis::LeftStickX => gilrs::Axis::LeftStickX,
+ Axis::LeftStickY => gilrs::Axis::LeftStickY,
+ Axis::LeftZ => gilrs::Axis::LeftZ,
+ Axis::RightStickX => gilrs::Axis::RightStickX,
+ Axis::RightStickY => gilrs::Axis::RightStickY,
+ Axis::RightZ => gilrs::Axis::RightZ,
+ Axis::DPadX => gilrs::Axis::DPadX,
+ Axis::DPadY => gilrs::Axis::DPadY,
+ }
+ }
+}
+
+impl From<Option<gilrs::PowerInfo>> for PowerInfo {
+ fn from(value: Option<gilrs::PowerInfo>) -> Self {
+ match value {
+ Some(gilrs::PowerInfo::Wired) => PowerInfo::Wired,
+ Some(gilrs::PowerInfo::Discharging(p)) => PowerInfo::Discharging(p),
+ Some(gilrs::PowerInfo::Charging(p)) => PowerInfo::Charging(p),
+ Some(gilrs::PowerInfo::Charged) => PowerInfo::Charged,
+ Some(gilrs::PowerInfo::Unknown) => PowerInfo::Unknown,
+ None => PowerInfo::Unknown,
+ }
+ }
+}
+
+impl From<KeyCode> for crossterm::event::KeyCode {
+ fn from(value: KeyCode) -> Self {
+ match value {
+ KeyCode::Backspace => crossterm::event::KeyCode::Backspace,
+ KeyCode::Enter => crossterm::event::KeyCode::Enter,
+ KeyCode::Left => crossterm::event::KeyCode::Left,
+ KeyCode::Right => crossterm::event::KeyCode::Right,
+ KeyCode::Up => crossterm::event::KeyCode::Up,
+ KeyCode::Down => crossterm::event::KeyCode::Down,
+ KeyCode::Home => crossterm::event::KeyCode::Home,
+ KeyCode::End => crossterm::event::KeyCode::End,
+ KeyCode::PageUp => crossterm::event::KeyCode::PageUp,
+ KeyCode::PageDown => crossterm::event::KeyCode::PageDown,
+ KeyCode::Tab => crossterm::event::KeyCode::Tab,
+ KeyCode::BackTab => crossterm::event::KeyCode::BackTab,
+ KeyCode::Delete => crossterm::event::KeyCode::Delete,
+ KeyCode::Insert => crossterm::event::KeyCode::Insert,
+ KeyCode::F(f) => crossterm::event::KeyCode::F(f),
+ KeyCode::Char(c) => crossterm::event::KeyCode::Char(c),
+ KeyCode::Null => crossterm::event::KeyCode::Null,
+ KeyCode::Esc => crossterm::event::KeyCode::Esc,
+ }
+ }
+}
+
+impl Gamepad {
+ pub fn is_button_pressed_this_frame(&self, context: &ScriptContext, button: Button) -> bool {
+ context
+ .0
+ .input
+ .is_button_pressed_this_frame(self.0, button.into())
+ }
+
+ pub fn is_button_released_this_frame(&self, context: &ScriptContext, button: Button) -> bool {
+ context
+ .0
+ .input
+ .is_button_released_this_frame(self.0, button.into())
+ }
+
+ pub fn is_button_down(&self, context: &ScriptContext, button: Button) -> bool {
+ context.0.input.is_button_down(self.0, button.into())
+ }
+
+ pub fn axis_value(&self, context: &ScriptContext, axis: Axis) -> f32 {
+ context
+ .0
+ .input
+ .axis_value(self.0, axis.into())
+ .unwrap_or_default()
+ }
+
+ pub fn is_connected(&self, context: &ScriptContext) -> bool {
+ context.0.input.is_gamepad_connected(self.0)
+ }
+
+ pub fn power_info(&self, context: &ScriptContext) -> PowerInfo {
+ context.0.input.gamepad_power_info(self.0).into()
+ }
+}
+
+impl ScriptContext<'_> {
+ pub fn is_key_pressed_this_frame(&self, key_code: KeyCode) -> bool {
+ puffin::profile_function!();
+ self.0.input.is_key_pressed_this_frame(key_code.into())
+ }
+
+ pub fn is_mouse_clicked_this_frame(&self) -> bool {
+ puffin::profile_function!();
+ self.0.input.is_mouse_clicked_this_frame()
+ }
+
+ pub fn mouse_location(&self) -> Option<Position> {
+ puffin::profile_function!();
+ self.0.input.mouse_location()
+ }
+
+ pub fn scroll_direction_this_frame(&self) -> Option<Direction> {
+ puffin::profile_function!();
+ self.0.input.scroll_direction_this_frame()
+ }
+
+ pub fn is_gamepad_connected_this_frame(&self) -> Option<Gamepad> {
+ puffin::profile_function!();
+ self.0.input.is_gamepad_connected_this_frame().map(Gamepad)
+ }
+
+ pub fn is_focused(&self) -> bool {
+ puffin::profile_function!();
+ self.0.input.is_focused()
+ }
+
+ pub fn was_focused_this_frame(&self) -> bool {
+ puffin::profile_function!();
+ self.0.input.was_focused_this_frame()
+ }
+
+ pub fn was_unfocused_this_frame(&self) -> bool {
+ puffin::profile_function!();
+ self.0.input.was_unfocused_this_frame()
+ }
+}
diff --git a/platforms/tortuise-script-compiled/src/lib.rs b/platforms/tortuise-script-compiled/src/lib.rs
new file mode 100644
index 0000000..47bc926
--- /dev/null
+++ b/platforms/tortuise-script-compiled/src/lib.rs
@@ -0,0 +1,119 @@
+use std::sync::Arc;
+
+use tortuise::{file::FileManager, script::WasmRunner};
+
+pub mod buffer;
+pub mod entity;
+pub mod input;
+pub mod process;
+pub mod random;
+pub mod scene;
+pub mod script;
+pub mod state;
+pub mod storage;
+pub mod time;
+
+pub struct ScriptContext<'a>(tortuise::script::FullScriptContext<'a>);
+
+pub struct TortuiseScript {
+ pub start: fn(cx: ScriptContext),
+ pub update: fn(cx: ScriptContext),
+ pub end: fn(cx: ScriptContext),
+}
+
+#[derive(Debug, Default, Clone)]
+pub struct TortuiseScene {
+ pub scripts: &'static [&'static str],
+}
+
+fn default_script(_cx: ScriptContext) {}
+
+impl Default for TortuiseScript {
+ fn default() -> Self {
+ Self {
+ start: default_script,
+ update: default_script,
+ end: default_script,
+ }
+ }
+}
+
+impl TortuiseScript {
+ pub const fn empty() -> Self {
+ Self {
+ start: default_script,
+ update: default_script,
+ end: default_script,
+ }
+ }
+}
+
+#[doc(hidden)]
+pub fn run_game(
+ name: &str,
+ author: &str,
+ tld: &str,
+ first_scene: &str,
+ scenes: &[(&str, TortuiseScene)],
+ scripts: &[(&str, TortuiseScript)],
+) {
+ use tortuise::manifest::HumaneGameManifest;
+
+ fn wrap_script_fn(f: fn(ScriptContext)) -> tortuise::script::StaticScriptFunction {
+ Some(Arc::new(move |c| f(ScriptContext(c))))
+ }
+
+ fn slice_to_boxed_slice(names: &[&str]) -> Box<[Box<str>]> {
+ names.iter().cloned().map(Into::into).collect()
+ }
+
+ let manifest = HumaneGameManifest {
+ name: Box::from(name),
+ author: Box::from(author),
+ tld: Box::from(tld),
+ first_scene: Box::from(first_scene),
+ scene: scenes
+ .iter()
+ .map(|(name, scene)| tortuise::manifest::PartialScene {
+ name: (*name).into(),
+ scripts: slice_to_boxed_slice(scene.scripts),
+ })
+ .collect(),
+ script: scripts
+ .iter()
+ .map(
+ |(name, script)| tortuise::script::ScriptDeclaration::Static {
+ name: (*name).into(),
+ start: wrap_script_fn(script.start),
+ update: wrap_script_fn(script.update),
+ end: wrap_script_fn(script.end),
+ },
+ )
+ .collect(),
+ };
+
+ let mut files = FileManager::new();
+ let wasm_runner = WasmRunner::new();
+ let mut manifest = manifest.into();
+ tortuise::init_logger(&manifest);
+ if let Err(e) = tortuise::apply_patches(&mut manifest, &mut files, &wasm_runner) {
+ log::error!("{e}");
+ }
+ tortuise::run_game(files, wasm_runner, manifest)
+}
+
+#[macro_export]
+macro_rules! game {
+ {
+ name: $name: literal,
+ author: $author: literal,
+ tld: $tld: literal,
+ first_scene: $first_scene: literal,
+ scenes: { $($scene_name: literal => $scene: expr),* },
+ scripts: { $($script_name: literal => $script: expr),* }
+ } => {
+ fn main() {
+ $crate::run_game($name, $author, $tld, $first_scene, &[$(($scene_name, $scene),)*], &[$(($script_name, $script),)*]);
+ }
+ };
+}
diff --git a/platforms/tortuise-script-compiled/src/process.rs b/platforms/tortuise-script-compiled/src/process.rs
new file mode 100644
index 0000000..1ee39f2
--- /dev/null
+++ b/platforms/tortuise-script-compiled/src/process.rs
@@ -0,0 +1,10 @@
+use tortuise::script::Operation;
+
+use crate::ScriptContext;
+
+impl ScriptContext<'_> {
+ pub fn exit(&mut self) {
+ puffin::profile_function!();
+ self.0.operation_queue.push(Operation::Exit)
+ }
+}
diff --git a/platforms/tortuise-script-compiled/src/random.rs b/platforms/tortuise-script-compiled/src/random.rs
new file mode 100644
index 0000000..148f152
--- /dev/null
+++ b/platforms/tortuise-script-compiled/src/random.rs
@@ -0,0 +1,70 @@
+use rand::{
+ Rng, SeedableRng, TryRng,
+ rngs::{SmallRng, StdRng},
+};
+
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct TortuiseRng(SmallRng);
+
+impl TortuiseRng {
+ pub fn new() -> Self {
+ puffin::profile_function!();
+ Self::seed_from_u64(rand::make_rng::<StdRng>().next_u64())
+ }
+}
+
+impl Default for TortuiseRng {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl SeedableRng for TortuiseRng {
+ type Seed = <SmallRng as SeedableRng>::Seed;
+
+ fn from_seed(seed: Self::Seed) -> Self {
+ Self(SmallRng::from_seed(seed))
+ }
+
+ fn seed_from_u64(state: u64) -> Self {
+ Self(SmallRng::seed_from_u64(state))
+ }
+
+ fn from_rng<R: Rng + ?Sized>(rng: &mut R) -> Self {
+ Self(SmallRng::from_rng(rng))
+ }
+
+ fn try_from_rng<R: TryRng + ?Sized>(rng: &mut R) -> Result<Self, R::Error> {
+ Ok(Self(SmallRng::try_from_rng(rng)?))
+ }
+
+ fn fork(&mut self) -> Self
+ where
+ Self: Rng,
+ {
+ Self(self.0.fork())
+ }
+
+ fn try_fork(&mut self) -> Result<Self, <Self as TryRng>::Error>
+ where
+ Self: TryRng,
+ {
+ Ok(Self(self.0.try_fork()?))
+ }
+}
+
+impl TryRng for TortuiseRng {
+ type Error = <SmallRng as TryRng>::Error;
+
+ fn try_next_u32(&mut self) -> Result<u32, Self::Error> {
+ self.0.try_next_u32()
+ }
+
+ fn try_next_u64(&mut self) -> Result<u64, Self::Error> {
+ self.0.try_next_u64()
+ }
+
+ fn try_fill_bytes(&mut self, dst: &mut [u8]) -> Result<(), Self::Error> {
+ self.0.try_fill_bytes(dst)
+ }
+}
diff --git a/platforms/tortuise-script-compiled/src/scene.rs b/platforms/tortuise-script-compiled/src/scene.rs
new file mode 100644
index 0000000..fc83d6b
--- /dev/null
+++ b/platforms/tortuise-script-compiled/src/scene.rs
@@ -0,0 +1,17 @@
+use tortuise::script::Operation;
+
+use crate::ScriptContext;
+
+impl ScriptContext<'_> {
+ pub fn scene_id(&self, name: &str) -> Option<usize> {
+ puffin::profile_function!();
+ tortuise::scene::scene_id_by_name(&self.0.manifest.scenes, name)
+ }
+
+ pub fn switch_scene(&mut self, name: &str) -> Option<()> {
+ puffin::profile_function!();
+ let id = self.scene_id(name)?;
+ self.0.operation_queue.push(Operation::SwitchScene(id));
+ Some(())
+ }
+}
diff --git a/platforms/tortuise-script-compiled/src/script.rs b/platforms/tortuise-script-compiled/src/script.rs
new file mode 100644
index 0000000..d316fe5
--- /dev/null
+++ b/platforms/tortuise-script-compiled/src/script.rs
@@ -0,0 +1,29 @@
+use tortuise::script::Operation;
+
+use crate::ScriptContext;
+
+impl ScriptContext<'_> {
+ pub fn current_script_name(&self) -> String {
+ puffin::profile_function!();
+ self.0.manifest.scripts[self.0.script_id].name().to_owned()
+ }
+
+ pub fn script_id(&self, name: &str) -> Option<usize> {
+ puffin::profile_function!();
+ self.0.scripts.script_id_by_name(name)
+ }
+
+ pub fn activate_script(&mut self, name: &str) -> Option<()> {
+ puffin::profile_function!();
+ let id = self.script_id(name)?;
+ self.0.operation_queue.push(Operation::ActivateScript(id));
+ Some(())
+ }
+
+ pub fn deactivate_script(&mut self, name: &str) -> Option<()> {
+ puffin::profile_function!();
+ let id = self.script_id(name)?;
+ self.0.operation_queue.push(Operation::DeactivateScript(id));
+ Some(())
+ }
+}
diff --git a/platforms/tortuise-script-compiled/src/state.rs b/platforms/tortuise-script-compiled/src/state.rs
new file mode 100644
index 0000000..896b836
--- /dev/null
+++ b/platforms/tortuise-script-compiled/src/state.rs
@@ -0,0 +1,65 @@
+use core::ops::{Deref, DerefMut};
+
+use spin::lock_api::{MappedMutexGuard, Mutex, MutexGuard};
+
+use crate::ScriptContext;
+
+pub struct ScriptState<T> {
+ data: Mutex<Option<T>>,
+ default: fn(&ScriptContext) -> T,
+}
+
+pub struct StateRef<'state, T> {
+ data: MappedMutexGuard<'state, T>,
+}
+
+pub struct StateMut<'state, T> {
+ data: MappedMutexGuard<'state, T>,
+}
+
+impl<T> ScriptState<T> {
+ pub const fn new(default: fn(&ScriptContext) -> T) -> Self {
+ Self {
+ data: Mutex::new(None),
+ default,
+ }
+ }
+
+ pub fn get(&self, context: &ScriptContext) -> StateRef<'_, T> {
+ puffin::profile_function!();
+ let data = MutexGuard::map(self.data.lock(), |data| {
+ data.get_or_insert_with(|| (self.default)(context))
+ });
+ StateRef { data }
+ }
+
+ pub fn get_mut(&self, context: &ScriptContext) -> StateMut<'_, T> {
+ puffin::profile_function!();
+ let data = MutexGuard::map(self.data.lock(), |data| {
+ data.get_or_insert_with(|| (self.default)(context))
+ });
+ StateMut { data }
+ }
+}
+
+impl<T> Deref for StateRef<'_, T> {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ &self.data
+ }
+}
+
+impl<T> Deref for StateMut<'_, T> {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ &self.data
+ }
+}
+
+impl<T> DerefMut for StateMut<'_, T> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.data
+ }
+}
diff --git a/platforms/tortuise-script-compiled/src/storage.rs b/platforms/tortuise-script-compiled/src/storage.rs
new file mode 100644
index 0000000..d028a91
--- /dev/null
+++ b/platforms/tortuise-script-compiled/src/storage.rs
@@ -0,0 +1,181 @@
+use core::ops::{Deref, DerefMut};
+
+use serde::{Serialize, de::DeserializeOwned};
+use spin::Mutex;
+use tortuise::script::Operation;
+
+use crate::ScriptContext;
+
+pub struct Storage<T> {
+ initial_state: fn() -> T,
+ data: Mutex<Option<T>>,
+}
+
+pub struct Config<T> {
+ initial_state: fn() -> T,
+ data: Mutex<Option<T>>,
+}
+
+pub struct StorageMut<'a, 'cx, T: Serialize + Eq> {
+ context: &'cx mut ScriptContext<'a>,
+ data: T,
+ original: T,
+}
+
+pub struct ConfigMut<'a, 'cx, T: Serialize + Eq> {
+ context: &'cx mut ScriptContext<'a>,
+ data: T,
+ original: T,
+}
+
+impl<T: Serialize + DeserializeOwned + Clone + Eq> Storage<T> {
+ pub const fn new(initial_state: fn() -> T) -> Self {
+ Self {
+ initial_state,
+ data: Mutex::new(None),
+ }
+ }
+
+ pub fn load(&self, context: &ScriptContext) -> T {
+ puffin::profile_function!();
+ let mut data = self.data.lock();
+ data.get_or_insert_with(|| {
+ context
+ .0
+ .storage
+ .data()
+ .map(messagepack_serde::from_slice)
+ .and_then(|r| r.ok())
+ .unwrap_or_else(self.initial_state)
+ })
+ .clone()
+ }
+
+ pub fn load_mut<'a, 'cx>(&self, context: &'cx mut ScriptContext<'a>) -> StorageMut<'a, 'cx, T> {
+ let data = self.load(context);
+ StorageMut {
+ original: data.clone(),
+ data,
+ context,
+ }
+ }
+
+ pub fn is_saving(&self, context: &ScriptContext) -> bool {
+ context.0.storage.is_saving_data()
+ }
+
+ pub fn save_failed(&self, context: &ScriptContext) -> bool {
+ context.0.storage.saving_data_failed()
+ }
+}
+
+impl<T: Serialize + DeserializeOwned + Clone + Eq> Config<T> {
+ pub const fn new(initial_state: fn() -> T) -> Self {
+ Self {
+ initial_state,
+ data: Mutex::new(None),
+ }
+ }
+
+ pub fn load(&self, context: &ScriptContext) -> T {
+ puffin::profile_function!();
+ let mut data = self.data.lock();
+ data.get_or_insert_with(|| {
+ context
+ .0
+ .storage
+ .config()
+ .map(toml::from_slice)
+ .and_then(|r| r.ok())
+ .unwrap_or_else(self.initial_state)
+ })
+ .clone()
+ }
+
+ pub fn load_mut<'a, 'cx>(&self, context: &'cx mut ScriptContext<'a>) -> StorageMut<'a, 'cx, T> {
+ let data = self.load(context);
+ StorageMut {
+ original: data.clone(),
+ data,
+ context,
+ }
+ }
+
+ pub fn is_saving(&self, context: &ScriptContext) -> bool {
+ context.0.storage.is_saving_config()
+ }
+
+ pub fn save_failed(&self, context: &ScriptContext) -> bool {
+ context.0.storage.saving_config_failed()
+ }
+}
+
+impl<T: Serialize + Eq> Deref for StorageMut<'_, '_, T> {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ &self.data
+ }
+}
+
+impl<T: Serialize + Eq> DerefMut for StorageMut<'_, '_, T> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.data
+ }
+}
+
+impl<T: Serialize + Eq> Drop for StorageMut<'_, '_, T> {
+ fn drop(&mut self) {
+ puffin::profile_function!();
+ if self.data == self.original {
+ return;
+ }
+
+ let new_save = match messagepack_serde::to_vec(&self.data) {
+ Ok(new_save) => new_save.into_boxed_slice(),
+ Err(e) => {
+ log::error!("{e}");
+ return;
+ }
+ };
+ self.context
+ .0
+ .operation_queue
+ .push(Operation::UpdateStorage { new_save });
+ }
+}
+
+impl<T: Serialize + Eq> Deref for ConfigMut<'_, '_, T> {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ &self.data
+ }
+}
+
+impl<T: Serialize + Eq> DerefMut for ConfigMut<'_, '_, T> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.data
+ }
+}
+
+impl<T: Serialize + Eq> Drop for ConfigMut<'_, '_, T> {
+ fn drop(&mut self) {
+ puffin::profile_function!();
+ if self.data == self.original {
+ return;
+ }
+
+ let new_save = match toml::to_string(&self.data) {
+ Ok(new_save) => new_save.as_bytes().into(),
+ Err(e) => {
+ log::error!("{e}");
+ return;
+ }
+ };
+ self.context
+ .0
+ .operation_queue
+ .push(Operation::UpdateConfig { new_save });
+ }
+}
diff --git a/platforms/tortuise-script-compiled/src/time.rs b/platforms/tortuise-script-compiled/src/time.rs
new file mode 100644
index 0000000..72a2f68
--- /dev/null
+++ b/platforms/tortuise-script-compiled/src/time.rs
@@ -0,0 +1,132 @@
+use core::{
+ ops::{Add, AddAssign, Sub, SubAssign},
+ time::Duration,
+};
+
+use serde::{Deserialize, Serialize};
+
+use crate::ScriptContext;
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
+#[repr(transparent)]
+#[serde(transparent)]
+pub struct SystemTime(std::time::SystemTime);
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
+#[repr(transparent)]
+#[serde(transparent)]
+pub struct Instant(u128);
+
+impl ScriptContext<'_> {
+ pub fn delta_time(&self) -> Duration {
+ puffin::profile_function!();
+ Duration::from_nanos_u128(self.0.time.delta_time().as_nanos())
+ }
+}
+
+impl SystemTime {
+ pub const UNIX_EPOCH: Self = Self(std::time::SystemTime::UNIX_EPOCH);
+
+ pub fn now(context: &ScriptContext) -> Self {
+ puffin::profile_function!();
+ Self(context.0.time.system_now())
+ }
+
+ pub fn duration_since(self, earlier: SystemTime) -> Option<Duration> {
+ puffin::profile_function!();
+ self.0.duration_since(earlier.0).ok()
+ }
+
+ pub fn checked_add(self, duration: Duration) -> Option<Self> {
+ Some(Self(self.0.checked_add(duration)?))
+ }
+
+ pub fn checked_sub(self, duration: Duration) -> Option<Self> {
+ Some(Self(self.0.checked_sub(duration)?))
+ }
+}
+
+impl Add<Duration> for SystemTime {
+ type Output = Self;
+
+ fn add(self, rhs: Duration) -> Self::Output {
+ self.checked_add(rhs).unwrap()
+ }
+}
+
+impl AddAssign<Duration> for SystemTime {
+ fn add_assign(&mut self, rhs: Duration) {
+ *self = self.add(rhs);
+ }
+}
+
+impl Sub<Duration> for SystemTime {
+ type Output = Self;
+
+ fn sub(self, rhs: Duration) -> Self::Output {
+ self.checked_sub(rhs).unwrap()
+ }
+}
+
+impl SubAssign<Duration> for SystemTime {
+ fn sub_assign(&mut self, rhs: Duration) {
+ *self = self.sub(rhs)
+ }
+}
+
+impl Instant {
+ pub fn now(context: &ScriptContext) -> Self {
+ puffin::profile_function!();
+ Self(context.0.time.monotonic_now().as_nanos())
+ }
+
+ pub fn duration_since(self, earlier: Instant) -> Option<Duration> {
+ if earlier > self {
+ return None;
+ }
+
+ Some(Duration::from_nanos_u128(self.0 - earlier.0))
+ }
+
+ pub fn elapsed(self, context: &ScriptContext) -> Duration {
+ Self::now(context)
+ .duration_since(self)
+ .unwrap_or(Duration::ZERO)
+ }
+
+ pub fn checked_add(self, duration: Duration) -> Option<Self> {
+ Some(Self(self.0.checked_add(duration.as_nanos())?))
+ }
+
+ pub fn checked_sub(self, duration: Duration) -> Option<Self> {
+ Some(Self(self.0.checked_sub(duration.as_nanos())?))
+ }
+}
+
+impl Add<Duration> for Instant {
+ type Output = Self;
+
+ fn add(self, rhs: Duration) -> Self::Output {
+ self.checked_add(rhs).unwrap()
+ }
+}
+
+impl AddAssign<Duration> for Instant {
+ fn add_assign(&mut self, rhs: Duration) {
+ *self = self.add(rhs);
+ }
+}
+
+impl Sub<Duration> for Instant {
+ type Output = Self;
+
+ fn sub(self, rhs: Duration) -> Self::Output {
+ self.checked_sub(rhs).unwrap()
+ }
+}
+
+impl SubAssign<Duration> for Instant {
+ fn sub_assign(&mut self, rhs: Duration) {
+ *self = self.sub(rhs)
+ }
+}
diff --git a/.cargo/config.toml b/platforms/tortuise-script-wasm/.cargo/config.toml
index f4e8c00..f4e8c00 100644
--- a/.cargo/config.toml
+++ b/platforms/tortuise-script-wasm/.cargo/config.toml
diff --git a/platforms/tortuise-script-wasm/Cargo.lock b/platforms/tortuise-script-wasm/Cargo.lock
new file mode 100644
index 0000000..f2c5e52
--- /dev/null
+++ b/platforms/tortuise-script-wasm/Cargo.lock
@@ -0,0 +1,176 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "allocator-api2"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c880a97d28a3681c0267bd29cff89621202715b065127cd445fa0f0fe0aa2880"
+
+[[package]]
+name = "autocfg"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
+
+[[package]]
+name = "lock_api"
+version = "0.4.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
+dependencies = [
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
+
+[[package]]
+name = "messagepack-core"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95fe590f7b7d58bfefd83d9995c0b09eaecb8607e4bb66a658c95c0bd691f876"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "messagepack-serde"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a630d3ff4e4c893267925baffa3f8e75f0cdb20d212170ee7d8576a45ac1869"
+dependencies = [
+ "messagepack-core",
+ "num-traits",
+ "serde",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.106"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "serde"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
+dependencies = [
+ "serde_core",
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_core"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.228"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "spin"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0134f9043ed38b087ac4f7d4af44c79e2c9e5094421fe3164f435ce585953b10"
+dependencies = [
+ "lock_api",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.118"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "talc"
+version = "5.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40a5888b3189a89207eea4190e74387b5db3150d213f84186cac637dcd55259d"
+dependencies = [
+ "allocator-api2",
+ "lock_api",
+]
+
+[[package]]
+name = "tortuise-script"
+version = "0.1.0"
+dependencies = [
+ "log",
+ "messagepack-serde",
+ "rand",
+ "serde",
+ "spin",
+ "talc",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
diff --git a/platforms/tortuise-script-wasm/Cargo.toml b/platforms/tortuise-script-wasm/Cargo.toml
new file mode 100644
index 0000000..768cdfe
--- /dev/null
+++ b/platforms/tortuise-script-wasm/Cargo.toml
@@ -0,0 +1,13 @@
+[package]
+name = "tortuise-script-wasm"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+log = "0.4"
+messagepack-serde = "0.2"
+rand = { version = "0.10", default-features = false }
+serde = "1"
+spin = { version = "0.12", features=["lock_api"] }
+talc = "5"
+toml = "1"
diff --git a/config.toml b/platforms/tortuise-script-wasm/config.toml
index f4e8c00..f4e8c00 100644
--- a/config.toml
+++ b/platforms/tortuise-script-wasm/config.toml
diff --git a/platforms/tortuise-script-wasm/rustfmt.toml b/platforms/tortuise-script-wasm/rustfmt.toml
new file mode 100644
index 0000000..d3d0d1e
--- /dev/null
+++ b/platforms/tortuise-script-wasm/rustfmt.toml
@@ -0,0 +1,3 @@
+edition = "2024"
+hard_tabs = true
+newline_style = "Unix"
diff --git a/src/allocator.rs b/platforms/tortuise-script-wasm/src/allocator.rs
index eaaba5f..b4e3dec 100644
--- a/src/allocator.rs
+++ b/platforms/tortuise-script-wasm/src/allocator.rs
@@ -198,7 +198,7 @@ unsafe impl GlobalAlloc for BumpAllocator {
unsafe {
self.dealloc(ptr, layout);
- self.alloc(Layout::from_size_align(new_size, layout.align()).unwrap_unchecked())
+ self.alloc(Layout::from_size_align_unchecked(new_size, layout.align()))
}
}
}
diff --git a/src/buffer.rs b/platforms/tortuise-script-wasm/src/buffer.rs
index 78b6bd5..bbcb3ca 100644
--- a/src/buffer.rs
+++ b/platforms/tortuise-script-wasm/src/buffer.rs
@@ -16,6 +16,7 @@ mod tortuise {
attributes: u32,
);
pub safe fn window_size(context: u64) -> u32;
+ pub safe fn set_framerate_limit(context: u64, limit: u32);
}
}
@@ -211,8 +212,8 @@ impl Attributes {
}
}
-impl ScriptContext {
- pub fn add_cell(&self, cell: Cell) {
+impl ScriptContext<'_> {
+ pub fn add_cell(&mut self, cell: Cell) {
tortuise::add_cell(
self.0,
cell.character as u32,
@@ -232,4 +233,8 @@ impl ScriptContext {
columns: (size & 0xffff) as u16,
}
}
+
+ pub fn set_framerate_limit(&mut self, limit: Option<u32>) {
+ tortuise::set_framerate_limit(self.0, limit.unwrap_or_default());
+ }
}
diff --git a/src/entity.rs b/platforms/tortuise-script-wasm/src/entity.rs
index 9cf6f17..e66e2fe 100644
--- a/src/entity.rs
+++ b/platforms/tortuise-script-wasm/src/entity.rs
@@ -1,6 +1,5 @@
use core::{
alloc::Layout,
- marker::PhantomData,
ops::{Deref, DerefMut},
};
@@ -10,6 +9,7 @@ use alloc::{
string::String,
};
use serde::{Deserialize, Serialize};
+use spin::lock_api::{MappedRwLockWriteGuard, RwLock, RwLockWriteGuard};
use crate::ScriptContext;
@@ -35,10 +35,19 @@ fn load_entity<'de, T: Deserialize<'de>>(context: u64, name: &str) -> Option<T>
log::error!("Failed to create layout for size: {size}");
panic!();
};
+ if layout.size() == 0 {
+ return None;
+ }
let ptr = alloc(layout);
tortuise::load_entity(context, name.as_ptr(), name.len(), ptr);
let data = core::slice::from_raw_parts(ptr, size);
- let data = messagepack_serde::from_slice(data).ok()?;
+ let data = match messagepack_serde::from_slice(data) {
+ Ok(data) => data,
+ Err(e) => {
+ log::error!("Failed to parse entity: {e}");
+ return None;
+ }
+ };
dealloc(ptr, layout);
Some(data)
}
@@ -46,7 +55,13 @@ fn load_entity<'de, T: Deserialize<'de>>(context: u64, name: &str) -> Option<T>
fn save_entity<T: Serialize>(context: u64, name: &str, state: &T) {
unsafe {
- let data = messagepack_serde::to_vec(state).unwrap();
+ let data = match messagepack_serde::to_vec(state) {
+ Ok(data) => data,
+ Err(e) => {
+ log::error!("Failed to save entity: {e}");
+ panic!();
+ }
+ };
tortuise::save_entity(
context,
name.as_ptr(),
@@ -59,47 +74,56 @@ fn save_entity<T: Serialize>(context: u64, name: &str, state: &T) {
pub struct Entity<T> {
name: Cow<'static, str>,
- default: fn() -> T,
+ data: RwLock<Option<T>>,
+ default: fn(&ScriptContext) -> T,
}
pub struct EntityRef<'entity, T> {
- entity: PhantomData<&'entity Entity<T>>,
- data: T,
+ data: MappedRwLockWriteGuard<'entity, T>,
}
pub struct EntityMut<'entity, T: Serialize> {
context: u64,
- entity: &'entity Entity<T>,
- data: T,
+ name: &'entity str,
+ data: MappedRwLockWriteGuard<'entity, T>,
}
impl<'de, T: Serialize + Deserialize<'de>> Entity<T> {
- pub const fn new(name: &'static str, default: fn() -> T) -> Self {
+ pub const fn new(name: &'static str, default: fn(&ScriptContext) -> T) -> Self {
Self {
name: Cow::Borrowed(name),
+ data: RwLock::new(None),
default,
}
}
- pub const fn from_string(name: String, default: fn() -> T) -> Self {
+ pub const fn from_string(name: String, default: fn(&ScriptContext) -> T) -> Self {
Self {
name: Cow::Owned(name),
+ data: RwLock::new(None),
default,
}
}
pub fn get(&self, context: &ScriptContext) -> EntityRef<'_, T> {
- EntityRef {
- entity: PhantomData,
- data: load_entity(context.0, &self.name).unwrap_or_else(self.default),
- }
+ let data = RwLockWriteGuard::map(self.data.write(), |data| {
+ data.get_or_insert_with(|| {
+ load_entity(context.0, &self.name).unwrap_or_else(|| (self.default)(context))
+ })
+ });
+ EntityRef { data }
}
- pub fn get_mut(&mut self, context: &ScriptContext) -> EntityMut<'_, T> {
+ pub fn get_mut(&self, context: &ScriptContext) -> EntityMut<'_, T> {
+ let data = RwLockWriteGuard::map(self.data.write(), |data| {
+ data.get_or_insert_with(|| {
+ load_entity(context.0, &self.name).unwrap_or_else(|| (self.default)(context))
+ })
+ });
EntityMut {
context: context.0,
- entity: self,
- data: load_entity(context.0, &self.name).unwrap_or_else(self.default),
+ name: &self.name,
+ data,
}
}
}
@@ -128,6 +152,6 @@ impl<T: Serialize> DerefMut for EntityMut<'_, T> {
impl<T: Serialize> Drop for EntityMut<'_, T> {
fn drop(&mut self) {
- save_entity(self.context, &self.entity.name, &self.data);
+ save_entity(self.context, self.name, &*self.data);
}
}
diff --git a/platforms/tortuise-script-wasm/src/input.rs b/platforms/tortuise-script-wasm/src/input.rs
new file mode 100644
index 0000000..07cbe62
--- /dev/null
+++ b/platforms/tortuise-script-wasm/src/input.rs
@@ -0,0 +1,264 @@
+use serde::{Deserialize, Serialize};
+
+use crate::ScriptContext;
+
+mod tortuise {
+ #[link(wasm_import_module = "tortuise")]
+ unsafe extern "C" {
+ pub safe fn is_key_pressed_this_frame(context: u64, keycode: u32) -> u32;
+ pub safe fn is_mouse_clicked_this_frame(context: u64) -> u32;
+ pub safe fn mouse_location(context: u64) -> u32;
+ pub safe fn scroll_direction_this_frame(context: u64) -> u32;
+ pub safe fn is_button_pressed_this_frame(context: u64, gamepad_id: u32, button: u32)
+ -> u32;
+ pub safe fn is_button_released_this_frame(
+ context: u64,
+ gamepad_id: u32,
+ button: u32,
+ ) -> u32;
+ pub safe fn is_button_down(context: u64, gamepad_id: u32, button: u32) -> u32;
+ pub safe fn axis_value(context: u64, gamepad_id: u32, axis: u32) -> f32;
+ pub safe fn is_gamepad_connected_this_frame(context: u64) -> u32;
+ pub safe fn is_gamepad_connected(context: u64, gamepad_id: u32) -> u32;
+ pub safe fn gamepad_power_info(context: u64, gamepad_id: u32) -> u32;
+ pub safe fn is_focused(context: u64) -> u32;
+ pub safe fn was_focused_this_frame(context: u64) -> u32;
+ pub safe fn was_unfocused_this_frame(context: u64) -> u32;
+ }
+}
+
+#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct Position {
+ pub x: u16,
+ pub y: u16,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum Direction {
+ Up = 1,
+ Down,
+ Left,
+ Right,
+}
+
+/// Represents a key.
+#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash, Serialize, Deserialize)]
+pub enum KeyCode {
+ /// Backspace key (Delete on macOS, Backspace on other platforms).
+ Backspace,
+ /// Enter key.
+ Enter,
+ /// Left arrow key.
+ Left,
+ /// Right arrow key.
+ Right,
+ /// Up arrow key.
+ Up,
+ /// Down arrow key.
+ Down,
+ /// Home key.
+ Home,
+ /// End key.
+ End,
+ /// Page up key.
+ PageUp,
+ /// Page down key.
+ PageDown,
+ /// Tab key.
+ Tab,
+ /// Shift + Tab key.
+ BackTab,
+ /// Delete key. (Fn+Delete on macOS, Delete on other platforms)
+ Delete,
+ /// Insert key.
+ Insert,
+ /// F key.
+ ///
+ /// `KeyCode::F(1)` represents F1 key, etc.
+ F(u8),
+ /// A character.
+ ///
+ /// `KeyCode::Char('c')` represents `c` character, etc.
+ Char(char),
+ /// Null.
+ Null,
+ /// Escape key.
+ Esc,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
+pub enum Button {
+ // Action Pad
+ South = 1,
+ East = 2,
+ North = 4,
+ West = 5,
+ C = 3,
+ Z = 6,
+ // Triggers
+ LeftTrigger = 7,
+ LeftTrigger2 = 9,
+ RightTrigger = 8,
+ RightTrigger2 = 10,
+ // Menu Pad
+ Select = 11,
+ Start = 12,
+ Mode = 13,
+ // Sticks
+ LeftThumb = 14,
+ RightThumb = 15,
+ // D-Pad
+ DPadUp = 16,
+ DPadDown = 17,
+ DPadLeft = 18,
+ DPadRight = 19,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
+pub enum Axis {
+ LeftStickX = 1,
+ LeftStickY = 2,
+ LeftZ = 3,
+ RightStickX = 4,
+ RightStickY = 5,
+ RightZ = 6,
+ DPadX = 7,
+ DPadY = 8,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
+pub enum PowerInfo {
+ Wired,
+ Discharging(u8),
+ Charging(u8),
+ Charged,
+ Unknown,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
+pub struct Gamepad(u32);
+
+impl From<u32> for Position {
+ fn from(value: u32) -> Self {
+ Self {
+ x: (value & 0xffff) as u16,
+ y: ((value >> 16) & 0xffff) as u16,
+ }
+ }
+}
+
+impl TryFrom<u32> for Direction {
+ type Error = ();
+
+ fn try_from(value: u32) -> Result<Self, Self::Error> {
+ match value {
+ 1 => Ok(Direction::Up),
+ 2 => Ok(Direction::Down),
+ 3 => Ok(Direction::Left),
+ 4 => Ok(Direction::Right),
+ _ => Err(()),
+ }
+ }
+}
+
+fn key_code_to_number(key_code: KeyCode) -> u32 {
+ match key_code {
+ KeyCode::Null => 0,
+ KeyCode::Backspace => 1 << 24,
+ KeyCode::Enter => 2 << 24,
+ KeyCode::Left => 3 << 24,
+ KeyCode::Right => 4 << 24,
+ KeyCode::Up => 5 << 24,
+ KeyCode::Down => 6 << 24,
+ KeyCode::Home => 7 << 24,
+ KeyCode::End => 8 << 24,
+ KeyCode::PageUp => 9 << 24,
+ KeyCode::PageDown => 10 << 24,
+ KeyCode::Tab => 11 << 24,
+ KeyCode::BackTab => 12 << 24,
+ KeyCode::Delete => 13 << 24,
+ KeyCode::Insert => 14 << 24,
+ KeyCode::F(f) => 15 << 24 | f as u32,
+ KeyCode::Char(c) => 16 << 24 | c as u32,
+ KeyCode::Esc => 17 << 24,
+ }
+}
+
+impl Gamepad {
+ pub fn is_button_pressed_this_frame(&self, context: &ScriptContext, button: Button) -> bool {
+ tortuise::is_button_pressed_this_frame(context.0, self.0, button as u32) > 0
+ }
+
+ pub fn is_button_released_this_frame(&self, context: &ScriptContext, button: Button) -> bool {
+ tortuise::is_button_released_this_frame(context.0, self.0, button as u32) > 0
+ }
+
+ pub fn is_button_down(&self, context: &ScriptContext, button: Button) -> bool {
+ tortuise::is_button_down(context.0, self.0, button as u32) > 0
+ }
+
+ pub fn axis_value(&self, context: &ScriptContext, axis: Axis) -> f32 {
+ tortuise::axis_value(context.0, self.0, axis as u32)
+ }
+
+ pub fn is_connected(&self, context: &ScriptContext) -> bool {
+ tortuise::is_gamepad_connected(context.0, self.0) > 0
+ }
+
+ pub fn power_info(&self, context: &ScriptContext) -> PowerInfo {
+ const WIRED: u32 = 1 << 16;
+ const DISCHARGING: u32 = 2 << 16;
+ const CHARGING: u32 = 3 << 16;
+ const CHARGED: u32 = 4 << 16;
+
+ match tortuise::gamepad_power_info(context.0, self.0) {
+ WIRED => PowerInfo::Wired,
+ power @ DISCHARGING..CHARGING => PowerInfo::Discharging((power & 0xff) as u8),
+ power @ CHARGING..CHARGED => PowerInfo::Charging((power & 0xff) as u8),
+ CHARGED => PowerInfo::Charged,
+ _ => PowerInfo::Unknown,
+ }
+ }
+}
+
+impl ScriptContext<'_> {
+ pub fn is_key_pressed_this_frame(&self, key_code: KeyCode) -> bool {
+ tortuise::is_key_pressed_this_frame(self.0, key_code_to_number(key_code)) > 0
+ }
+
+ pub fn is_mouse_clicked_this_frame(&self) -> bool {
+ tortuise::is_mouse_clicked_this_frame(self.0) > 0
+ }
+
+ pub fn mouse_location(&self) -> Option<Position> {
+ let location = tortuise::mouse_location(self.0);
+ (location != u32::MAX).then(|| location.into())
+ }
+
+ pub fn scroll_direction_this_frame(&self) -> Option<Direction> {
+ tortuise::scroll_direction_this_frame(self.0)
+ .try_into()
+ .ok()
+ }
+
+ pub fn is_gamepad_connected_this_frame(&self) -> Option<Gamepad> {
+ let gamepad_id = tortuise::is_gamepad_connected_this_frame(self.0);
+ if gamepad_id == 0 {
+ return None;
+ }
+
+ Some(Gamepad(gamepad_id))
+ }
+
+ pub fn is_focused(&self) -> bool {
+ tortuise::is_focused(self.0) > 0
+ }
+
+ pub fn was_focused_this_frame(&self) -> bool {
+ tortuise::was_focused_this_frame(self.0) > 0
+ }
+
+ pub fn was_unfocused_this_frame(&self) -> bool {
+ tortuise::was_unfocused_this_frame(self.0) > 0
+ }
+}
diff --git a/platforms/tortuise-script-wasm/src/lib.rs b/platforms/tortuise-script-wasm/src/lib.rs
new file mode 100644
index 0000000..6394b8f
--- /dev/null
+++ b/platforms/tortuise-script-wasm/src/lib.rs
@@ -0,0 +1,239 @@
+#![no_std]
+
+use core::marker::PhantomData;
+
+use alloc::vec::Vec;
+use serde::Serialize;
+
+extern crate alloc;
+
+pub mod allocator;
+pub mod buffer;
+pub mod entity;
+pub mod input;
+pub mod log;
+pub mod process;
+pub mod random;
+pub mod scene;
+pub mod script;
+pub mod state;
+pub mod storage;
+pub mod time;
+
+#[derive(Clone, Copy)]
+#[repr(transparent)]
+pub struct ScriptContext<'a>(u64, PhantomData<&'a ()>);
+
+pub struct TortuiseScript {
+ pub start: fn(cx: ScriptContext),
+ pub update: fn(cx: ScriptContext),
+ pub end: fn(cx: ScriptContext),
+}
+
+#[derive(Debug, Default, Clone)]
+pub struct TortuiseScene {
+ pub scripts: &'static [&'static str],
+}
+
+fn default_script(_cx: ScriptContext) {}
+
+impl Default for TortuiseScript {
+ fn default() -> Self {
+ Self {
+ start: default_script,
+ update: default_script,
+ end: default_script,
+ }
+ }
+}
+
+impl TortuiseScript {
+ pub const fn empty() -> Self {
+ Self {
+ start: default_script,
+ update: default_script,
+ end: default_script,
+ }
+ }
+}
+
+#[doc(hidden)]
+pub fn to_vec<T: Serialize>(val: &T) -> &'static [u8] {
+ Vec::leak(messagepack_serde::to_vec(val).unwrap())
+}
+
+#[doc(hidden)]
+pub type Lazy<T> = spin::LazyLock<T>;
+
+#[macro_export]
+macro_rules! game {
+ {
+ name: $name: literal,
+ author: $author: literal,
+ tld: $tld: literal,
+ first_scene: $first_scene: literal,
+ scenes: { $($scene_name: literal => $scene: expr),* },
+ scripts: { $($script_name: literal => $script: expr),* }
+ } => {
+ use alloc::boxed::Box;
+
+ #[derive(Debug, Clone, serde::Serialize)]
+ struct GameManifest {
+ name: Box<str>,
+ author: Box<str>,
+ tld: Box<str>,
+ first_scene: Box<str>,
+ scene: Box<[ManifestScene]>,
+ script: Box<[Box<str>]>,
+ }
+
+ #[derive(Debug, Clone, serde::Serialize)]
+ struct ManifestScene {
+ name: Box<str>,
+ scripts: Box<[Box<str>]>,
+ }
+
+ #[global_allocator]
+ static ALLOCATOR: $crate::allocator::TalcAllocator =
+ $crate::allocator::TalcAllocator::new();
+
+ static SCRIPTS: phf::Map<&'static str, $crate::TortuiseScript> = phf::phf_map! {
+ $($script_name => $script,)*
+ };
+
+ static MANIFEST: $crate::Lazy<&'static [u8]> = $crate::Lazy::new(|| {
+ fn slice_to_boxed_slice(names: &[&str]) -> Box<[Box<str>]> {
+ names.iter().cloned().map(Into::into).collect()
+ }
+
+ let manifest = GameManifest {
+ name: $name.into(),
+ author: $author.into(),
+ tld: $tld.into(),
+ first_scene: $first_scene.into(),
+ script: slice_to_boxed_slice([$($script_name,)*].as_slice()),
+ scene: Box::from([$(ManifestScene {
+ name: $scene_name.into(),
+ scripts: slice_to_boxed_slice($scene.scripts),
+ },)*].as_slice())
+ };
+
+ $crate::to_vec(&manifest)
+ });
+
+ #[unsafe(no_mangle)]
+ extern "C" fn manifest_addr() -> *const u8 {
+ MANIFEST.as_ptr()
+ }
+
+ #[unsafe(no_mangle)]
+ extern "C" fn manifest_len() -> usize {
+ MANIFEST.len()
+ }
+
+ #[unsafe(no_mangle)]
+ extern "C" fn start_script(cx: $crate::ScriptContext) {
+ let _ = $crate::log::TortuiseLogger::init();
+ let Some(script) = SCRIPTS.get(&*cx.current_script_name()) else { return };
+ (script.start)(cx);
+ }
+
+ #[unsafe(no_mangle)]
+ extern "C" fn update_script(cx: $crate::ScriptContext) {
+ let _ = $crate::log::TortuiseLogger::init();
+ let Some(script) = SCRIPTS.get(&*cx.current_script_name()) else { return };
+ (script.update)(cx);
+ }
+
+ #[unsafe(no_mangle)]
+ extern "C" fn end_script(cx: $crate::ScriptContext) {
+ let _ = $crate::log::TortuiseLogger::init();
+ let Some(script) = SCRIPTS.get(&*cx.current_script_name()) else { return };
+ (script.end)(cx);
+ }
+ };
+}
+
+#[macro_export]
+macro_rules! patch {
+ {
+ scenes: { $($scene_name: literal => $scene: expr),* },
+ scripts: { $($script_name: literal => $script: expr),* }
+ } => {
+ use alloc::boxed::Box;
+
+ #[derive(Debug, Clone, serde::Serialize)]
+ struct GameManifestPatch {
+ new_scenes: Box<[ManifestScene]>,
+ new_scripts: Box<[Box<str>]>,
+ patched_scenes: Box<[ScenePatch]>,
+ }
+
+ #[derive(Debug, Clone, serde::Serialize)]
+ struct ManifestScene {
+ name: Box<str>,
+ scripts: Box<[Box<str>]>,
+ }
+
+ #[derive(Debug, Clone, serde::Serialize)]
+ struct ScenePatch {
+ add_scripts: Box<[Box<str>]>,
+ remove_scripts: Box<[Box<str>]>,
+ }
+
+ #[global_allocator]
+ static ALLOCATOR: $crate::allocator::TalcAllocator =
+ $crate::allocator::TalcAllocator::new();
+
+ static SCRIPTS: phf::Map<&'static str, $crate::TortuiseScript> = phf::phf_map! {
+ $($script_name => $script,)*
+ };
+
+ static MANIFEST: $crate::Lazy<&'static [u8]> = $crate::Lazy::new(|| {
+ fn slice_to_boxed_slice(names: &[&str]) -> Box<[Box<str>]> {
+ names.iter().cloned().map(Into::into).collect()
+ }
+
+ let manifest = GameManifestPatch {
+ added_scripts: slice_to_boxed_slice([$($script_name,)*].as_slice()),
+ added_scenes: Box::from([$(ManifestScene {
+ name: $scene_name.into(),
+ scripts: slice_to_boxed_slice($scene.scripts),
+ },)*].as_slice())
+ };
+
+ $crate::to_vec(&manifest)
+ });
+
+ #[unsafe(no_mangle)]
+ extern "C" fn manifest_addr() -> *const u8 {
+ MANIFEST.as_ptrhttps://botahamec.dev/cgit/shake/plain/shake()
+ }
+
+ #[unsafe(no_mangle)]
+ extern "C" fn manifest_len() -> usize {
+ MANIFEST.len()
+ }
+
+ #[unsafe(no_mangle)]
+ extern "C" fn start_script(cx: $crate::ScriptContext) {
+ let _ = $crate::log::TortuiseLogger::init();
+ let Some(script) = SCRIPTS.get(&*cx.current_script_name()) else { return };
+ (script.start)(cx);
+ }
+
+ #[unsafe(no_mangle)]
+ extern "C" fn update_script(cx: $crate::ScriptContext) {
+ let _ = $crate::log::TortuiseLogger::init();
+ let Some(script) = SCRIPTS.get(&*cx.current_script_name()) else { return };
+ (script.update)(cx);
+ }
+
+ #[unsafe(no_mangle)]
+ extern "C" fn end_script(cx: $crate::ScriptContext) {
+ let _ = $crate::log::TortuiseLogger::init();
+ let Some(script) = SCRIPTS.get(&*cx.current_script_name()) else { return };
+ (script.end)(cx);
+ }
+ };
+}
diff --git a/src/log.rs b/platforms/tortuise-script-wasm/src/log.rs
index 306a0d5..306a0d5 100644
--- a/src/log.rs
+++ b/platforms/tortuise-script-wasm/src/log.rs
diff --git a/src/process.rs b/platforms/tortuise-script-wasm/src/process.rs
index ed8c542..fe2af19 100644
--- a/src/process.rs
+++ b/platforms/tortuise-script-wasm/src/process.rs
@@ -3,12 +3,12 @@ use crate::ScriptContext;
mod tortuise {
#[link(wasm_import_module = "tortuise")]
unsafe extern "C" {
- pub safe fn exit(context: u64) -> !;
+ pub safe fn exit(context: u64);
}
}
-impl ScriptContext {
- pub fn exit(&self) -> ! {
+impl ScriptContext<'_> {
+ pub fn exit(&mut self) {
tortuise::exit(self.0)
}
}
diff --git a/src/random.rs b/platforms/tortuise-script-wasm/src/random.rs
index 334b6bf..334b6bf 100644
--- a/src/random.rs
+++ b/platforms/tortuise-script-wasm/src/random.rs
diff --git a/platforms/tortuise-script-wasm/src/scene.rs b/platforms/tortuise-script-wasm/src/scene.rs
new file mode 100644
index 0000000..93889a7
--- /dev/null
+++ b/platforms/tortuise-script-wasm/src/scene.rs
@@ -0,0 +1,22 @@
+use crate::ScriptContext;
+
+mod tortuise {
+ #[link(wasm_import_module = "tortuise")]
+ unsafe extern "C" {
+ pub unsafe fn scene_id_by_name(context: u64, name: *const u8, length: usize) -> usize;
+ pub safe fn switch_scene(context: u64, id: usize);
+ }
+}
+
+impl ScriptContext<'_> {
+ pub fn scene_id(&self, name: &str) -> Option<usize> {
+ let id = unsafe { tortuise::scene_id_by_name(self.0, name.as_ptr(), name.len()) };
+ (id < usize::MAX).then_some(id)
+ }
+
+ pub fn switch_scene(&mut self, name: &str) -> Option<()> {
+ let id = self.scene_id(name)?;
+ tortuise::switch_scene(self.0, id);
+ Some(())
+ }
+}
diff --git a/platforms/tortuise-script-wasm/src/script.rs b/platforms/tortuise-script-wasm/src/script.rs
new file mode 100644
index 0000000..ee4d91d
--- /dev/null
+++ b/platforms/tortuise-script-wasm/src/script.rs
@@ -0,0 +1,48 @@
+use core::alloc::Layout;
+
+use alloc::string::String;
+
+use crate::ScriptContext;
+
+mod tortuise {
+ #[link(wasm_import_module = "tortuise")]
+ unsafe extern "C" {
+ pub safe fn script_name_size(context: u64) -> usize;
+ pub unsafe fn load_script_name(context: u64, offset: *const u8);
+ pub unsafe fn script_id_by_name(context: u64, name: *const u8, length: usize) -> usize;
+ pub safe fn activate_script(context: u64, id: usize);
+ pub safe fn deactivate_script(context: u64, id: usize);
+ }
+}
+
+impl ScriptContext<'_> {
+ pub fn current_script_name(&self) -> String {
+ // safety: the length is correct and the string will fit into offset
+ unsafe {
+ let len = tortuise::script_name_size(self.0);
+ if len == 0 {
+ return String::new();
+ }
+ let offset = alloc::alloc::alloc(Layout::array::<u8>(len).unwrap());
+ tortuise::load_script_name(self.0, offset);
+ String::from_raw_parts(offset, len, len)
+ }
+ }
+
+ pub fn script_id(&self, name: &str) -> Option<usize> {
+ let id = unsafe { tortuise::script_id_by_name(self.0, name.as_ptr(), name.len()) };
+ (id < usize::MAX).then_some(id)
+ }
+
+ pub fn activate_script(&mut self, name: &str) -> Option<()> {
+ let id = self.script_id(name)?;
+ tortuise::activate_script(self.0, id);
+ Some(())
+ }
+
+ pub fn deactivate_script(&mut self, name: &str) -> Option<()> {
+ let id = self.script_id(name)?;
+ tortuise::deactivate_script(self.0, id);
+ Some(())
+ }
+}
diff --git a/src/state.rs b/platforms/tortuise-script-wasm/src/state.rs
index 8b4de18..ffcdb3e 100644
--- a/src/state.rs
+++ b/platforms/tortuise-script-wasm/src/state.rs
@@ -18,11 +18,14 @@ mod tortuise {
}
}
-impl ScriptContext {
+impl ScriptContext<'_> {
pub fn load_state<T: DeserializeOwned>(&self) -> Option<T> {
unsafe {
let size = tortuise::state_size(self.0);
let layout = Layout::array::<u8>(size).unwrap();
+ if layout.size() == 0 {
+ return None;
+ }
let ptr = alloc(layout);
tortuise::load_state(self.0, ptr);
let data = core::slice::from_raw_parts(ptr, size);
diff --git a/platforms/tortuise-script-wasm/src/storage.rs b/platforms/tortuise-script-wasm/src/storage.rs
new file mode 100644
index 0000000..2606e2b
--- /dev/null
+++ b/platforms/tortuise-script-wasm/src/storage.rs
@@ -0,0 +1,194 @@
+use core::{
+ alloc::Layout,
+ marker::PhantomData,
+ ops::{Deref, DerefMut},
+};
+
+use alloc::alloc::{alloc, dealloc};
+use serde::{Serialize, de::DeserializeOwned};
+
+use crate::ScriptContext;
+
+mod tortuise {
+ #[link(wasm_import_module = "tortuise")]
+ unsafe extern "C" {
+ pub safe fn storage_size(context: u64) -> usize;
+ pub unsafe fn load_storage(context: u64, ptr: *mut u8);
+ pub unsafe fn save_storage(context: u64, ptr: *const u8, size: usize);
+ pub safe fn is_saving_storage(context: u64) -> usize;
+ pub safe fn saving_storage_failed(context: u64) -> usize;
+ pub safe fn config_size(context: u64) -> usize;
+ pub unsafe fn load_config(context: u64, ptr: *mut u8);
+ pub unsafe fn save_config(context: u64, ptr: *const u8, size: usize);
+ pub safe fn is_saving_config(context: u64) -> usize;
+ pub safe fn saving_config_failed(context: u64) -> usize;
+ }
+}
+
+fn load_storage<T: DeserializeOwned>(context: u64) -> Option<T> {
+ unsafe {
+ let size = tortuise::storage_size(context);
+ let layout = Layout::array::<u8>(size).unwrap();
+ if layout.size() == 0 {
+ return None;
+ }
+ let ptr = alloc(layout);
+ tortuise::load_storage(context, ptr);
+ let data = core::slice::from_raw_parts(ptr, size);
+ let data = messagepack_serde::from_slice(data).ok()?;
+ dealloc(ptr, layout);
+ Some(data)
+ }
+}
+
+fn save_storage<T: Serialize>(context: u64, state: &T) {
+ unsafe {
+ let data = messagepack_serde::to_vec(state).unwrap();
+ tortuise::save_storage(context, data.as_ptr(), data.len());
+ }
+}
+
+fn load_config<T: DeserializeOwned>(context: u64) -> Option<T> {
+ unsafe {
+ let size = tortuise::config_size(context);
+ let layout = Layout::array::<u8>(size).unwrap();
+ if layout.size() == 0 {
+ return None;
+ }
+ let ptr = alloc(layout);
+ tortuise::load_config(context, ptr);
+ let data = core::slice::from_raw_parts(ptr, size);
+ let data = toml::from_slice(data).ok()?;
+ dealloc(ptr, layout);
+ Some(data)
+ }
+}
+
+fn save_config<T: Serialize>(context: u64, state: &T) {
+ unsafe {
+ let data = toml::to_string(state).unwrap();
+ tortuise::save_config(context, data.as_ptr(), data.len());
+ }
+}
+
+pub struct Storage<T> {
+ initial_state: fn() -> T,
+}
+
+pub struct Config<T> {
+ initial_state: fn() -> T,
+}
+
+pub struct StorageMut<'a, 'cx, T: Serialize + Eq> {
+ context: u64,
+ original: T,
+ data: T,
+ _phantom: PhantomData<(&'a (), &'cx ())>,
+}
+
+pub struct ConfigMut<'a, 'cx, T: Serialize + Eq> {
+ context: u64,
+ original: T,
+ data: T,
+ _phantom: PhantomData<(&'a (), &'cx ())>,
+}
+
+impl<T: Serialize + DeserializeOwned + Clone + Eq> Storage<T> {
+ pub const fn new(initial_state: fn() -> T) -> Self {
+ Self { initial_state }
+ }
+
+ pub fn load(&self, context: &ScriptContext) -> T {
+ load_storage(context.0).unwrap_or_else(self.initial_state)
+ }
+
+ pub fn load_mut<'a, 'cx>(&self, context: &'cx mut ScriptContext<'a>) -> StorageMut<'a, 'cx, T> {
+ let data = load_storage(context.0).unwrap_or_else(self.initial_state);
+ StorageMut {
+ context: context.0,
+ original: data.clone(),
+ data,
+ _phantom: PhantomData,
+ }
+ }
+
+ pub fn is_saving(&self, context: &ScriptContext) -> bool {
+ tortuise::is_saving_storage(context.0) > 0
+ }
+
+ pub fn save_failed(&self, context: &ScriptContext) -> bool {
+ tortuise::saving_storage_failed(context.0) > 0
+ }
+}
+
+impl<T: Serialize + DeserializeOwned + Clone + Eq> Config<T> {
+ pub const fn new(initial_state: fn() -> T) -> Self {
+ Self { initial_state }
+ }
+
+ pub fn load(&self, context: &ScriptContext) -> T {
+ load_config(context.0).unwrap_or_else(self.initial_state)
+ }
+
+ pub fn load_mut<'a, 'cx>(&self, context: &'cx mut ScriptContext<'a>) -> StorageMut<'a, 'cx, T> {
+ let data = load_config(context.0).unwrap_or_else(self.initial_state);
+ StorageMut {
+ context: context.0,
+ original: data.clone(),
+ data,
+ _phantom: PhantomData,
+ }
+ }
+
+ pub fn is_saving(&self, context: &ScriptContext) -> bool {
+ tortuise::is_saving_config(context.0) > 0
+ }
+
+ pub fn save_failed(&self, context: &ScriptContext) -> bool {
+ tortuise::saving_config_failed(context.0) > 0
+ }
+}
+
+impl<T: Serialize + Eq> Deref for StorageMut<'_, '_, T> {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ &self.data
+ }
+}
+
+impl<T: Serialize + Eq> DerefMut for StorageMut<'_, '_, T> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.data
+ }
+}
+
+impl<T: Serialize + Eq> Drop for StorageMut<'_, '_, T> {
+ fn drop(&mut self) {
+ if self.data != self.original {
+ save_storage(self.context, &self.data);
+ }
+ }
+}
+
+impl<T: Serialize + Eq> Deref for ConfigMut<'_, '_, T> {
+ type Target = T;
+
+ fn deref(&self) -> &Self::Target {
+ &self.data
+ }
+}
+
+impl<T: Serialize + Eq> DerefMut for ConfigMut<'_, '_, T> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.data
+ }
+}
+
+impl<T: Serialize + Eq> Drop for ConfigMut<'_, '_, T> {
+ fn drop(&mut self) {
+ if self.data != self.original {
+ save_config(self.context, &self.data);
+ }
+ }
+}
diff --git a/src/time.rs b/platforms/tortuise-script-wasm/src/time.rs
index 35e1a86..a7c8b72 100644
--- a/src/time.rs
+++ b/platforms/tortuise-script-wasm/src/time.rs
@@ -27,7 +27,7 @@ pub struct SystemTime(u128);
#[serde(transparent)]
pub struct Instant(u128);
-impl ScriptContext {
+impl ScriptContext<'_> {
pub fn delta_time(&self) -> Duration {
Duration::from_nanos(tortuise::delta_time(self.0))
}
diff --git a/src/input.rs b/src/input.rs
deleted file mode 100644
index a72597d..0000000
--- a/src/input.rs
+++ /dev/null
@@ -1,84 +0,0 @@
-use serde::{Deserialize, Serialize};
-
-use crate::ScriptContext;
-
-mod tortuise {
- #[link(wasm_import_module = "tortuise")]
- unsafe extern "C" {
- pub safe fn is_key_pressed_this_frame(context: u64, keycode: u32) -> u32;
- }
-}
-
-/// Represents a key.
-#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash, Serialize, Deserialize)]
-pub enum KeyCode {
- /// Backspace key (Delete on macOS, Backspace on other platforms).
- Backspace,
- /// Enter key.
- Enter,
- /// Left arrow key.
- Left,
- /// Right arrow key.
- Right,
- /// Up arrow key.
- Up,
- /// Down arrow key.
- Down,
- /// Home key.
- Home,
- /// End key.
- End,
- /// Page up key.
- PageUp,
- /// Page down key.
- PageDown,
- /// Tab key.
- Tab,
- /// Shift + Tab key.
- BackTab,
- /// Delete key. (Fn+Delete on macOS, Delete on other platforms)
- Delete,
- /// Insert key.
- Insert,
- /// F key.
- ///
- /// `KeyCode::F(1)` represents F1 key, etc.
- F(u8),
- /// A character.
- ///
- /// `KeyCode::Char('c')` represents `c` character, etc.
- Char(char),
- /// Null.
- Null,
- /// Escape key.
- Esc,
-}
-
-fn key_code_to_number(key_code: KeyCode) -> u32 {
- match key_code {
- KeyCode::Null => 0,
- KeyCode::Backspace => 1 << 24,
- KeyCode::Enter => 2 << 24,
- KeyCode::Left => 3 << 24,
- KeyCode::Right => 4 << 24,
- KeyCode::Up => 5 << 24,
- KeyCode::Down => 6 << 24,
- KeyCode::Home => 7 << 24,
- KeyCode::End => 8 << 24,
- KeyCode::PageUp => 9 << 24,
- KeyCode::PageDown => 10 << 24,
- KeyCode::Tab => 11 << 24,
- KeyCode::BackTab => 12 << 24,
- KeyCode::Delete => 13 << 24,
- KeyCode::Insert => 14 << 24,
- KeyCode::F(f) => 15 << 24 | f as u32,
- KeyCode::Char(c) => 16 << 24 | c as u32,
- KeyCode::Esc => 17 << 24,
- }
-}
-
-impl ScriptContext {
- pub fn is_key_pressed_this_frame(&self, key_code: KeyCode) -> bool {
- tortuise::is_key_pressed_this_frame(self.0, key_code_to_number(key_code)) > 0
- }
-}
diff --git a/src/lib.rs b/src/lib.rs
index bc94658..c78eccb 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,29 +1,4 @@
-#![no_std]
-
-extern crate alloc;
-
-pub mod allocator;
-pub mod buffer;
-pub mod entity;
-pub mod input;
-pub mod log;
-pub mod process;
-pub mod random;
-pub mod scene;
-pub mod script;
-pub mod state;
-pub mod storage;
-pub mod time;
-
-#[derive(Clone, Copy)]
-#[repr(transparent)]
-pub struct ScriptContext(u64);
-
-#[macro_export]
-macro_rules! prelude {
- () => {
- #[global_allocator]
- static ALLOCATOR: $crate::allocator::BumpAllocator =
- $crate::allocator::BumpAllocator::new();
- };
+cfg_select! {
+ target_arch = "wasm32" => { pub use tortuise_script_wasm::*; }
+ _ => { pub use tortuise_script_compiled::*; }
}
diff --git a/src/scene.rs b/src/scene.rs
deleted file mode 100644
index 95284e9..0000000
--- a/src/scene.rs
+++ /dev/null
@@ -1,32 +0,0 @@
-use crate::ScriptContext;
-
-mod tortuise {
- #[link(wasm_import_module = "tortuise")]
- unsafe extern "C" {
- pub unsafe fn scene_id_by_name(context: u64, name: *const u8, length: usize) -> usize;
- pub safe fn preload_scene(context: u64, id: usize);
- pub safe fn is_scene_loaded(context: u64, id: usize) -> u32;
- pub safe fn switch_scene(context: u64, id: usize);
- }
-}
-
-impl ScriptContext {
- pub fn scene_id(&self, name: &str) -> usize {
- unsafe { tortuise::scene_id_by_name(self.0, name.as_ptr(), name.len()) }
- }
-
- pub fn preload_scene(&self, name: &str) {
- let id = self.scene_id(name);
- tortuise::preload_scene(self.0, id);
- }
-
- pub fn is_scene_loaded(&self, name: &str) -> bool {
- let id = self.scene_id(name);
- tortuise::is_scene_loaded(self.0, id) > 0
- }
-
- pub fn switch_scene(&self, name: &str) {
- let id = self.scene_id(name);
- tortuise::switch_scene(self.0, id);
- }
-}
diff --git a/src/script.rs b/src/script.rs
deleted file mode 100644
index 0de2b54..0000000
--- a/src/script.rs
+++ /dev/null
@@ -1,44 +0,0 @@
-use crate::ScriptContext;
-
-mod tortuise {
- #[link(wasm_import_module = "tortuise")]
- unsafe extern "C" {
- pub unsafe fn script_id_by_name(context: u64, name: *const u8, length: usize) -> usize;
- pub safe fn preload_script(context: u64, id: usize);
- pub safe fn is_script_loaded(context: u64, id: usize) -> u32;
- pub safe fn unload_script(context: u64, id: usize);
- pub safe fn activate_script(context: u64, id: usize);
- pub safe fn deactivate_script(context: u64, id: usize);
- }
-}
-
-impl ScriptContext {
- pub fn script_id(&self, name: &str) -> usize {
- unsafe { tortuise::script_id_by_name(self.0, name.as_ptr(), name.len()) }
- }
-
- pub fn preload_script(&self, name: &str) {
- let id = self.script_id(name);
- tortuise::preload_script(self.0, id)
- }
-
- pub fn is_script_loaded(&self, name: &str) -> bool {
- let id = self.script_id(name);
- tortuise::is_script_loaded(self.0, id) > 0
- }
-
- pub fn unload_script(&self, name: &str) {
- let id = self.script_id(name);
- tortuise::unload_script(self.0, id)
- }
-
- pub fn activate_script(&self, name: &str) {
- let id = self.script_id(name);
- tortuise::activate_script(self.0, id)
- }
-
- pub fn deactivate_script(&self, name: &str) {
- let id = self.script_id(name);
- tortuise::deactivate_script(self.0, id)
- }
-}
diff --git a/src/storage.rs b/src/storage.rs
deleted file mode 100644
index 48fdcb4..0000000
--- a/src/storage.rs
+++ /dev/null
@@ -1,84 +0,0 @@
-use core::{
- alloc::Layout,
- ops::{Deref, DerefMut},
-};
-
-use alloc::alloc::{alloc, dealloc};
-use serde::{Deserialize, Serialize};
-
-use crate::ScriptContext;
-
-mod tortuise {
- #[link(wasm_import_module = "tortuise")]
- unsafe extern "C" {
- pub safe fn storage_size(context: u64) -> usize;
- pub unsafe fn load_storage(context: u64, ptr: *mut u8);
- pub unsafe fn save_storage(context: u64, ptr: *const u8, size: usize);
- }
-}
-
-fn load_storage<'de, T: Deserialize<'de>>(context: u64) -> Option<T> {
- unsafe {
- let size = tortuise::storage_size(context);
- let layout = Layout::array::<u8>(size).unwrap();
- let ptr = alloc(layout);
- tortuise::load_storage(context, ptr);
- let data = core::slice::from_raw_parts(ptr, size);
- let data = messagepack_serde::from_slice(data).ok()?;
- dealloc(ptr, layout);
- Some(data)
- }
-}
-
-fn save_storage<T: Serialize>(context: u64, state: &T) {
- unsafe {
- let data = messagepack_serde::to_vec(state).unwrap();
- tortuise::save_storage(context, data.as_ptr(), data.len());
- }
-}
-
-pub struct Storage<T> {
- initial_state: fn() -> T,
-}
-
-pub struct StorageMut<T: Serialize> {
- context: u64,
- data: T,
-}
-
-impl<'de, T: Serialize + Deserialize<'de>> Storage<T> {
- pub const fn new(initial_state: fn() -> T) -> Self {
- Self { initial_state }
- }
-
- pub fn load(&self, context: &ScriptContext) -> T {
- load_storage(context.0).unwrap_or_else(self.initial_state)
- }
-
- pub fn load_mut(&self, context: &ScriptContext) -> StorageMut<T> {
- StorageMut {
- context: context.0,
- data: load_storage(context.0).unwrap_or_else(self.initial_state),
- }
- }
-}
-
-impl<T: Serialize> Deref for StorageMut<T> {
- type Target = T;
-
- fn deref(&self) -> &Self::Target {
- &self.data
- }
-}
-
-impl<T: Serialize> DerefMut for StorageMut<T> {
- fn deref_mut(&mut self) -> &mut Self::Target {
- &mut self.data
- }
-}
-
-impl<T: Serialize> Drop for StorageMut<T> {
- fn drop(&mut self) {
- save_storage(self.context, &self.data);
- }
-}