From 0c128e472b1b49e32c313874eaeccc8c5f1b1202 Mon Sep 17 00:00:00 2001 From: Mica White Date: Mon, 26 Oct 2026 10:51:49 -0400 Subject: Finish engine --- Cargo.lock | 526 +++++++++++++++++++++++++++++++++-------------------- Cargo.toml | 7 +- manifest.toml | 6 - src/buffer.rs | 63 ++++--- src/file.rs | 255 ++++++++++---------------- src/input.rs | 220 +++++++++++++++++++++- src/lib.rs | 156 ++++++++++++++++ src/main.rs | 161 ++-------------- src/scene.rs | 58 +----- src/script.rs | 230 ++++++++++------------- src/script/lua.rs | 67 +------ src/script/wasm.rs | 380 +++++++++++++++++++++++++++++++++----- src/storage.rs | 106 +++++++++-- 13 files changed, 1379 insertions(+), 856 deletions(-) delete mode 100644 manifest.toml 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", @@ -221,26 +221,6 @@ version = "3.20.3" 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" @@ -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", @@ -541,6 +532,40 @@ dependencies = [ "r-efi", ] +[[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" @@ -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,14 +636,34 @@ 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" @@ -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", @@ -771,6 +828,18 @@ dependencies = [ "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" @@ -780,6 +849,26 @@ dependencies = [ "autocfg", ] +[[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" @@ -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,65 +1312,26 @@ dependencies = [ [[package]] name = "tinywasm-types" -version = "0.10.0" +version = "0.11.0-pre.0" dependencies = [ "log", "postcard", "serde", ] -[[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" @@ -1311,6 +1362,22 @@ 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" @@ -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", ] @@ -1403,6 +1480,27 @@ 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" @@ -1416,6 +1514,17 @@ dependencies = [ "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" @@ -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]] @@ -1444,6 +1553,16 @@ 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" @@ -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 for Event { fn try_from(value: crossterm::event::Event) -> Result { 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>; +type FileTask = Task>; pub struct FileManager { + watcher: Notifier, + updated_files: Receiver>, active_tasks: FxHashMap, FileTask>, - cache: FxHashMap, FileData>, -} - -#[derive(Debug, Clone)] -pub struct GameManifest { - pub name: Box, - pub first_scene: usize, - pub scenes: Box<[Scene]>, - pub scripts: Box<[ScriptDeclaration]>, -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct TomlGameManifest { - name: Box, - first_scene: Box, - #[serde(default)] - scene: Box<[TomlScene]>, - #[serde(default)] - script: Box<[TomlScript]>, -} - -#[derive(Debug, Serialize, Deserialize)] -struct TomlScene { - name: Box, - #[serde(default)] - preload_scenes: Box<[Box]>, - #[serde(default)] - scripts: Box<[Box]>, - #[serde(default)] - preload_scripts: Box<[Box]>, + cache: FxHashMap, Arc>>, } -#[derive(Debug, Serialize, Deserialize)] -#[serde(tag = "type", rename_all = "kebab-case")] -enum TomlScript { - Wasm { name: Box, path: Box }, - Lua { name: Box, path: Box }, +struct Notifier { + paths: Arc>>>, + cancel_token: Arc, } -pub enum FileData { - GameManifest(GameManifest), - WasmModule(Box), - LuaModule(Box), -} - -pub fn normalize_game_manifest(manifest: TomlGameManifest) -> std::io::Result { - let mut scripts = Vec::with_capacity(manifest.script.len()); - let script_map: FxHashMap, 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 { - 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 { 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>, 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(); + + 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_wasm_module(&self) -> &WasmModule { - if let FileData::WasmModule(module) = self { - module - } else { - panic!("Not a WASM module") + Self { + paths, + cancel_token, } } - pub fn unwrap_lua_module(&self) -> &LuaModule { - if let FileData::LuaModule(module) = self { - module - } else { - panic!("Not a Lua module") + pub fn watch(&mut self, path: Box) { + let mut paths = self.paths.lock(); + if paths.contains(&path) { + return; } - } - pub fn unwrap_wasm_module_mut(&mut self) -> &mut WasmModule { - if let FileData::WasmModule(module) = self { - module - } else { - panic!("Not a WASM module") - } + 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, 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> { + 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, wasm_compiler: &WasmRunner) { - let boxed_path = Box::from(path.as_ref()); + pub fn start_loading_file(&mut self, path: Box, 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]> { @@ -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, + mouse_clicked: bool, + mouse_location: Option, + scroll_direction: Option, + is_focused: bool, + is_focused_this_frame: bool, + is_unfocused_this_frame: bool, + gil_cx: Option, + gamepad_map: FxHashMap, + gamepad_map_rev: FxHashMap, + connected_gamepads: VecDeque, + pressed_buttons: FxHashSet, + released_buttons: FxHashSet, } 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 { + self.mouse_location + } + + pub fn scroll_direction_this_frame(&self) -> Option { + 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 { + 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 { + 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 { + 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 = 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::(&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::>() - .join(","), - scene - .scripts - .required - .iter() - .map(|i| i.to_string()) - .collect::>() - .join(","), - scene - .scripts - .preload - .iter() - .map(|i| i.to_string()) - .collect::>() - .join(","), - )) - .collect::>() - .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 = 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, - 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, } pub fn scene_id_by_name(scenes: &[Scene], name: &str) -> Option { @@ -31,34 +22,6 @@ pub fn scene_id_by_name(scenes: &[Scene], name: &str) -> Option { 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], - 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 = 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, 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), 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, &mut Box<[u8]>)>; +pub type StaticScriptFunction = Option) + Send + Sync>>; -#[derive(Debug, Clone)] +#[derive(Clone, Serialize, Deserialize)] +#[serde(tag = "type", rename_all = "kebab-case")] pub enum ScriptDeclaration { + #[serde(skip)] Static { name: Box, start: StaticScriptFunction, update: StaticScriptFunction, end: StaticScriptFunction, }, - Lua { - name: Box, - path: Box, - }, Wasm { name: Box, path: Box, @@ -100,10 +102,6 @@ impl<'a> ScriptContext<'a> { } } -fn get_script_state(states: &mut FxHashMap>, 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, + module: &WasmModule, + ) -> Result { + 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::(&manifest).unwrap(); + Ok(GameManifest::from_wasm(path, manifest)) + } + + pub fn patch(&self, module: &WasmModule) -> Result { + 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::(&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]>, 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]) -> 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 { 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { +fn script_name_size(_caller: FuncContext<'_>, context: u64) -> tinywasm::Result { 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 { - 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 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 { + 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 { + puffin::profile_function!(); + Ok(if context_ref(context).storage.saving_data_failed() { + 1 + } else { + 0 + }) +} + +fn config_size(_caller: FuncContext<'_>, context: u64) -> tinywasm::Result { + 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 { + 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 { + puffin::profile_function!(); + Ok(if context_ref(context).storage.saving_config_failed() { + 1 + } else { + 0 + }) +} + fn system_now(_caller: FuncContext<'_>, context: u64) -> tinywasm::Result { puffin::profile_function!(); Ok(context_ref(context) @@ -514,18 +786,29 @@ pub fn linker(store: &mut Store) -> Result { [ 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 { 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>, data: Option>, + saving_config: Arc, + saving_data: Arc, + saving_config_failed: Arc, + saving_data_failed: Arc, } impl StorageManager { pub fn load(app: AppStrategyArgs) -> std::io::Result { 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, + failed: Arc, + ) { + 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(), + ) } } -- cgit v1.3.1