summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMica White <botahamec@outlook.com>2026-10-26 10:51:49 -0400
committerMica White <botahamec@outlook.com>2026-10-26 10:51:49 -0400
commit0c128e472b1b49e32c313874eaeccc8c5f1b1202 (patch)
tree2ce06455440e85ec3387f7348b7bd69bf2119634
parentc687e2aba30ca231b03288108b632f1bad464028 (diff)
Finish engineHEADmain
-rw-r--r--Cargo.lock526
-rw-r--r--Cargo.toml7
-rw-r--r--manifest.toml6
-rw-r--r--src/buffer.rs63
-rw-r--r--src/file.rs255
-rw-r--r--src/input.rs220
-rw-r--r--src/lib.rs156
-rw-r--r--src/main.rs161
-rw-r--r--src/scene.rs58
-rw-r--r--src/script.rs230
-rw-r--r--src/script/lua.rs67
-rw-r--r--src/script/wasm.rs380
-rw-r--r--src/storage.rs106
13 files changed, 1379 insertions, 856 deletions
diff --git a/Cargo.lock b/Cargo.lock
index f7237d6..0193acc 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4,18 +4,18 @@ version = 4
[[package]]
name = "android_system_properties"
-version = "0.1.5"
+version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc"
dependencies = [
"libc",
]
[[package]]
name = "anyhow"
-version = "1.0.103"
+version = "1.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"
+checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
[[package]]
name = "async-channel"
@@ -168,15 +168,15 @@ dependencies = [
[[package]]
name = "bitflags"
-version = "2.13.0"
+version = "2.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
+checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
[[package]]
name = "blocking"
-version = "1.6.2"
+version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21"
+checksum = "a70e4329df6cb94385eed412ec92375c3cdd8a6e502493d1229b6414e4036dfa"
dependencies = [
"async-channel",
"async-task",
@@ -187,9 +187,9 @@ dependencies = [
[[package]]
name = "bpaf"
-version = "0.9.26"
+version = "0.9.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0b86829876e7e200161a5aa6ea688d46c32d64d70ee3100127790dde84688d6e"
+checksum = "597e81e56404b7a3e26ef7b449f82a4b7a661ae76c3cac692b42d6bad06a0fbe"
dependencies = [
"bpaf_derive",
]
@@ -202,14 +202,14 @@ checksum = "2f7e98cee839b19076cb3ce1afdb62bb182e04ff5f71f70188827002fae91094"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.119",
]
[[package]]
name = "bstr"
-version = "1.13.0"
+version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f7dc094d718f2e1c1559ad110e27eeaae14a5465d3d56dd6dbd793079fbd530"
+checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f"
dependencies = [
"memchr",
"serde_core",
@@ -222,26 +222,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
[[package]]
-name = "bytemuck"
-version = "1.25.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
-dependencies = [
- "bytemuck_derive",
-]
-
-[[package]]
-name = "bytemuck_derive"
-version = "1.10.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -249,9 +229,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cc"
-version = "1.2.64"
+version = "1.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dad887fd958be91b5098c0248def011f4523ab786cd411be668777e55063501f"
+checksum = "54413ede23c2daf518f35156dfde027feb2374004d63bd497f983c8db9c0e313"
dependencies = [
"find-msvc-tools",
"shlex",
@@ -259,9 +239,15 @@ dependencies = [
[[package]]
name = "cfg-if"
-version = "1.0.4"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
+
+[[package]]
+name = "cfg_aliases"
+version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
+checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527"
[[package]]
name = "chrono"
@@ -317,18 +303,18 @@ checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b"
[[package]]
name = "crossbeam-channel"
-version = "0.5.15"
+version = "0.5.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
+checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
-version = "0.8.21"
+version = "0.8.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
+checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6"
[[package]]
name = "crossterm"
@@ -376,7 +362,7 @@ dependencies = [
"proc-macro2",
"quote",
"rustc_version",
- "syn",
+ "syn 2.0.119",
]
[[package]]
@@ -390,9 +376,9 @@ dependencies = [
[[package]]
name = "either"
-version = "1.16.0"
+version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e"
+checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34"
[[package]]
name = "embedded-io"
@@ -445,11 +431,10 @@ dependencies = [
[[package]]
name = "event-listener"
-version = "5.4.1"
+version = "5.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab"
+checksum = "5a23add41df1562121a9393cb065eab5146a1242410f23a644851e90cfd669d2"
dependencies = [
- "concurrent-queue",
"parking",
"pin-project-lite",
]
@@ -466,27 +451,33 @@ dependencies = [
[[package]]
name = "fastrand"
-version = "2.4.1"
+version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6"
+checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
[[package]]
name = "find-msvc-tools"
-version = "0.1.9"
+version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
+checksum = "ef25905e51abafe4dcea6c15fec58c57b601cdbd0ee53d22ea1d3016c587d39b"
+
+[[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.33"
+version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2cd50c473c80f6d7c3670a752354b8e569b1a7cbfdc0419ec88e5edad85e0dc7"
+checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
[[package]]
name = "futures-io"
-version = "0.3.33"
+version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a"
+checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed"
[[package]]
name = "futures-lite"
@@ -503,15 +494,15 @@ dependencies = [
[[package]]
name = "futures-task"
-version = "0.3.33"
+version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b231ed28831efb4a61a08580c4bc233ec56bc009f4cd8f52da2c3cb97df0c109"
+checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
[[package]]
name = "futures-util"
-version = "0.3.33"
+version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a77a90a256fce34da66415271e30f94ee91c57b04b8a2c042d9cf3220179deaa"
+checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
dependencies = [
"futures-core",
"futures-task",
@@ -542,6 +533,40 @@ dependencies = [
]
[[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"
@@ -566,15 +591,15 @@ dependencies = [
"hash32",
"rustc_version",
"serde",
- "spin 0.9.8",
+ "spin 0.9.9",
"stable_deref_trait",
]
[[package]]
name = "hermit-abi"
-version = "0.5.2"
+version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
+checksum = "e17592d60ebacc7d5e169f4663c5f84f9161cc90328abcfe8456f41e4dfcb284"
[[package]]
name = "hex_color"
@@ -611,15 +636,35 @@ dependencies = [
[[package]]
name = "indexmap"
-version = "2.14.0"
+version = "2.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
+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"
@@ -630,9 +675,9 @@ dependencies = [
[[package]]
name = "js-sys"
-version = "0.3.103"
+version = "0.3.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102"
+checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e"
dependencies = [
"cfg-if",
"futures-util",
@@ -641,9 +686,9 @@ dependencies = [
[[package]]
name = "libc"
-version = "0.2.186"
+version = "0.2.189"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
+checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
[[package]]
name = "libm"
@@ -652,12 +697,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981"
[[package]]
-name = "libmimalloc-sys"
-version = "0.1.49"
+name = "libudev-sys"
+version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a45a52f43e1c16f667ccfe4dd8c85b7f7c204fd5e3bf46c5b0db9a5c3c0b8e9"
+checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324"
dependencies = [
- "cc",
+ "libc",
+ "pkg-config",
]
[[package]]
@@ -683,24 +729,24 @@ dependencies = [
[[package]]
name = "log"
-version = "0.4.32"
+version = "0.4.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a"
+checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
[[package]]
name = "lua-src"
-version = "550.1.1"
+version = "551.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "75c110c2fa33f34e0de05448e1f3eb2e0631e7a69e2d8ae1586cffc9fc9f9949"
+checksum = "d400ffef0e3d4d29287092bdc5a276d0bf468b2c69709d5bab0d469312d9f947"
dependencies = [
"cc",
]
[[package]]
name = "luajit-src"
-version = "210.7.2+b925b3e"
+version = "210.7.3+1ee778a"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "920cf654b23d217c550ceea57c32cd2a413ea27b6d47ed77b5ee0cf655adefa6"
+checksum = "869665372263eb337b14f480cfb864b89f12eade4eb42cb415f71517b4a67572"
dependencies = [
"cc",
"which",
@@ -714,24 +760,35 @@ checksum = "7ef0d4ed8669f8f8826eb00dc878084aa8f253506c4fd5e8f58f5bce72ddb97e"
[[package]]
name = "memchr"
-version = "2.8.2"
+version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
+checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
+
+[[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 = "mimalloc"
-version = "0.1.52"
+name = "messagepack-serde"
+version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2d4139bb28d14ad1facf21d5eb8825051b326e172d216b39f6d31df53cc97862"
+checksum = "4a630d3ff4e4c893267925baffa3f8e75f0cdb20d212170ee7d8576a45ac1869"
dependencies = [
- "libmimalloc-sys",
+ "messagepack-core",
+ "num-traits",
+ "serde",
]
[[package]]
name = "mio"
-version = "1.2.1"
+version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda"
+checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8"
dependencies = [
"libc",
"log",
@@ -741,9 +798,9 @@ dependencies = [
[[package]]
name = "mlua"
-version = "0.12.0"
+version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ad72ffa037cf5970c9860674f32f703fda25d86cf217475fe7a79c5f9961bcaa"
+checksum = "d96e5d00f19d8c46c71ceaced99593b90c31c57aa1fe2cb3e93a8b1698eedba9"
dependencies = [
"bstr",
"either",
@@ -759,9 +816,9 @@ dependencies = [
[[package]]
name = "mlua-sys"
-version = "0.11.0"
+version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "92136787b906d4e55cfe96cd6c62e010bb1a56889d0d6cf83eb016dbad07576b"
+checksum = "b806d7ade031f5d6607eae3e283fb034cb795a76247dd0d1ba753c8c42debccf"
dependencies = [
"cc",
"cfg-if",
@@ -772,6 +829,18 @@ dependencies = [
]
[[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"
@@ -781,6 +850,26 @@ 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"
@@ -843,9 +932,9 @@ dependencies = [
[[package]]
name = "pkg-config"
-version = "0.3.33"
+version = "0.3.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
+checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
[[package]]
name = "polling"
@@ -885,9 +974,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
-version = "1.0.106"
+version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
+checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
dependencies = [
"unicode-ident",
]
@@ -932,9 +1021,9 @@ dependencies = [
[[package]]
name = "quote"
-version = "1.0.45"
+version = "1.0.47"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
+checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
dependencies = [
"proc-macro2",
]
@@ -947,9 +1036,9 @@ checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
[[package]]
name = "rand"
-version = "0.8.7"
+version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a"
+checksum = "e058c7de0b26af77780c769414d6257830bb240f3c38477dbc2c16e5f54d6d4c"
dependencies = [
"libc",
"rand_chacha",
@@ -986,9 +1075,9 @@ dependencies = [
[[package]]
name = "rustc-hash"
-version = "2.1.2"
+version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe"
+checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d"
[[package]]
name = "rustc_version"
@@ -1001,9 +1090,9 @@ dependencies = [
[[package]]
name = "rustix"
-version = "1.1.4"
+version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
+checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d"
dependencies = [
"bitflags",
"errno",
@@ -1032,9 +1121,9 @@ checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
[[package]]
name = "serde"
-version = "1.0.228"
+version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
+checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
dependencies = [
"serde_core",
"serde_derive",
@@ -1052,31 +1141,22 @@ dependencies = [
[[package]]
name = "serde_core"
-version = "1.0.228"
+version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
+checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.228"
+version = "1.0.229"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
+checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
dependencies = [
"proc-macro2",
"quote",
- "syn",
-]
-
-[[package]]
-name = "serde_spanned"
-version = "1.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26"
-dependencies = [
- "serde_core",
+ "syn 3.0.6",
]
[[package]]
@@ -1124,9 +1204,9 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
name = "smallvec"
-version = "1.15.2"
+version = "1.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
+checksum = "ba467056f1b547ed52077911161fc86985becbc60e8e1857c8a144dab0def891"
[[package]]
name = "smol"
@@ -1147,18 +1227,18 @@ dependencies = [
[[package]]
name = "spin"
-version = "0.9.8"
+version = "0.9.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
+checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e"
dependencies = [
"lock_api",
]
[[package]]
name = "spin"
-version = "0.12.2"
+version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8abadc99fd9c7bbb7d0ca2b31d72a067d0c0dcd7aad25ab8cac71ba91417694b"
+checksum = "0134f9043ed38b087ac4f7d4af44c79e2c9e5094421fe3164f435ce585953b10"
dependencies = [
"lock_api",
]
@@ -1171,9 +1251,20 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596"
[[package]]
name = "syn"
-version = "2.0.118"
+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.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422"
+checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee"
dependencies = [
"proc-macro2",
"quote",
@@ -1182,27 +1273,27 @@ dependencies = [
[[package]]
name = "thiserror"
-version = "2.0.18"
+version = "2.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
+checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
-version = "2.0.18"
+version = "2.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
+checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 3.0.6",
]
[[package]]
name = "tinywasm"
-version = "0.10.0"
+version = "0.11.0-pre.0"
dependencies = [
"libm",
"log",
@@ -1212,7 +1303,7 @@ dependencies = [
[[package]]
name = "tinywasm-parser"
-version = "0.10.0"
+version = "0.11.0-pre.0"
dependencies = [
"log",
"tinywasm-types",
@@ -1221,7 +1312,7 @@ dependencies = [
[[package]]
name = "tinywasm-types"
-version = "0.10.0"
+version = "0.11.0-pre.0"
dependencies = [
"log",
"postcard",
@@ -1229,57 +1320,18 @@ dependencies = [
]
[[package]]
-name = "toml"
-version = "1.1.2+spec-1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee"
-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.2+spec-1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526"
-dependencies = [
- "winnow",
-]
-
-[[package]]
-name = "toml_writer"
-version = "1.1.1+spec-1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db"
-
-[[package]]
name = "tortuise"
version = "0.1.0"
dependencies = [
"bpaf",
- "bytemuck",
"chrono",
"crossterm",
"etcetera",
"getrandom 0.4.3",
+ "gilrs",
"hex_color",
"log",
- "mimalloc",
+ "messagepack-serde",
"mlua",
"postcard",
"prologger",
@@ -1288,9 +1340,8 @@ dependencies = [
"rustc-hash",
"serde",
"smol",
- "spin 0.12.2",
+ "spin 0.12.3",
"tinywasm",
- "toml",
]
[[package]]
@@ -1301,9 +1352,9 @@ checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
[[package]]
name = "unicode-ident"
-version = "1.0.24"
+version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
+checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
[[package]]
name = "unicode-segmentation"
@@ -1312,6 +1363,22 @@ 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"
@@ -1319,9 +1386,9 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasm-bindgen"
-version = "0.2.126"
+version = "0.2.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4"
+checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf"
dependencies = [
"cfg-if",
"once_cell",
@@ -1332,9 +1399,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
-version = "0.2.126"
+version = "0.2.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1"
+checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -1342,41 +1409,51 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
-version = "0.2.126"
+version = "0.2.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e"
+checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a"
dependencies = [
"bumpalo",
"proc-macro2",
"quote",
- "syn",
+ "syn 3.0.6",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
-version = "0.2.126"
+version = "0.2.128"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24"
+checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e"
dependencies = [
"unicode-ident",
]
[[package]]
name = "wasmparser"
-version = "0.254.0"
+version = "0.255.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d5769a29f799fbab136aaf65b4fe5384cd7d93fe6fc9ba0dcb6c8382a1f16e27"
+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.5"
+version = "8.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f3ef584124b911bcc3875c2f1472e80f24361ceb789bd1c62b3e9a3df9ff43c"
+checksum = "bae2f2b2b816647a1cab1acc91f5bd20812d53cb344382635ec2181940c8034f"
dependencies = [
"libc",
]
@@ -1404,6 +1481,27 @@ 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"
@@ -1417,6 +1515,17 @@ dependencies = [
]
[[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"
@@ -1424,7 +1533,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.119",
]
[[package]]
@@ -1435,7 +1544,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.119",
]
[[package]]
@@ -1445,6 +1554,16 @@ 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"
@@ -1472,27 +1591,30 @@ dependencies = [
]
[[package]]
-name = "winnow"
-version = "1.0.3"
+name = "windows-threading"
+version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1"
+checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37"
+dependencies = [
+ "windows-link",
+]
[[package]]
name = "zerocopy"
-version = "0.8.55"
+version = "0.8.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb"
+checksum = "d35102a9f36d089ccae9e4c6802bc118be4487b80aaffc0ab4e0cf5ce92d2873"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
-version = "0.8.55"
+version = "0.8.57"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb"
+checksum = "146c01f5ab44258da43cf276c74a2763db2ff3969c9c652c3f2de07041d0b2bc"
dependencies = [
"proc-macro2",
"quote",
- "syn",
+ "syn 2.0.119",
]
diff --git a/Cargo.toml b/Cargo.toml
index abde866..1e60e85 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -5,15 +5,15 @@ edition = "2024"
[dependencies]
bpaf = { version = "0.9", features = ["derive"] }
-bytemuck = { version = "1", features = ["derive"] }
crossterm = "0.29"
etcetera = "0.11"
getrandom = "0.4"
hex_color = "3"
chrono = "0.4"
log = "0.4"
+gilrs = "0.11"
+messagepack-serde = "0.2"
mlua = { version = "0.12", features = ["lua51", "vendored", "serde", "send", "error-send"] }
-mimalloc = "0.1"
postcard = { version = "1", features = ["use-std"] }
prologger = "0.3"
puffin = "0.20"
@@ -23,9 +23,10 @@ serde = { version = "1", features = ["derive"] }
smol = "2"
spin = "0.12"
tinywasm = { path = "../tinywasm/crates/tinywasm" }
-toml = "1"
[profile.release]
opt-level = 'z'
lto = true
+codegen-units = 1
panic = "abort"
+strip = true
diff --git a/manifest.toml b/manifest.toml
deleted file mode 100644
index d6f7f8d..0000000
--- a/manifest.toml
+++ /dev/null
@@ -1,6 +0,0 @@
-name = "framerate_test"
-first_scene = "main"
-
-[[scene]]
-name = "main"
-scripts = { required = [0, 1], preload = [] }
diff --git a/src/buffer.rs b/src/buffer.rs
index 6d983db..dcb7b8b 100644
--- a/src/buffer.rs
+++ b/src/buffer.rs
@@ -1,8 +1,11 @@
-use std::io::Write;
+use std::{io::Write, time::Duration};
use crossterm::{
cursor::{Hide, MoveTo, Show},
- event::{self, KeyEvent},
+ event::{
+ self, DisableFocusChange, DisableMouseCapture, EnableFocusChange, EnableMouseCapture,
+ KeyEvent, MouseEvent,
+ },
execute, queue,
style::{ContentStyle, PrintStyledContent, StyledContent},
terminal::{
@@ -29,6 +32,8 @@ pub struct Buffer {
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Event {
Key(KeyEvent),
+ Mouse(MouseEvent),
+ FocusChanged(bool),
Render,
}
@@ -38,6 +43,9 @@ impl TryFrom<crossterm::event::Event> for Event {
fn try_from(value: crossterm::event::Event) -> Result<Self, Self::Error> {
match value {
crossterm::event::Event::Key(event) => Ok(Event::Key(event)),
+ crossterm::event::Event::Mouse(event) => Ok(Event::Mouse(event)),
+ crossterm::event::Event::FocusGained => Ok(Event::FocusChanged(true)),
+ crossterm::event::Event::FocusLost => Ok(Event::FocusChanged(false)),
_ => Err(()),
}
}
@@ -54,7 +62,13 @@ impl Buffer {
fn initialize(writer: &mut impl Write) -> std::io::Result<()> {
enable_raw_mode()?;
- execute!(writer, Hide, EnterAlternateScreen)?;
+ queue!(
+ writer,
+ Hide,
+ EnterAlternateScreen,
+ EnableMouseCapture,
+ EnableFocusChange
+ )?;
Ok(())
}
@@ -82,7 +96,13 @@ impl Buffer {
}
pub fn finish(&mut self) -> std::io::Result<()> {
- execute!(self.stdout, Show, LeaveAlternateScreen)?;
+ execute!(
+ self.stdout,
+ Show,
+ DisableMouseCapture,
+ DisableFocusChange,
+ LeaveAlternateScreen
+ )?;
disable_raw_mode()?;
Ok(())
}
@@ -92,29 +112,18 @@ impl Buffer {
mut event_handler: impl FnMut(&mut Self, Event),
) -> std::io::Result<()> {
Self::initialize(&mut self.stdout)?;
- let (sender, receiver) = std::sync::mpsc::channel();
- std::thread::scope(|scope| -> std::io::Result<()> {
- scope.spawn(|| {
- loop {
- let Ok(event) = event::read() else {
- continue;
- };
- let _ = sender.send(event);
- }
- });
- loop {
- puffin::profile_scope!("frame");
- while let Ok(event) = receiver.try_recv() {
- if let Ok(event) = event.try_into() {
- event_handler(self, event);
- }
- }
- event_handler(self, Event::Render);
- self.render()?;
- puffin::GlobalProfiler::lock().new_frame();
+ loop {
+ puffin::profile_scope!("frame");
+ while matches!(event::poll(Duration::ZERO), Ok(true)) {
+ let Ok(event) = event::read() else { continue };
+ let Ok(event) = event.try_into() else {
+ continue;
+ };
+ event_handler(self, event);
}
- })?;
- self.finish()?;
- Ok(())
+ event_handler(self, Event::Render);
+ self.render()?;
+ puffin::GlobalProfiler::lock().new_frame();
+ }
}
}
diff --git a/src/file.rs b/src/file.rs
index 4aedd48..b8f6610 100644
--- a/src/file.rs
+++ b/src/file.rs
@@ -1,178 +1,96 @@
-use std::{ffi::OsStr, path::Path};
+use std::{
+ ffi::OsStr,
+ path::Path,
+ sync::{
+ Arc,
+ atomic::{AtomicBool, Ordering},
+ mpsc::{Receiver, Sender},
+ },
+ time::{Duration, Instant, SystemTime},
+};
use rustc_hash::FxHashMap;
-use serde::{Deserialize, Serialize};
-use smol::{Task, unblock};
+use smol::{Task, block_on, unblock};
-use crate::{
- scene::{ResourceRequirements, Scene, scene_id_by_name},
- script::{ScriptDeclaration, WasmRunner, lua::LuaModule, wasm::WasmModule},
-};
+use crate::script::{WasmRunner, wasm::WasmModule};
-type FileTask = Task<std::io::Result<FileData>>;
+type FileTask = Task<std::io::Result<WasmModule>>;
pub struct FileManager {
+ watcher: Notifier,
+ updated_files: Receiver<Box<Path>>,
active_tasks: FxHashMap<Box<Path>, FileTask>,
- cache: FxHashMap<Box<Path>, FileData>,
+ cache: FxHashMap<Box<Path>, Arc<std::io::Result<WasmModule>>>,
}
-#[derive(Debug, Clone)]
-pub struct GameManifest {
- pub name: Box<str>,
- pub first_scene: usize,
- pub scenes: Box<[Scene]>,
- pub scripts: Box<[ScriptDeclaration]>,
+struct Notifier {
+ paths: Arc<spin::Mutex<Vec<Box<Path>>>>,
+ cancel_token: Arc<AtomicBool>,
}
-#[derive(Debug, Serialize, Deserialize)]
-pub struct TomlGameManifest {
- name: Box<str>,
- first_scene: Box<str>,
- #[serde(default)]
- scene: Box<[TomlScene]>,
- #[serde(default)]
- script: Box<[TomlScript]>,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-struct TomlScene {
- name: Box<str>,
- #[serde(default)]
- preload_scenes: Box<[Box<str>]>,
- #[serde(default)]
- scripts: Box<[Box<str>]>,
- #[serde(default)]
- preload_scripts: Box<[Box<str>]>,
-}
-
-#[derive(Debug, Serialize, Deserialize)]
-#[serde(tag = "type", rename_all = "kebab-case")]
-enum TomlScript {
- Wasm { name: Box<str>, path: Box<Path> },
- Lua { name: Box<str>, path: Box<Path> },
-}
-
-pub enum FileData {
- GameManifest(GameManifest),
- WasmModule(Box<WasmModule>),
- LuaModule(Box<LuaModule>),
-}
-
-pub fn normalize_game_manifest(manifest: TomlGameManifest) -> std::io::Result<GameManifest> {
- let mut scripts = Vec::with_capacity(manifest.script.len());
- let script_map: FxHashMap<Box<str>, usize> = manifest
- .script
- .into_iter()
- .map(|script| match script {
- TomlScript::Wasm { name, path } => {
- scripts.push(ScriptDeclaration::Wasm {
- name: name.clone(),
- path,
- });
- (name, scripts.len() - 1)
- }
- TomlScript::Lua { name, path } => {
- scripts.push(ScriptDeclaration::Lua {
- name: name.clone(),
- path,
- });
- (name, scripts.len() - 1)
- }
- })
- .collect();
- let scenes: Box<[Scene]> = manifest
- .scene
- .into_iter()
- .map(|scene| Scene {
- name: scene.name,
- preload_scenes: Box::from([]),
- scripts: ResourceRequirements {
- required: scene
- .scripts
- .iter()
- .map(|s| *script_map.get(s).unwrap())
- .collect(),
- preload: scene
- .preload_scripts
- .iter()
- .map(|s| *script_map.get(s).unwrap())
- .collect(),
- },
- })
- .collect();
- let first_scene = scene_id_by_name(&scenes, &manifest.first_scene).ok_or(
- std::io::Error::other("Invalid first scene: scene not found"),
- )?;
- Ok(GameManifest {
- name: manifest.name,
- first_scene,
- scenes,
- scripts: scripts.into_boxed_slice(),
- })
-}
-
-fn compile_data(path: &Path, data: &[u8], compiler: &WasmRunner) -> std::io::Result<FileData> {
- if path == "tortuise.toml" {
- return Ok(FileData::GameManifest(normalize_game_manifest(
- toml::from_slice(data).map_err(std::io::Error::other)?,
- )?));
- }
+fn compile_data(path: &Path, data: &[u8], compiler: &WasmRunner) -> std::io::Result<WasmModule> {
let extension = path
.extension()
.and_then(OsStr::to_str)
.map(|ext| ext.to_lowercase());
if extension.as_deref() == Some("wasm") {
- return Ok(FileData::WasmModule(Box::new(
- compiler.compile(data).map_err(std::io::Error::other)?,
- )));
- }
- if extension.as_deref() == Some("lua") {
- return Ok(FileData::LuaModule(Box::new(
- LuaModule::new(data).map_err(std::io::Error::other)?,
- )));
+ return compiler.compile(data).map_err(std::io::Error::other);
}
Err(std::io::Error::other("Unrecognized file extension"))
}
-impl FileData {
- pub fn unwrap_manifest(&self) -> &GameManifest {
- if let FileData::GameManifest(manifest) = self {
- manifest
- } else {
- panic!("Not a game manifest");
- }
- }
+impl Notifier {
+ pub fn new(channel: Sender<Box<Path>>, interval: Duration) -> Self {
+ let paths = Arc::new(spin::Mutex::new(Vec::new()));
+ let paths_2 = paths.clone();
+ let cancel_token = Arc::new(AtomicBool::new(false));
+ let cancel_token_2 = cancel_token.clone();
+ std::thread::spawn(move || {
+ loop {
+ let last_check = SystemTime::now();
- pub fn unwrap_wasm_module(&self) -> &WasmModule {
- if let FileData::WasmModule(module) = self {
- module
- } else {
- panic!("Not a WASM module")
- }
- }
+ std::thread::sleep(interval);
+ if cancel_token_2.load(Ordering::Acquire) {
+ break;
+ }
+ let paths = paths_2.lock().clone();
+ for path in paths {
+ if let Ok(metadata) = std::fs::metadata(&path)
+ && let Ok(mtime) = metadata.modified()
+ && mtime > last_check
+ && let Err(e) = channel.send(path)
+ {
+ log::error!("{e}");
+ }
+ }
+ }
+ });
- pub fn unwrap_lua_module(&self) -> &LuaModule {
- if let FileData::LuaModule(module) = self {
- module
- } else {
- panic!("Not a Lua module")
+ Self {
+ paths,
+ cancel_token,
}
}
- pub fn unwrap_wasm_module_mut(&mut self) -> &mut WasmModule {
- if let FileData::WasmModule(module) = self {
- module
- } else {
- panic!("Not a WASM module")
+ pub fn watch(&mut self, path: Box<Path>) {
+ let mut paths = self.paths.lock();
+ if paths.contains(&path) {
+ return;
}
+
+ paths.push(path);
}
}
impl FileManager {
pub fn new() -> Self {
+ let (sender, updated_files) = std::sync::mpsc::channel();
+ let watcher = Notifier::new(sender, Duration::from_secs(10));
Self {
active_tasks: FxHashMap::default(),
cache: FxHashMap::default(),
+ watcher,
+ updated_files,
}
}
@@ -180,28 +98,30 @@ impl FileManager {
&mut self,
path: Box<Path>,
wasm_compiler: &WasmRunner,
- ) -> std::io::Result<&FileData> {
- if !self.cache.contains_key(&path) {
- let data = compile_data(
- path.as_ref(),
- &std::fs::read(path.as_ref()).map(Vec::into_boxed_slice)?,
- wasm_compiler,
- )?;
- self.cache.entry(path.clone()).insert_entry(data);
+ ) -> Arc<std::io::Result<WasmModule>> {
+ if let Some(task) = self.active_tasks.get(&path)
+ && task.is_finished()
+ {
+ log::info!("loaded {}", path.to_string_lossy());
+ let task = self.active_tasks.remove(&path).unwrap();
+ let data = block_on(task);
+ self.cache.insert(path.clone(), Arc::new(data));
+ } else if !self.cache.contains_key(&path) {
+ let read_time = Instant::now();
+ let data = std::fs::read(path.as_ref()).map(Vec::into_boxed_slice);
+ log::info!("Load time: {} ms", read_time.elapsed().as_millis());
+ let compile_time = Instant::now();
+ let data = data.and_then(|data| compile_data(path.as_ref(), &data, wasm_compiler));
+ log::info!("Compile time: {} ms", compile_time.elapsed().as_millis());
+ self.cache.entry(path.clone()).insert_entry(Arc::new(data));
}
- Ok(self.cache.get(&path).unwrap())
- }
- pub fn get_cached_file(&self, path: &Path) -> Option<&FileData> {
- self.cache.get(path)
+ self.watcher.watch(path.clone());
+ self.cache.get(&path).unwrap().clone()
}
- pub fn get_cached_file_mut(&mut self, path: &Path) -> Option<&mut FileData> {
- self.cache.get_mut(path)
- }
-
- pub fn start_loading_file(&mut self, path: impl AsRef<Path>, wasm_compiler: &WasmRunner) {
- let boxed_path = Box::from(path.as_ref());
+ pub fn start_loading_file(&mut self, path: Box<Path>, wasm_compiler: &WasmRunner) {
+ let boxed_path = path.clone();
let compiler = wasm_compiler.clone();
let task = unblock(move || {
compile_data(
@@ -210,8 +130,15 @@ impl FileManager {
&compiler,
)
});
- let boxed_path = Box::from(path.as_ref());
- self.active_tasks.insert(boxed_path, task);
+ self.watcher.watch(path.clone());
+ self.active_tasks.insert(path, task);
+ }
+
+ pub fn reload_files(&mut self, wasm_compiler: &WasmRunner) {
+ while let Ok(path) = self.updated_files.try_recv() {
+ log::info!("reloading {}", path.to_string_lossy());
+ self.start_loading_file(path, wasm_compiler);
+ }
}
pub fn loaded_files(&self) -> Box<[Box<Path>]> {
@@ -241,3 +168,9 @@ impl Default for FileManager {
Self::new()
}
}
+
+impl Drop for Notifier {
+ fn drop(&mut self) {
+ self.cancel_token.store(true, Ordering::Release);
+ }
+}
diff --git a/src/input.rs b/src/input.rs
index 90ddbb2..3eea33c 100644
--- a/src/input.rs
+++ b/src/input.rs
@@ -1,15 +1,65 @@
+use std::{collections::VecDeque, fmt::Debug, time::Duration};
+
use crossterm::event::KeyCode;
-use rustc_hash::FxHashSet;
+use gilrs::{
+ Axis, Button, GamepadId, Gilrs, PowerInfo,
+ ff::{BaseEffect, Replay, Ticks},
+};
+use rustc_hash::{FxHashMap, FxHashSet};
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum Direction {
+ Up = 1,
+ Down,
+ Left,
+ Right,
+}
+
+#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
+pub struct Position {
+ pub x: u16,
+ pub y: u16,
+}
+
+#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
+struct ControllerButton {
+ gamepad_id: usize,
+ button: Button,
+}
-#[derive(Debug, Default, Clone)]
+#[derive(Debug)]
pub struct InputManager {
pressed_keys: FxHashSet<KeyCode>,
+ mouse_clicked: bool,
+ mouse_location: Option<Position>,
+ scroll_direction: Option<Direction>,
+ is_focused: bool,
+ is_focused_this_frame: bool,
+ is_unfocused_this_frame: bool,
+ gil_cx: Option<Gilrs>,
+ gamepad_map: FxHashMap<usize, GamepadId>,
+ gamepad_map_rev: FxHashMap<GamepadId, usize>,
+ connected_gamepads: VecDeque<usize>,
+ pressed_buttons: FxHashSet<ControllerButton>,
+ released_buttons: FxHashSet<ControllerButton>,
}
impl InputManager {
pub fn new() -> Self {
Self {
pressed_keys: FxHashSet::default(),
+ mouse_clicked: false,
+ mouse_location: None,
+ scroll_direction: None,
+ is_focused: true,
+ is_focused_this_frame: false,
+ is_unfocused_this_frame: false,
+ gil_cx: Gilrs::new().ok(),
+ gamepad_map: FxHashMap::default(),
+ gamepad_map_rev: FxHashMap::default(),
+ connected_gamepads: VecDeque::new(),
+ pressed_buttons: FxHashSet::default(),
+ released_buttons: FxHashSet::default(),
}
}
@@ -17,12 +67,178 @@ impl InputManager {
self.pressed_keys.insert(key_code);
}
+ pub fn click_mouse(&mut self) {
+ self.mouse_clicked = true;
+ }
+
+ pub fn move_mouse(&mut self, location: Position) {
+ self.mouse_location = Some(location);
+ }
+
+ pub fn scroll(&mut self, direction: Direction) {
+ self.scroll_direction = Some(direction);
+ }
+
pub fn reset_next_frame(&mut self) {
puffin::profile_function!();
self.pressed_keys.clear();
+ self.mouse_clicked = false;
+ self.scroll_direction = None;
+ self.is_focused_this_frame = false;
+ self.is_unfocused_this_frame = false;
+ self.pressed_buttons.clear();
+ self.released_buttons.clear();
+ self.connected_gamepads.pop_front();
}
pub fn is_key_pressed_this_frame(&self, key_code: KeyCode) -> bool {
self.pressed_keys.contains(&key_code)
}
+
+ pub fn is_mouse_clicked_this_frame(&self) -> bool {
+ self.mouse_clicked
+ }
+
+ pub fn mouse_location(&self) -> Option<Position> {
+ self.mouse_location
+ }
+
+ pub fn scroll_direction_this_frame(&self) -> Option<Direction> {
+ self.scroll_direction
+ }
+
+ pub fn read_gil_events(&mut self) {
+ let Some(gil) = self.gil_cx.as_mut() else {
+ return;
+ };
+ while let Some(event) = gil.next_event() {
+ let gamepad_id = self.gamepad_map_rev.get(&event.id).copied();
+ match event.event {
+ gilrs::EventType::Connected => {
+ let gamepad_id = self.gamepad_map.len() + 1;
+ self.gamepad_map.insert(gamepad_id, event.id);
+ self.gamepad_map_rev.insert(event.id, gamepad_id);
+ self.connected_gamepads.push_back(gamepad_id);
+ }
+ gilrs::EventType::ButtonPressed(button, _) => {
+ let Some(gamepad_id) = gamepad_id else {
+ continue;
+ };
+ self.pressed_buttons
+ .insert(ControllerButton { gamepad_id, button });
+ }
+ gilrs::EventType::ButtonReleased(button, _) => {
+ let Some(gamepad_id) = gamepad_id else {
+ continue;
+ };
+ self.released_buttons
+ .insert(ControllerButton { gamepad_id, button });
+ }
+ _ => {}
+ }
+ }
+ }
+
+ pub fn is_gamepad_connected_this_frame(&self) -> Option<usize> {
+ self.connected_gamepads.front().copied()
+ }
+
+ pub fn is_button_pressed_this_frame(&self, gamepad_id: usize, button: Button) -> bool {
+ self.pressed_buttons
+ .contains(&ControllerButton { gamepad_id, button })
+ }
+
+ pub fn is_button_released_this_frame(&self, gamepad_id: usize, button: Button) -> bool {
+ self.released_buttons
+ .contains(&ControllerButton { gamepad_id, button })
+ }
+
+ pub fn is_button_down(&self, gamepad_id: usize, button: Button) -> bool {
+ self.gamepad_map.get(&gamepad_id).is_some_and(|id| {
+ self.gil_cx
+ .as_ref()
+ .is_some_and(|gil| gil.gamepad(*id).is_pressed(button))
+ })
+ }
+
+ pub fn axis_value(&self, gamepad_id: usize, axis: Axis) -> Option<f32> {
+ self.gamepad_map
+ .get(&gamepad_id)
+ .and_then(|id| self.gil_cx.as_ref().map(|gil| gil.gamepad(*id).value(axis)))
+ }
+
+ pub fn is_gamepad_connected(&self, gamepad_id: usize) -> bool {
+ self.gamepad_map.get(&gamepad_id).is_some_and(|id| {
+ self.gil_cx
+ .as_ref()
+ .is_some_and(|gil| gil.gamepad(*id).is_connected())
+ })
+ }
+
+ pub fn gamepad_power_info(&self, gamepad_id: usize) -> Option<PowerInfo> {
+ self.gamepad_map.get(&gamepad_id).and_then(|id| {
+ self.gil_cx
+ .as_ref()
+ .map(|gil| gil.gamepad(*id).power_info())
+ })
+ }
+
+ pub fn vibrate(&mut self, gamepad_id: usize, intensity: f32, duration: Duration) {
+ let Some(gamepad_id) = self.gamepad_map.get(&gamepad_id) else {
+ return;
+ };
+ let Some(gil) = self.gil_cx.as_mut() else {
+ return;
+ };
+ let gamepad = gil.gamepad(*gamepad_id);
+ let Ok(effect) = gilrs::ff::EffectBuilder::new()
+ .add_gamepad(&gamepad)
+ .add_effect(BaseEffect {
+ kind: gilrs::ff::BaseEffectType::Strong {
+ magnitude: (intensity * u16::MAX as f32).round() as u16,
+ },
+ scheduling: Replay {
+ after: Ticks::from_ms(0),
+ play_for: Ticks::from_ms(duration.as_millis() as u32),
+ with_delay: Ticks::from_ms(0),
+ },
+ envelope: todo!(),
+ })
+ .repeat(gilrs::ff::Repeat::For(Ticks::from_ms(
+ duration.as_millis() as u32
+ )))
+ .finish(gil)
+ else {
+ return;
+ };
+ _ = effect.play();
+ }
+
+ pub fn focus(&mut self) {
+ self.is_focused = true;
+ self.is_focused_this_frame = true;
+ }
+
+ pub fn unfocus(&mut self) {
+ self.is_focused = false;
+ self.is_unfocused_this_frame = true;
+ }
+
+ pub fn is_focused(&self) -> bool {
+ self.is_focused
+ }
+
+ pub fn was_focused_this_frame(&self) -> bool {
+ self.is_focused_this_frame
+ }
+
+ pub fn was_unfocused_this_frame(&self) -> bool {
+ self.is_unfocused_this_frame
+ }
+}
+
+impl Default for InputManager {
+ fn default() -> Self {
+ Self::new()
+ }
}
diff --git a/src/lib.rs b/src/lib.rs
index 767eff9..1418ebd 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,7 +1,163 @@
pub mod buffer;
pub mod file;
pub mod input;
+pub mod manifest;
pub mod scene;
pub mod script;
pub mod storage;
pub mod time;
+
+use crate::{
+ buffer::{Buffer, Event},
+ file::FileManager,
+ input::{Direction, InputManager, Position},
+ manifest::GameManifest,
+ scene::switch_scenes,
+ script::{ScriptContext, ScriptManager, WasmRunner},
+ storage::StorageManager,
+ time::TimeContext,
+};
+use crossterm::event::MouseEventKind;
+use etcetera::AppStrategy;
+use prologger::{ColorMode, ProLogger, RotationConfig};
+use std::{
+ fs::File,
+ io::{BufRead, BufWriter},
+ path::PathBuf,
+ time::{Duration, Instant},
+};
+
+pub fn apply_patches(
+ manifest: &mut GameManifest,
+ files: &mut FileManager,
+ wasm_compiler: &WasmRunner,
+) -> std::io::Result<()> {
+ let path = etcetera::choose_app_strategy(manifest.app_strategy_args())
+ .unwrap()
+ .in_config_dir("patches");
+ let patchfile = File::open(path)?;
+
+ let mut patches = Vec::new();
+ for line in std::io::BufReader::new(patchfile).lines() {
+ let Ok(line) = line else {
+ continue;
+ };
+ patches.push(line);
+ }
+
+ for path in patches {
+ let path = PathBuf::from(path).into_boxed_path();
+ let patch = files.load_file(path.clone(), wasm_compiler);
+ let patch = patch.as_ref().as_ref().unwrap();
+ let patch = wasm_compiler.patch(patch)?;
+ manifest.apply_wasm_patch(path, patch);
+ }
+
+ Ok(())
+}
+
+pub fn init_logger(manifest: &GameManifest) {
+ let path = etcetera::choose_app_strategy(manifest.app_strategy_args())
+ .unwrap()
+ .in_state_dir("log")
+ .unwrap();
+ let path = path.to_string_lossy();
+ ProLogger::builder()
+ .with_rotating_file(&path, RotationConfig::new(100_000, 2))
+ .with_color(ColorMode::Never)
+ .init()
+ .unwrap();
+}
+
+pub fn run_game(mut files: FileManager, wasm_runner: WasmRunner, manifest: GameManifest) {
+ let start = Instant::now();
+
+ let stdout = Box::new(BufWriter::new(std::io::stdout().lock()));
+ let mut framerate_limit: Option<Duration> = None;
+ let mut buffer = Buffer::new(stdout).unwrap();
+ let mut input = InputManager::new();
+ let mut time = TimeContext::new();
+ let mut storage = StorageManager::load(manifest.app_strategy_args()).unwrap();
+ let mut scripts = ScriptManager::new(&manifest.scripts);
+ let mut context = ScriptContext {
+ framerate_limit: &mut framerate_limit,
+ manifest: &manifest,
+ buffer: &mut buffer,
+ storage: &mut storage,
+ input: &input,
+ time: &time,
+ operation_queue: Vec::new(),
+ };
+
+ let scene_start = Instant::now();
+ switch_scenes(
+ &mut scripts,
+ &mut context,
+ &mut files,
+ &wasm_runner,
+ manifest.first_scene,
+ );
+ log::info!(
+ "Switch scenes time: {} ms",
+ scene_start.elapsed().as_millis()
+ );
+
+ let server_addr = format!("127.0.0.1:{}", puffin_http::DEFAULT_PORT);
+ let _puffin_server = puffin_http::Server::new(&server_addr).unwrap();
+ puffin::set_scopes_on(true);
+
+ log::info!("Total startup time: {} ms", start.elapsed().as_millis());
+ buffer
+ .render_loop(|buffer, event| match event {
+ Event::Key(event) => {
+ if event.is_press() {
+ input.press_key(event.code);
+ }
+ }
+ Event::Mouse(event) => {
+ match event.kind {
+ MouseEventKind::Down(_) => input.click_mouse(),
+ MouseEventKind::ScrollDown => input.scroll(Direction::Down),
+ MouseEventKind::ScrollUp => input.scroll(Direction::Up),
+ MouseEventKind::ScrollLeft => input.scroll(Direction::Left),
+ MouseEventKind::ScrollRight => input.scroll(Direction::Right),
+ _ => (),
+ }
+
+ input.move_mouse(Position {
+ x: event.column,
+ y: event.row,
+ });
+ }
+ Event::FocusChanged(is_focused) => {
+ if is_focused {
+ input.focus();
+ } else {
+ input.unfocus();
+ }
+ }
+ Event::Render => {
+ input.read_gil_events();
+ buffer.cells.clear();
+ let mut context = ScriptContext {
+ framerate_limit: &mut framerate_limit,
+ manifest: &manifest,
+ buffer,
+ storage: &mut storage,
+ input: &input,
+ time: &time,
+ operation_queue: Vec::new(),
+ };
+ scripts.run_scripts(&mut context, &wasm_runner, &mut files);
+ files.reload_files(&wasm_runner);
+ input.reset_next_frame();
+ if let Some(framerate_limit) = framerate_limit
+ && let Ok(elapsed) = time.system_now().elapsed()
+ {
+ std::thread::sleep(framerate_limit - elapsed);
+ }
+ time.next_frame();
+ }
+ })
+ .unwrap();
+}
diff --git a/src/main.rs b/src/main.rs
index 75068fd..6f5a695 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,155 +1,26 @@
-use std::io::BufWriter;
+use std::path::{Path, PathBuf};
use bpaf::Bpaf;
-use mimalloc::MiMalloc;
-use prologger::{ColorMode, ProLogger, RotationConfig};
-use tortuise::{
- buffer::{Buffer, Event},
- file::{FileManager, TomlGameManifest, normalize_game_manifest},
- input::InputManager,
- scene::switch_scenes,
- script::{ScriptContext, ScriptManager, WasmRunner},
- storage::StorageManager,
- time::TimeContext,
-};
-
-// #[global_allocator]
-// static GLOBAL: MiMalloc = MiMalloc;
+use tortuise::{apply_patches, file::FileManager, init_logger, script::WasmRunner};
#[derive(Debug, Clone, Bpaf)]
#[bpaf(options)]
-enum Options {
- #[bpaf(command)]
- Run,
- #[bpaf(command)]
- Compile,
+struct Options {
+ module: PathBuf,
}
fn main() {
- let options = options().run();
-
- match options {
- Options::Compile => {
- let manifest = std::fs::read("tortuise.toml").unwrap();
- let manifest = toml::from_slice::<TomlGameManifest>(&manifest).unwrap();
- let manifest = normalize_game_manifest(manifest).unwrap();
-
- let manifest_decl = format!(
- r#"let manifest = GameManifest {{
- name: "{}".to_string(),
- first_scene: {},
- scenes: Box::from([{}]),
- scripts: Box::from([]),
- }}"#,
- manifest.name,
- manifest.first_scene,
- manifest
- .scenes
- .iter()
- .map(|scene| format!(
- r#"Scene {{
- name: \"{}\".to_string(),
- preload_scenes: Box::from([{}]),
- scripts: ResourceRequirements {{
- required: Box::from([{}]),
- preload: Box::from([{}]),
- }}
- }}"#,
- scene.name,
- scene
- .preload_scenes
- .iter()
- .map(|i| i.to_string())
- .collect::<Vec<_>>()
- .join(","),
- scene
- .scripts
- .required
- .iter()
- .map(|i| i.to_string())
- .collect::<Vec<_>>()
- .join(","),
- scene
- .scripts
- .preload
- .iter()
- .map(|i| i.to_string())
- .collect::<Vec<_>>()
- .join(","),
- ))
- .collect::<Vec<_>>()
- .join(",")
- );
- }
- Options::Run => {
- ProLogger::builder()
- .with_rotating_file(".log", RotationConfig::new(100_000, 2))
- .with_color(ColorMode::Never)
- .init()
- .unwrap();
+ let mut files = FileManager::default();
+ let wasm_runner = WasmRunner::new();
- let mut files = FileManager::default();
- let wasm_runner = WasmRunner::new();
- let manifest = files
- .load_file(Box::from("tortuise.toml".as_ref()), &wasm_runner)
- .unwrap()
- .unwrap_manifest()
- .clone();
-
- let stdout = Box::new(BufWriter::new(std::io::stdout().lock()));
- let mut buffer = Buffer::new(stdout).unwrap();
- let mut input = InputManager::new();
- let mut time = TimeContext::new();
- let mut storage = StorageManager::load(etcetera::AppStrategyArgs {
- top_level_domain: "com".into(),
- author: "botahamec".into(),
- app_name: manifest.name.to_string(),
- })
- .unwrap();
- let mut scripts = ScriptManager::new(&manifest.scripts);
- let mut context = ScriptContext {
- manifest: &manifest,
- buffer: &mut buffer,
- storage: &mut storage,
- input: &input,
- time: &time,
- operation_queue: Vec::new(),
- };
-
- switch_scenes(
- &mut scripts,
- &mut context,
- &mut files,
- &wasm_runner,
- manifest.first_scene,
- );
-
- let server_addr = format!("127.0.0.1:{}", puffin_http::DEFAULT_PORT);
- let _puffin_server = puffin_http::Server::new(&server_addr).unwrap();
- puffin::set_scopes_on(true);
- buffer
- .render_loop(|buffer, event| match event {
- Event::Key(event) => {
- if event.is_press() {
- input.press_key(event.code);
- }
- }
- Event::Render => {
- buffer.cells.clear();
- let mut context = ScriptContext {
- manifest: &manifest,
- buffer,
- storage: &mut storage,
- input: &input,
- time: &time,
- operation_queue: Vec::new(),
- };
- scripts.run_scripts(&mut context, &wasm_runner, &mut files);
- input.reset_next_frame();
- time.next_frame();
- }
- })
- .unwrap();
- }
- }
+ let options = options().run();
+ let path: Box<Path> = options.module.into();
+ let manifest_script = files.load_file(path.clone(), &wasm_runner);
+ let manifest_script = manifest_script.as_ref().as_ref().unwrap();
+ let mut manifest = wasm_runner.manifest(path.clone(), manifest_script).unwrap();
+ init_logger(&manifest);
+ if let Err(e) = apply_patches(&mut manifest, &mut files, &wasm_runner) {
+ log::error!("{}", e);
+ };
+ tortuise::run_game(files, wasm_runner, manifest);
}
diff --git a/src/scene.rs b/src/scene.rs
index 807628d..aa4a443 100644
--- a/src/scene.rs
+++ b/src/scene.rs
@@ -1,5 +1,3 @@
-use std::path::Path;
-
use rustc_hash::FxHashSet;
use serde::{Deserialize, Serialize};
@@ -11,14 +9,7 @@ use crate::{
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Scene {
pub name: Box<str>,
- pub preload_scenes: Box<[usize]>,
- pub scripts: ResourceRequirements,
-}
-
-#[derive(Debug, Clone, Default, Serialize, Deserialize)]
-pub struct ResourceRequirements {
- pub required: Box<[usize]>,
- pub preload: Box<[usize]>,
+ pub scripts: Vec<usize>,
}
pub fn scene_id_by_name(scenes: &[Scene], name: &str) -> Option<usize> {
@@ -31,34 +22,6 @@ pub fn scene_id_by_name(scenes: &[Scene], name: &str) -> Option<usize> {
None
}
-pub fn preload_scene(
- scenes: &[Scene],
- scripts: &mut ScriptManager,
- files: &mut FileManager,
- wasm_runner: &WasmRunner,
- id: usize,
-) {
- let scene = &scenes[id];
- for script in &scene.scripts.required {
- scripts.preload_script(*script, files, wasm_runner);
- }
-}
-
-pub fn is_scene_loaded(
- scenes: &[Scene],
- scripts: &ScriptManager,
- loaded_files: &[Box<Path>],
- id: usize,
-) -> bool {
- let scene = &scenes[id];
- scene
- .scripts
- .required
- .iter()
- .cloned()
- .all(|script_id| scripts.is_script_loaded(script_id, loaded_files))
-}
-
pub fn switch_scenes(
scripts: &mut ScriptManager,
context: &mut ScriptContext<'_>,
@@ -77,25 +40,8 @@ pub fn switch_scenes(
let mut needed_scripts = FxHashSet::default();
let scene = &context.manifest.scenes[new];
- for script_id in &scene.scripts.required {
+ for script_id in &scene.scripts {
needed_scripts.insert(*script_id);
scripts.activate_script(*script_id, context, wasm_runner, files);
}
- for script_id in &scene.scripts.preload {
- needed_scripts.insert(*script_id);
- scripts.preload_script(*script_id, files, wasm_runner);
- }
- for scene_to_preload in &scene.preload_scenes {
- let scene_to_preload = &context.manifest.scenes[*scene_to_preload];
- for script_id in &scene_to_preload.scripts.required {
- needed_scripts.insert(*script_id);
- scripts.preload_script(*script_id, files, wasm_runner);
- }
- }
-
- let active_scripts: FxHashSet<usize> = active_scripts.iter().cloned().collect();
- let unneeded_scripts = active_scripts.difference(&needed_scripts);
- for script_id in unneeded_scripts {
- scripts.unload_script(*script_id, files);
- }
}
diff --git a/src/script.rs b/src/script.rs
index a2cb76d..306ea49 100644
--- a/src/script.rs
+++ b/src/script.rs
@@ -1,12 +1,14 @@
-use std::{path::Path, sync::Arc};
+use std::{path::Path, sync::Arc, time::Duration};
use rustc_hash::FxHashMap;
+use serde::{Deserialize, Serialize};
use crate::{
buffer::{Buffer, Cell},
- file::{FileManager, GameManifest},
+ file::FileManager,
input::InputManager,
- scene::{preload_scene, switch_scenes},
+ manifest::{DeserializedGameManifest, GameManifest, GameManifestPatch},
+ scene::switch_scenes,
script::wasm::WasmModule,
storage::StorageManager,
time::TimeContext,
@@ -30,6 +32,7 @@ pub struct WasmRunner {
}
pub struct ScriptContext<'a> {
+ pub framerate_limit: &'a mut Option<Duration>,
pub manifest: &'a GameManifest,
pub buffer: &'a mut Buffer,
pub input: &'a InputManager,
@@ -53,10 +56,8 @@ pub struct FullScriptContext<'a> {
pub enum Operation {
AddCell(Cell),
- PreloadScene(usize),
+ SetFrameLimit(Option<Duration>),
SwitchScene(usize),
- PreloadScript(usize),
- UnloadScript(usize),
ActivateScript(usize),
DeactivateScript(usize),
UpdateState {
@@ -70,23 +71,24 @@ pub enum Operation {
UpdateStorage {
new_save: Box<[u8]>,
},
+ UpdateConfig {
+ new_save: Box<[u8]>,
+ },
Exit,
}
-type StaticScriptFunction = Option<fn(&mut ScriptContext<'_>, &mut Box<[u8]>)>;
+pub type StaticScriptFunction = Option<Arc<dyn Fn(FullScriptContext<'_>) + Send + Sync>>;
-#[derive(Debug, Clone)]
+#[derive(Clone, Serialize, Deserialize)]
+#[serde(tag = "type", rename_all = "kebab-case")]
pub enum ScriptDeclaration {
+ #[serde(skip)]
Static {
name: Box<str>,
start: StaticScriptFunction,
update: StaticScriptFunction,
end: StaticScriptFunction,
},
- Lua {
- name: Box<str>,
- path: Box<Path>,
- },
Wasm {
name: Box<str>,
path: Box<Path>,
@@ -100,10 +102,6 @@ impl<'a> ScriptContext<'a> {
}
}
-fn get_script_state(states: &mut FxHashMap<usize, Box<[u8]>>, script_id: usize) -> &mut Box<[u8]> {
- states.entry(script_id).or_insert(Box::from([]))
-}
-
impl Default for WasmRunner {
fn default() -> Self {
Self::new()
@@ -133,11 +131,54 @@ impl WasmRunner {
Ok(WasmModule { instance })
}
+ pub fn manifest(
+ &self,
+ path: Box<Path>,
+ module: &WasmModule,
+ ) -> Result<GameManifest, tinywasm::Error> {
+ let mut store = self.store.lock();
+ let offset = module
+ .instance
+ .func::<(), u32>(&store, "manifest_addr")?
+ .call(&mut store, ())?;
+ let length = module
+ .instance
+ .func::<(), u32>(&store, "manifest_len")?
+ .call(&mut store, ())?;
+ let manifest =
+ module
+ .instance
+ .memory("memory")?
+ .read_vec(&store, offset as usize, length as usize)?;
+ let manifest =
+ messagepack_serde::from_slice::<DeserializedGameManifest>(&manifest).unwrap();
+ Ok(GameManifest::from_wasm(path, manifest))
+ }
+
+ pub fn patch(&self, module: &WasmModule) -> Result<GameManifestPatch, tinywasm::Error> {
+ let mut store = self.store.lock();
+ let offset = module
+ .instance
+ .func::<(), u32>(&store, "manifest_addr")?
+ .call(&mut store, ())?;
+ let length = module
+ .instance
+ .func::<(), u32>(&store, "manifest_len")?
+ .call(&mut store, ())?;
+ let manifest =
+ module
+ .instance
+ .memory("memory")?
+ .read_vec(&store, offset as usize, length as usize)?;
+ let manifest = messagepack_serde::from_slice::<GameManifestPatch>(&manifest).unwrap();
+ Ok(manifest)
+ }
+
#[expect(clippy::too_many_arguments)]
pub fn run_function(
&self,
script_id: usize,
- module: &mut WasmModule,
+ module: &WasmModule,
scripts: &ScriptManager,
files: Box<[Box<Path>]>,
wasm_runner: &WasmRunner,
@@ -171,6 +212,15 @@ impl WasmRunner {
}
}
+impl ScriptDeclaration {
+ pub fn name(&self) -> &str {
+ match self {
+ Self::Static { name, .. } => name,
+ Self::Wasm { name, .. } => name,
+ }
+ }
+}
+
impl<'a> ScriptManager<'a> {
pub fn new(scripts: &'a [ScriptDeclaration]) -> Self {
Self {
@@ -189,11 +239,6 @@ impl<'a> ScriptManager<'a> {
return Some(i);
}
}
- ScriptDeclaration::Lua { name, .. } => {
- if &**name == needle {
- return Some(i);
- }
- }
ScriptDeclaration::Wasm { name, .. } => {
if &**name == needle {
return Some(i);
@@ -205,38 +250,6 @@ impl<'a> ScriptManager<'a> {
None
}
- pub fn preload_script(
- &self,
- script_id: usize,
- files: &mut FileManager,
- wasm_runner: &WasmRunner,
- ) {
- let script = &self.scripts[script_id];
- match script {
- ScriptDeclaration::Static { .. } => (),
- ScriptDeclaration::Lua { path, .. } => files.start_loading_file(path, wasm_runner),
- ScriptDeclaration::Wasm { path, .. } => files.start_loading_file(path, wasm_runner),
- }
- }
-
- pub fn is_script_loaded(&self, script_id: usize, loaded_files: &[Box<Path>]) -> bool {
- let script = &self.scripts[script_id];
- match script {
- ScriptDeclaration::Static { .. } => true,
- ScriptDeclaration::Lua { path, .. } => loaded_files.contains(path),
- ScriptDeclaration::Wasm { path, .. } => loaded_files.contains(path),
- }
- }
-
- pub fn unload_script(&self, script_id: usize, files: &mut FileManager) {
- let script = &self.scripts[script_id];
- match script {
- ScriptDeclaration::Static { .. } => (),
- ScriptDeclaration::Lua { path, .. } => files.unload_file(path),
- ScriptDeclaration::Wasm { path, .. } => files.unload_file(path),
- }
- }
-
pub fn handle_queue(
&mut self,
context: &mut ScriptContext<'_>,
@@ -246,12 +259,11 @@ impl<'a> ScriptManager<'a> {
for operation in &context.operation_queue {
match operation {
Operation::AddCell(cell) => context.buffer.cells.push(*cell),
- Operation::PreloadScene(id) => {
- preload_scene(&context.manifest.scenes, self, files, wasm_runner, *id)
- }
+ Operation::SetFrameLimit(duration) => *context.framerate_limit = *duration,
Operation::SwitchScene(id) => switch_scenes(
self,
&mut ScriptContext {
+ framerate_limit: context.framerate_limit,
manifest: context.manifest,
buffer: context.buffer,
input: context.input,
@@ -263,8 +275,6 @@ impl<'a> ScriptManager<'a> {
wasm_runner,
*id,
),
- Operation::PreloadScript(id) => self.preload_script(*id, files, wasm_runner),
- Operation::UnloadScript(id) => self.unload_script(*id, files),
Operation::ActivateScript(id) => self.activate_script(
*id,
&mut ScriptContext {
@@ -274,6 +284,7 @@ impl<'a> ScriptManager<'a> {
time: context.time,
storage: context.storage,
operation_queue: Vec::new(),
+ framerate_limit: context.framerate_limit,
},
wasm_runner,
files,
@@ -287,6 +298,7 @@ impl<'a> ScriptManager<'a> {
time: context.time,
storage: context.storage,
operation_queue: Vec::new(),
+ framerate_limit: context.framerate_limit,
},
wasm_runner,
files,
@@ -301,7 +313,10 @@ impl<'a> ScriptManager<'a> {
self.entities.insert(name.clone(), new_state.clone());
}
Operation::UpdateStorage { new_save } => {
- context.storage.background_save(new_save.clone());
+ context.storage.background_save_data(new_save.clone());
+ }
+ Operation::UpdateConfig { new_save } => {
+ context.storage.background_save_config(new_save.clone());
}
Operation::Exit => context.exit(),
}
@@ -321,20 +336,9 @@ impl<'a> ScriptManager<'a> {
};
match script_declaration {
ScriptDeclaration::Static { start, .. } => {
- start.map(|s| {
- s(
- context,
- get_script_state(&mut self.script_states, script_id),
- )
- });
- }
- ScriptDeclaration::Lua { path, .. } => {
- files.load_file(path.clone(), wasm_runner).unwrap();
- let loaded_files = files.loaded_files();
- let module = files.get_cached_file_mut(path).unwrap().unwrap_lua_module();
- if let Err(e) = module.run_function(
- "start",
- FullScriptContext {
+ start.as_ref().inspect(|s| {
+ let loaded_files = files.loaded_files();
+ s(FullScriptContext {
script_id,
manifest: context.manifest,
buffer: context.buffer,
@@ -345,18 +349,13 @@ impl<'a> ScriptManager<'a> {
storage: context.storage,
wasm_runner,
operation_queue: &mut context.operation_queue,
- },
- ) {
- log::error!("{e}");
- }
+ })
+ });
}
ScriptDeclaration::Wasm { path, .. } => {
- files.load_file(path.clone(), wasm_runner).unwrap();
+ let module = files.load_file(path.clone(), wasm_runner);
+ let module = module.as_ref().as_ref().unwrap();
let loaded_files = files.loaded_files();
- let module = files
- .get_cached_file_mut(path)
- .unwrap()
- .unwrap_wasm_module_mut();
if let Err(e) = wasm_runner.run_function(
script_id,
module,
@@ -364,7 +363,7 @@ impl<'a> ScriptManager<'a> {
loaded_files,
wasm_runner,
context,
- "start",
+ "start_script",
) {
log::error!("{e}");
}
@@ -393,20 +392,9 @@ impl<'a> ScriptManager<'a> {
self.script_states.remove(&script_id);
match script {
ScriptDeclaration::Static { end, .. } => {
- end.map(|s| {
- s(
- context,
- get_script_state(&mut self.script_states, script_id),
- );
- });
- }
- ScriptDeclaration::Lua { path, .. } => {
- files.load_file(path.clone(), wasm_runner).unwrap();
- let loaded_files = files.loaded_files();
- let module = files.get_cached_file_mut(path).unwrap().unwrap_lua_module();
- if let Err(e) = module.run_function(
- "end",
- FullScriptContext {
+ end.as_ref().inspect(|s| {
+ let loaded_files = files.loaded_files();
+ s(FullScriptContext {
script_id,
manifest: context.manifest,
buffer: context.buffer,
@@ -417,18 +405,13 @@ impl<'a> ScriptManager<'a> {
storage: context.storage,
wasm_runner,
operation_queue: &mut context.operation_queue,
- },
- ) {
- log::error!("{e}");
- }
+ });
+ });
}
ScriptDeclaration::Wasm { path, .. } => {
- files.load_file(path.clone(), wasm_runner).unwrap();
+ let module = files.load_file(path.clone(), wasm_runner);
+ let module = module.as_ref().as_ref().unwrap();
let loaded_files = files.loaded_files();
- let module = files
- .get_cached_file_mut(path)
- .unwrap()
- .unwrap_wasm_module_mut();
if let Err(e) = wasm_runner.run_function(
script_id,
module,
@@ -436,7 +419,7 @@ impl<'a> ScriptManager<'a> {
loaded_files,
wasm_runner,
context,
- "end",
+ "end_script",
) {
log::error!("{e}");
}
@@ -459,21 +442,11 @@ impl<'a> ScriptManager<'a> {
unreachable!("The active script does not exist");
};
match script {
- ScriptDeclaration::Static { update, .. } => {
+ ScriptDeclaration::Static { name, update, .. } => {
if let Some(s) = update {
- s(
- context,
- get_script_state(&mut self.script_states, *script_id),
- )
- }
- }
- ScriptDeclaration::Lua { path, .. } => {
- files.load_file(path.clone(), wasm_runner).unwrap();
- let loaded_files = files.loaded_files();
- let module = files.get_cached_file_mut(path).unwrap().unwrap_lua_module();
- if let Err(e) = module.run_function(
- "update",
- FullScriptContext {
+ puffin::profile_scope!("run script", name);
+ let loaded_files = files.loaded_files();
+ s(FullScriptContext {
script_id: *script_id,
manifest: context.manifest,
buffer: context.buffer,
@@ -484,18 +457,13 @@ impl<'a> ScriptManager<'a> {
storage: context.storage,
wasm_runner,
operation_queue: &mut context.operation_queue,
- },
- ) {
- log::error!("{e}");
+ });
}
}
ScriptDeclaration::Wasm { path, .. } => {
- files.load_file(path.clone(), wasm_runner).unwrap();
+ let module = files.load_file(path.clone(), wasm_runner);
+ let module = module.as_ref().as_ref().unwrap();
let loaded_files = files.loaded_files();
- let module = files
- .get_cached_file_mut(path)
- .unwrap()
- .unwrap_wasm_module_mut();
if let Err(e) = wasm_runner.run_function(
*script_id,
module,
@@ -503,7 +471,7 @@ impl<'a> ScriptManager<'a> {
loaded_files,
wasm_runner,
context,
- "update",
+ "update_script",
) {
log::error!("Error while running scripts: {e}");
}
diff --git a/src/script/lua.rs b/src/script/lua.rs
index a5c7f6a..92d455d 100644
--- a/src/script/lua.rs
+++ b/src/script/lua.rs
@@ -50,12 +50,7 @@ impl LuaModule {
warn,
error,
exit,
- is_scene_loaded,
- preload_scene,
switch_scene,
- is_script_loaded,
- preload_script,
- unload_script,
activate_script,
deactivate_script,
state,
@@ -246,29 +241,6 @@ fn exit(lua: &Lua, _: ()) -> mlua::Result<()> {
Ok(())
}
-fn is_scene_loaded(lua: &Lua, scene: String) -> mlua::Result<()> {
- puffin::profile_function!();
- let context = unsafe { context(lua) };
- let scene = scene_id_by_name(&context.manifest.scenes, &scene)
- .ok_or(mlua::Error::runtime("No scene with the given name found"))?;
- crate::scene::is_scene_loaded(
- &context.manifest.scenes,
- context.scripts,
- &context.files,
- scene,
- );
- Ok(())
-}
-
-fn preload_scene(lua: &Lua, scene: String) -> mlua::Result<()> {
- puffin::profile_function!();
- let context = unsafe { context(lua) };
- let scene = scene_id_by_name(&context.manifest.scenes, &scene)
- .ok_or(mlua::Error::runtime("No scene with the given name found"))?;
- context.operation_queue.push(Operation::PreloadScene(scene));
- Ok(())
-}
-
fn switch_scene(lua: &Lua, scene: String) -> mlua::Result<()> {
puffin::profile_function!();
let context = unsafe { context(lua) };
@@ -278,43 +250,6 @@ fn switch_scene(lua: &Lua, scene: String) -> mlua::Result<()> {
Ok(())
}
-fn is_script_loaded(lua: &Lua, script: String) -> mlua::Result<()> {
- puffin::profile_function!();
- let context = unsafe { context(lua) };
- let script = context
- .scripts
- .script_id_by_name(&script)
- .ok_or(mlua::Error::runtime("No script with the given name found"))?;
- context.scripts.is_script_loaded(script, &context.files);
- Ok(())
-}
-
-fn preload_script(lua: &Lua, script: String) -> mlua::Result<()> {
- puffin::profile_function!();
- let context = unsafe { context(lua) };
- let script = context
- .scripts
- .script_id_by_name(&script)
- .ok_or(mlua::Error::runtime("No script with the given name found"))?;
- context
- .operation_queue
- .push(Operation::PreloadScript(script));
- Ok(())
-}
-
-fn unload_script(lua: &Lua, script: String) -> mlua::Result<()> {
- puffin::profile_function!();
- let context = unsafe { context(lua) };
- let script = context
- .scripts
- .script_id_by_name(&script)
- .ok_or(mlua::Error::runtime("No script with the given name found"))?;
- context
- .operation_queue
- .push(Operation::UnloadScript(script));
- Ok(())
-}
-
fn activate_script(lua: &Lua, script: String) -> mlua::Result<()> {
puffin::profile_function!();
let context = unsafe { context(lua) };
@@ -436,7 +371,7 @@ fn storage(lua: &Lua, default: MyLuaValue) -> mlua::Result<MyLuaValue> {
let context = unsafe { context(lua) };
Ok(context
.storage
- .get()
+ .data()
.map(|state| postcard::from_bytes(state).unwrap())
.unwrap_or(default))
}
diff --git a/src/script/wasm.rs b/src/script/wasm.rs
index 9b9cd91..aaf25cf 100644
--- a/src/script/wasm.rs
+++ b/src/script/wasm.rs
@@ -1,4 +1,4 @@
-use std::time::UNIX_EPOCH;
+use std::time::{Duration, UNIX_EPOCH};
use crossterm::{
event::KeyCode,
@@ -35,6 +35,60 @@ fn context_ref<'b>(address: u64) -> &'b FullScriptContext<'b> {
}
}
+fn number_to_button(number: u32) -> gilrs::Button {
+ match number {
+ 0 => gilrs::Button::Unknown,
+ 1 => gilrs::Button::South,
+ 2 => gilrs::Button::East,
+ 3 => gilrs::Button::C,
+ 4 => gilrs::Button::North,
+ 5 => gilrs::Button::West,
+ 6 => gilrs::Button::Z,
+ 7 => gilrs::Button::LeftTrigger,
+ 8 => gilrs::Button::RightTrigger,
+ 9 => gilrs::Button::LeftTrigger2,
+ 10 => gilrs::Button::RightTrigger2,
+ 11 => gilrs::Button::Select,
+ 12 => gilrs::Button::Start,
+ 13 => gilrs::Button::Mode,
+ 14 => gilrs::Button::LeftThumb,
+ 15 => gilrs::Button::RightThumb,
+ 16 => gilrs::Button::DPadUp,
+ 17 => gilrs::Button::DPadDown,
+ 18 => gilrs::Button::DPadLeft,
+ 19 => gilrs::Button::DPadRight,
+ _ => gilrs::Button::Unknown,
+ }
+}
+
+fn number_to_axis(number: u32) -> gilrs::Axis {
+ match number {
+ 0 => gilrs::Axis::Unknown,
+ 1 => gilrs::Axis::LeftStickX,
+ 2 => gilrs::Axis::LeftStickY,
+ 3 => gilrs::Axis::LeftZ,
+ 4 => gilrs::Axis::RightStickX,
+ 5 => gilrs::Axis::RightStickY,
+ 6 => gilrs::Axis::RightZ,
+ 7 => gilrs::Axis::DPadX,
+ 8 => gilrs::Axis::DPadY,
+ _ => gilrs::Axis::Unknown,
+ }
+}
+
+fn set_framerate_limit(
+ _caller: FuncContext<'_>,
+ (context, limit): (u64, u32),
+) -> tinywasm::Result<()> {
+ puffin::profile_function!();
+ let context = context_mut(context);
+ let duration = (limit != 0).then(|| Duration::from_nanos(1_000_000_000 / limit as u64));
+ context
+ .operation_queue
+ .push(Operation::SetFrameLimit(duration));
+ Ok(())
+}
+
fn add_cell(
_caller: FuncContext<'_>,
(
@@ -219,6 +273,174 @@ fn is_key_pressed_this_frame(
)
}
+fn is_mouse_clicked_this_frame(
+ _caller: FuncContext<'_>,
+ (context,): (u64,),
+) -> tinywasm::Result<u32> {
+ puffin::profile_function!();
+ if context_ref(context).input.is_mouse_clicked_this_frame() {
+ Ok(1)
+ } else {
+ Ok(0)
+ }
+}
+
+fn mouse_location(_caller: FuncContext<'_>, (context,): (u64,)) -> tinywasm::Result<u32> {
+ puffin::profile_function!();
+ let location = context_ref(context).input.mouse_location();
+
+ if let Some(location) = location {
+ Ok(((location.y as u32) << 16) | location.x as u32)
+ } else {
+ Ok(u32::MAX)
+ }
+}
+
+fn scroll_direction_this_frame(
+ _caller: FuncContext<'_>,
+ (context,): (u64,),
+) -> tinywasm::Result<u32> {
+ puffin::profile_function!();
+ let direction = context_ref(context).input.scroll_direction_this_frame();
+ Ok(direction.map(|d| d as u32).unwrap_or_default())
+}
+
+fn is_focused(_caller: FuncContext<'_>, (context,): (u64,)) -> tinywasm::Result<u32> {
+ puffin::profile_function!();
+ Ok(if context_ref(context).input.is_focused() {
+ 1
+ } else {
+ 0
+ })
+}
+
+fn is_gamepad_connected_this_frame(
+ _caller: FuncContext<'_>,
+ context: u64,
+) -> tinywasm::Result<u32> {
+ puffin::profile_function!();
+ Ok(context_ref(context)
+ .input
+ .is_gamepad_connected_this_frame()
+ .unwrap_or_default() as u32)
+}
+
+fn is_button_pressed_this_frame(
+ _caller: FuncContext<'_>,
+ (context, gamepad_id, button): (u64, u32, u32),
+) -> tinywasm::Result<u32> {
+ puffin::profile_function!();
+ Ok(
+ if context_ref(context)
+ .input
+ .is_button_pressed_this_frame(gamepad_id as usize, number_to_button(button))
+ {
+ 1
+ } else {
+ 0
+ },
+ )
+}
+
+fn is_button_released_this_frame(
+ _caller: FuncContext<'_>,
+ (context, gamepad_id, button): (u64, u32, u32),
+) -> tinywasm::Result<u32> {
+ puffin::profile_function!();
+ Ok(
+ if context_ref(context)
+ .input
+ .is_button_released_this_frame(gamepad_id as usize, number_to_button(button))
+ {
+ 1
+ } else {
+ 0
+ },
+ )
+}
+
+fn is_button_down(
+ _caller: FuncContext<'_>,
+ (context, gamepad_id, button): (u64, u32, u32),
+) -> tinywasm::Result<u32> {
+ puffin::profile_function!();
+ Ok(
+ if context_ref(context)
+ .input
+ .is_button_down(gamepad_id as usize, number_to_button(button))
+ {
+ 1
+ } else {
+ 0
+ },
+ )
+}
+
+fn is_gamepad_connected(
+ _caller: FuncContext<'_>,
+ (context, gamepad_id): (u64, u32),
+) -> tinywasm::Result<u32> {
+ puffin::profile_function!();
+ Ok(
+ if context_ref(context)
+ .input
+ .is_gamepad_connected(gamepad_id as usize)
+ {
+ 1
+ } else {
+ 0
+ },
+ )
+}
+
+fn axis_value(
+ _caller: FuncContext<'_>,
+ (context, gamepad_id, axis): (u64, u32, u32),
+) -> tinywasm::Result<f32> {
+ puffin::profile_function!();
+ Ok(context_ref(context)
+ .input
+ .axis_value(gamepad_id as usize, number_to_axis(axis))
+ .unwrap_or_default())
+}
+
+fn gamepad_power_info(
+ _caller: FuncContext<'_>,
+ (context, gamepad_id): (u64, u32),
+) -> tinywasm::Result<u32> {
+ puffin::profile_function!();
+ Ok(
+ match context_ref(context)
+ .input
+ .gamepad_power_info(gamepad_id as usize)
+ {
+ None | Some(gilrs::PowerInfo::Unknown) => 0,
+ Some(gilrs::PowerInfo::Wired) => 1 << 16,
+ Some(gilrs::PowerInfo::Discharging(power)) => 2 << 16 | power as u32,
+ Some(gilrs::PowerInfo::Charging(power)) => 3 << 16 | power as u32,
+ Some(gilrs::PowerInfo::Charged) => 4 << 16,
+ },
+ )
+}
+
+fn was_focused_this_frame(_caller: FuncContext<'_>, (context,): (u64,)) -> tinywasm::Result<u32> {
+ puffin::profile_function!();
+ Ok(if context_ref(context).input.was_focused_this_frame() {
+ 1
+ } else {
+ 0
+ })
+}
+
+fn was_unfocused_this_frame(_caller: FuncContext<'_>, (context,): (u64,)) -> tinywasm::Result<u32> {
+ puffin::profile_function!();
+ Ok(if context_ref(context).input.was_unfocused_this_frame() {
+ 1
+ } else {
+ 0
+ })
+}
+
fn log(
caller: FuncContext<'_>,
(severity, offset, length): (u32, u32, u32),
@@ -261,30 +483,31 @@ fn scene_id_by_name(
)
}
-fn preload_scene(_caller: FuncContext<'_>, (context, id): (u64, u32)) -> tinywasm::Result<()> {
+fn switch_scene(_caller: FuncContext<'_>, (context, id): (u64, u32)) -> tinywasm::Result<()> {
puffin::profile_function!();
context_mut(context)
.operation_queue
- .push(Operation::PreloadScene(id as usize));
+ .push(Operation::SwitchScene(id as usize));
Ok(())
}
-fn is_scene_loaded(_caller: FuncContext<'_>, (context, id): (u64, u32)) -> tinywasm::Result<u32> {
+fn script_name_size(_caller: FuncContext<'_>, context: u64) -> tinywasm::Result<u32> {
puffin::profile_function!();
let context = context_ref(context);
- Ok(crate::scene::is_scene_loaded(
- &context.manifest.scenes,
- context.scripts,
- &context.files,
- id as usize,
- ) as u32)
+ Ok(context.scripts.scripts[context.script_id].name().len() as u32)
}
-fn switch_scene(_caller: FuncContext<'_>, (context, id): (u64, u32)) -> tinywasm::Result<()> {
+fn load_script_name(
+ mut caller: FuncContext<'_>,
+ (context, offset): (u64, u32),
+) -> tinywasm::Result<()> {
puffin::profile_function!();
- context_mut(context)
- .operation_queue
- .push(Operation::SwitchScene(id as usize));
+ let memory = caller.memory("memory").unwrap();
+ let context = context_ref(context);
+ let name = context.scripts.scripts[context.script_id].name();
+ memory
+ .write(&mut caller, offset as usize, name.as_bytes())
+ .unwrap();
Ok(())
}
@@ -302,30 +525,6 @@ fn script_id_by_name(
.unwrap_or(u32::MAX))
}
-fn preload_script(_caller: FuncContext<'_>, (context, id): (u64, u32)) -> tinywasm::Result<()> {
- puffin::profile_function!();
- context_mut(context)
- .operation_queue
- .push(Operation::PreloadScript(id as usize));
- Ok(())
-}
-
-fn is_script_loaded(_caller: FuncContext<'_>, (context, id): (u64, u32)) -> tinywasm::Result<u32> {
- puffin::profile_function!();
- let context = context_ref(context);
- Ok(context
- .scripts
- .is_script_loaded(id as usize, &context.files) as u32)
-}
-
-fn unload_script(_caller: FuncContext<'_>, (context, id): (u64, u32)) -> tinywasm::Result<()> {
- puffin::profile_function!();
- context_mut(context)
- .operation_queue
- .push(Operation::UnloadScript(id as usize));
- Ok(())
-}
-
fn activate_script(_caller: FuncContext<'_>, (context, id): (u64, u32)) -> tinywasm::Result<()> {
puffin::profile_function!();
context_mut(context)
@@ -444,7 +643,7 @@ fn storage_size(_caller: FuncContext<'_>, context: u64) -> tinywasm::Result<u32>
puffin::profile_function!();
Ok(context_ref(context)
.storage
- .get()
+ .data()
.map(|data| data.len())
.unwrap_or_default() as u32)
}
@@ -456,7 +655,7 @@ fn load_storage(
puffin::profile_function!();
let memory = caller.memory("memory").unwrap();
let context = context_ref(context);
- let state = context.storage.get().unwrap_or(&[]);
+ let state = context.storage.data().unwrap_or(&[]);
memory.write(&mut caller, offset as usize, state).unwrap();
Ok(())
}
@@ -480,6 +679,79 @@ fn save_storage(
Ok(())
}
+fn is_saving_storage(_caller: FuncContext<'_>, context: u64) -> tinywasm::Result<u32> {
+ puffin::profile_function!();
+ Ok(if context_ref(context).storage.is_saving_data() {
+ 1
+ } else {
+ 0
+ })
+}
+
+fn saving_storage_failed(_caller: FuncContext<'_>, context: u64) -> tinywasm::Result<u32> {
+ puffin::profile_function!();
+ Ok(if context_ref(context).storage.saving_data_failed() {
+ 1
+ } else {
+ 0
+ })
+}
+
+fn config_size(_caller: FuncContext<'_>, context: u64) -> tinywasm::Result<u32> {
+ puffin::profile_function!();
+ Ok(context_ref(context)
+ .storage
+ .config()
+ .map(|data| data.len())
+ .unwrap_or_default() as u32)
+}
+
+fn load_config(mut caller: FuncContext<'_>, (context, offset): (u64, u32)) -> tinywasm::Result<()> {
+ puffin::profile_function!();
+ let memory = caller.memory("memory").unwrap();
+ let context = context_ref(context);
+ let state = context.storage.config().unwrap_or(&[]);
+ memory.write(&mut caller, offset as usize, state).unwrap();
+ Ok(())
+}
+
+fn save_config(
+ mut caller: FuncContext<'_>,
+ (context, offset, length): (u64, u32, u32),
+) -> tinywasm::Result<()> {
+ puffin::profile_function!();
+ let offset = offset as usize;
+ let length = length as usize;
+ let memory = caller.memory("memory").unwrap();
+ let new_save = memory
+ .read_vec(caller.store_mut(), offset, length)
+ .unwrap()
+ .into_boxed_slice();
+ let context = context_mut(context);
+ context
+ .operation_queue
+ .push(Operation::UpdateConfig { new_save });
+ Ok(())
+}
+
+fn is_saving_config(_caller: FuncContext<'_>, context: u64) -> tinywasm::Result<u32> {
+ puffin::profile_function!();
+ Ok(if context_ref(context).storage.is_saving_config() {
+ 1
+ } else {
+ 0
+ })
+}
+
+fn saving_config_failed(_caller: FuncContext<'_>, context: u64) -> tinywasm::Result<u32> {
+ puffin::profile_function!();
+ Ok(if context_ref(context).storage.saving_config_failed() {
+ 1
+ } else {
+ 0
+ })
+}
+
fn system_now(_caller: FuncContext<'_>, context: u64) -> tinywasm::Result<u64> {
puffin::profile_function!();
Ok(context_ref(context)
@@ -514,18 +786,29 @@ pub fn linker(store: &mut Store) -> Result<Imports, tinywasm::Error> {
[
add_cell,
window_size,
+ set_framerate_limit,
is_key_pressed_this_frame,
+ is_mouse_clicked_this_frame,
+ mouse_location,
+ scroll_direction_this_frame,
+ is_focused,
+ is_button_pressed_this_frame,
+ is_button_released_this_frame,
+ is_gamepad_connected_this_frame,
+ is_gamepad_connected,
+ is_button_down,
+ axis_value,
+ gamepad_power_info,
+ was_focused_this_frame,
+ was_unfocused_this_frame,
log,
exit,
random_u64,
+ script_name_size,
+ load_script_name,
scene_id_by_name,
- preload_scene,
switch_scene,
- is_scene_loaded,
script_id_by_name,
- preload_script,
- is_script_loaded,
- unload_script,
activate_script,
deactivate_script,
state_size,
@@ -537,6 +820,13 @@ pub fn linker(store: &mut Store) -> Result<Imports, tinywasm::Error> {
storage_size,
load_storage,
save_storage,
+ is_saving_storage,
+ saving_storage_failed,
+ config_size,
+ load_config,
+ save_config,
+ is_saving_config,
+ saving_config_failed,
system_now,
monotonic_now,
delta_time
diff --git a/src/storage.rs b/src/storage.rs
index 90abf05..c099481 100644
--- a/src/storage.rs
+++ b/src/storage.rs
@@ -1,41 +1,123 @@
+use std::{
+ path::PathBuf,
+ sync::{
+ Arc,
+ atomic::{AtomicBool, Ordering},
+ },
+};
+
use etcetera::{AppStrategy, AppStrategyArgs, app_strategy::Xdg};
use smol::unblock;
pub struct StorageManager {
xdg: Xdg,
+ config: Option<Box<[u8]>>,
data: Option<Box<[u8]>>,
+ saving_config: Arc<AtomicBool>,
+ saving_data: Arc<AtomicBool>,
+ saving_config_failed: Arc<AtomicBool>,
+ saving_data_failed: Arc<AtomicBool>,
}
impl StorageManager {
pub fn load(app: AppStrategyArgs) -> std::io::Result<Self> {
let xdg = etcetera::choose_app_strategy(app).map_err(std::io::Error::other)?;
+ let config = std::fs::read(xdg.in_config_dir("config"))
+ .or_else(|_| std::fs::read(xdg.in_config_dir("config.tmp")))
+ .map(|config| config.into_boxed_slice())
+ .ok();
let data = std::fs::read(xdg.in_data_dir("save"))
.or_else(|_| std::fs::read(xdg.in_data_dir("save.tmp")))
.map(|data| data.into_boxed_slice())
.ok();
- Ok(Self { data, xdg })
+ Ok(Self {
+ xdg,
+ config,
+ data,
+ saving_config: Arc::new(AtomicBool::new(false)),
+ saving_data: Arc::new(AtomicBool::new(false)),
+ saving_config_failed: Arc::new(AtomicBool::new(false)),
+ saving_data_failed: Arc::new(AtomicBool::new(false)),
+ })
+ }
+
+ pub fn config(&self) -> Option<&[u8]> {
+ self.config.as_deref()
}
- pub fn get(&self) -> Option<&[u8]> {
+ pub fn data(&self) -> Option<&[u8]> {
self.data.as_deref()
}
- pub fn background_save(&mut self, data: Box<[u8]>) {
- let tmp_file = self.xdg.in_data_dir("save.tmp");
- let save_file = self.xdg.in_data_dir("save");
+ pub fn is_saving_config(&self) -> bool {
+ self.saving_config.load(Ordering::Acquire)
+ }
+
+ pub fn is_saving_data(&self) -> bool {
+ self.saving_data.load(Ordering::Acquire)
+ }
+
+ pub fn saving_config_failed(&self) -> bool {
+ self.saving_config_failed.load(Ordering::Acquire)
+ }
+
+ pub fn saving_data_failed(&self) -> bool {
+ self.saving_data_failed.load(Ordering::Acquire)
+ }
+
+ fn background_save(
+ &mut self,
+ file: PathBuf,
+ data: Box<[u8]>,
+ state: Arc<AtomicBool>,
+ failed: Arc<AtomicBool>,
+ ) {
+ let tmp_file = file.with_added_extension("tmp");
self.data = Some(data.clone());
unblock(move || {
- std::fs::write(&tmp_file, &data)?;
+ if let Err(e) = {
+ std::fs::write(&tmp_file, &data)?;
- let saved_data = std::fs::read(&tmp_file)?;
- if *saved_data != *data {
- return Err(std::io::Error::other("Failed to verify file integrity"));
+ let saved_data = std::fs::read(&tmp_file)?;
+ if *saved_data != *data {
+ return Err(std::io::Error::other("Failed to verify file integrity"));
+ }
+
+ std::fs::rename(tmp_file, &file)?;
+ state.store(false, Ordering::Release);
+ Ok(())
+ } {
+ log::error!("{e}");
+ failed.store(true, Ordering::Release);
+ Err(e)
+ } else {
+ Ok(())
}
+ })
+ .detach();
+ }
+
+ pub fn background_save_config(&mut self, data: Box<[u8]>) {
+ self.saving_config_failed.store(false, Ordering::Release);
+ self.saving_config.store(true, Ordering::Release);
+ self.background_save(
+ self.xdg.in_data_dir("config"),
+ data,
+ self.saving_config.clone(),
+ self.saving_config_failed.clone(),
+ )
+ }
- std::fs::rename(tmp_file, save_file)?;
- Ok(())
- });
+ pub fn background_save_data(&mut self, data: Box<[u8]>) {
+ self.saving_data_failed.store(false, Ordering::Release);
+ self.saving_data.store(true, Ordering::Release);
+ self.background_save(
+ self.xdg.in_data_dir("save"),
+ data,
+ self.saving_data.clone(),
+ self.saving_config_failed.clone(),
+ )
}
}