From 75efd36ce0afe5c2bdb03ee85effa88b43f0ee21 Mon Sep 17 00:00:00 2001 From: Matt Mastracci Date: Sat, 20 Dec 2025 18:05:55 -0700 Subject: [PATCH 1/3] Fix for WASM latency --- Cargo.lock | 707 +++++++++++++++----------- Cargo.toml | 12 +- crates/ssu/src/session/wasm.rs | 69 ++- examples/wasm/index-cross-origin.html | 51 ++ examples/wasm/index.html | 26 +- examples/wasm/termtris/index.js | 26 +- src/host/lk201/winit.rs | 2 +- src/host/screen/framebuffer.rs | 2 +- src/host/wgpu/mod.rs | 540 +++++++++++++------- src/host/wgpu/policy.rs | 143 ++++++ src/machine/generic/color.rs | 6 + 11 files changed, 1057 insertions(+), 527 deletions(-) create mode 100644 examples/wasm/index-cross-origin.html create mode 100644 src/host/wgpu/policy.rs diff --git a/Cargo.lock b/Cargo.lock index b257137..c07d258 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -54,9 +54,9 @@ checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" [[package]] name = "android-activity" -version = "0.5.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee91c0c2905bae44f84bfa4e044536541df26b7703fd0888deeb9060fcc44289" +checksum = "ef6978589202a00cd7e118380c448a08b6ed394c3a8df3a430d0898e3a42d046" dependencies = [ "android-properties", "bitflags 2.10.0", @@ -68,7 +68,7 @@ dependencies = [ "log", "ndk", "ndk-context", - "ndk-sys", + "ndk-sys 0.6.0+11769913", "num_enum", "thiserror 1.0.69", ] @@ -104,7 +104,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dc11e49578ae4bf0df28f17516c0e9d006a04cefcedcb006b81134552e2cc8d" dependencies = [ "proc-macro2 1.0.103", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -213,9 +213,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "axum" -version = "0.8.7" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b098575ebe77cb6d14fc7f32749631a6e44edbef6b796f89b020e99ba20d425" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" dependencies = [ "axum-core", "bytes", @@ -356,6 +356,8 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", + "web-time", + "winit", "winit_input_helper", ] @@ -374,30 +376,20 @@ dependencies = [ "generic-array", ] -[[package]] -name = "block-sys" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae85a0696e7ea3b835a453750bf002770776609115e6d25c6d2ff28a8200f7e7" -dependencies = [ - "objc-sys", -] - [[package]] name = "block2" -version = "0.3.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15b55663a85f33501257357e6421bb33e769d5c9ffb5ba0921c975a123e35e68" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" dependencies = [ - "block-sys", "objc2", ] [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "bytemuck" @@ -416,26 +408,26 @@ checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" dependencies = [ "proc-macro2 1.0.103", "quote 1.0.42", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" [[package]] name = "bytesize" -version = "2.2.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c99fa31e08a43eaa5913ef68d7e01c37a2bdce6ed648168239ad33b7d30a9cd8" +checksum = "6bd91ee7b2422bcb158d90ef4d14f75ef67f340943fc4149891dcce8f8b972a3" [[package]] name = "calloop" -version = "0.12.4" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fba7adb4dd5aa98e5553510223000e7148f621165ec5f9acd7113f6ca4995298" +checksum = "b99da2f8558ca23c71f4fd15dc57c906239752dd27ff3c00a1d56b685b7cbfec" dependencies = [ "bitflags 2.10.0", "log", @@ -447,9 +439,9 @@ dependencies = [ [[package]] name = "calloop-wayland-source" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f0ea9b9476c7fad82841a8dbb380e2eae480c21910feba80725b46931ed8f02" +checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20" dependencies = [ "calloop", "rustix 0.38.44", @@ -480,9 +472,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.45" +version = "1.2.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35900b6c8d709fb1d854671ae27aeaa9eec2f8b01b364e1619a40da3e6fe2afe" +checksum = "9f50d563227a1c37cc0a263f64eca3334388c01c5e4c4861a9def205c614383c" dependencies = [ "find-msvc-tools", "jobserver", @@ -516,9 +508,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "clap" -version = "4.5.51" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" +checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" dependencies = [ "clap_builder", "clap_derive", @@ -526,9 +518,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.51" +version = "4.5.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" +checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" dependencies = [ "anstream", "anstyle", @@ -545,7 +537,7 @@ dependencies = [ "heck", "proc-macro2 1.0.103", "quote 1.0.42", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -646,9 +638,9 @@ dependencies = [ [[package]] name = "convert_case" -version = "0.7.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" dependencies = [ "unicode-segmentation", ] @@ -754,7 +746,7 @@ checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ "bitflags 2.10.0", "crossterm_winapi", - "derive_more 2.0.1", + "derive_more 2.1.0", "document-features", "mio", "parking_lot", @@ -831,7 +823,7 @@ dependencies = [ "proc-macro2 1.0.103", "quote 1.0.42", "strsim", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -842,7 +834,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote 1.0.42", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -874,23 +866,24 @@ dependencies = [ [[package]] name = "derive_more" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +checksum = "10b768e943bed7bf2cab53df09f4bc34bfd217cdb57d971e769874c9a6710618" dependencies = [ "derive_more-impl", ] [[package]] name = "derive_more-impl" -version = "2.0.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +checksum = "6d286bfdaf75e988b4a78e013ecd79c581e06399ab53fbacd2d916c2f904f30b" dependencies = [ "convert_case", "proc-macro2 1.0.103", "quote 1.0.42", - "syn 2.0.110", + "rustc_version 0.4.1", + "syn 2.0.111", ] [[package]] @@ -939,6 +932,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" + [[package]] name = "either" version = "1.15.0" @@ -1014,9 +1013,9 @@ dependencies = [ [[package]] name = "find-msvc-tools" -version = "0.1.4" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" [[package]] name = "finl_unicode" @@ -1076,7 +1075,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" dependencies = [ "proc-macro2 1.0.103", "quote 1.0.42", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -1117,7 +1116,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2 1.0.103", "quote 1.0.42", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -1154,9 +1153,9 @@ dependencies = [ [[package]] name = "game-loop" -version = "1.1.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a06ba31889b085d08926f154e554ba0b87d6f6f5d11624ea3445e76df0d0028" +checksum = "c020a1b6923f4c24ecfc54968e13515522fe7b21b892f6a08daae53fb76d222a" dependencies = [ "wasm-bindgen", "web-sys", @@ -1310,7 +1309,6 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash", "allocator-api2", - "serde", ] [[package]] @@ -1325,9 +1323,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ "allocator-api2", "equivalent", @@ -1500,18 +1498,7 @@ checksum = "a6b05c71a01ba1ed3cd3f7126f8c36934a35ba48afdb93068fbd8b039fc0d028" dependencies = [ "proc-macro2 1.0.103", "quote 1.0.42", - "syn 2.0.110", -] - -[[package]] -name = "icrate" -version = "0.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d3aaff8a54577104bafdf686ff18565c3b6903ca5782a2026ef06e2c7aa319" -dependencies = [ - "block2", - "dispatch", - "objc2", + "syn 2.0.111", ] [[package]] @@ -1541,12 +1528,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.0" +version = "2.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" dependencies = [ "equivalent", - "hashbrown 0.16.0", + "hashbrown 0.16.1", "serde", "serde_core", ] @@ -1562,15 +1549,15 @@ dependencies = [ [[package]] name = "instability" -version = "0.3.9" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435d80800b936787d62688c927b6490e887c7ef5ff9ce922c6c6050fca75eb9a" +checksum = "6778b0196eefee7df739db78758e5cf9b37412268bfa5650bfeed028aed20d9c" dependencies = [ "darling", "indoc", "proc-macro2 1.0.103", "quote 1.0.42", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -1647,9 +1634,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.82" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b011eec8cc36da2aab2d5cff675ec18454fad408585853910a202391cf9f8e65" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" dependencies = [ "once_cell", "wasm-bindgen", @@ -1657,11 +1644,11 @@ dependencies = [ [[package]] name = "kasuari" -version = "0.4.10" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "012b421f57b19b6dca64a11f6703087faa71aaef81c6e7cae57dcf7cdf286303" +checksum = "8fe90c1150662e858c7d5f945089b7517b0a80d8bf7ba4b1b5ffc984e7230a5b" dependencies = [ - "hashbrown 0.16.0", + "hashbrown 0.16.1", "portable-atomic", "thiserror 2.0.17", ] @@ -1709,9 +1696,9 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" -version = "0.2.177" +version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "libloading" @@ -1735,13 +1722,13 @@ dependencies = [ [[package]] name = "libredox" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +checksum = "df15f6eac291ed1cf25865b1ee60399f57e7c227e7f51bdbd4c5270396a9ed50" dependencies = [ "bitflags 2.10.0", "libc", - "redox_syscall 0.5.18", + "redox_syscall 0.6.0", ] [[package]] @@ -1766,9 +1753,9 @@ dependencies = [ [[package]] name = "line-clipping" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9bd35dcd25f8578944c44cd75649d6487d74cf895002f6d86613164f2635b72" +checksum = "5f4de44e98ddbf09375cbf4d17714d18f39195f4f4894e8524501726fd9a8a4a" dependencies = [ "bitflags 2.10.0", ] @@ -1809,9 +1796,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.28" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "lru" @@ -1819,7 +1806,7 @@ version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96051b46fc183dc9cd4a223960ef37b9af631b55191852a8274bfef064cda20f" dependencies = [ - "hashbrown 0.16.0", + "hashbrown 0.16.1", ] [[package]] @@ -1944,9 +1931,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", "log", @@ -1964,7 +1951,7 @@ dependencies = [ "bitflags 2.10.0", "codespan-reporting", "hexf-parse", - "indexmap 2.12.0", + "indexmap 2.12.1", "log", "num-traits", "rustc-hash", @@ -1976,14 +1963,14 @@ dependencies = [ [[package]] name = "ndk" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" dependencies = [ "bitflags 2.10.0", "jni-sys", "log", - "ndk-sys", + "ndk-sys 0.6.0+11769913", "num_enum", "raw-window-handle", "thiserror 1.0.69", @@ -2004,6 +1991,15 @@ dependencies = [ "jni-sys", ] +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys", +] + [[package]] name = "nix" version = "0.26.4" @@ -2061,7 +2057,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2 1.0.103", "quote 1.0.42", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -2092,7 +2088,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2 1.0.103", "quote 1.0.42", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -2122,19 +2118,200 @@ checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" [[package]] name = "objc2" -version = "0.4.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "559c5a40fdd30eb5e344fbceacf7595a81e242529fb4e21cf5f43fb4f11ff98d" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" dependencies = [ "objc-sys", "objc2-encode", ] +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.10.0", + "block2", + "libc", + "objc2", + "objc2-core-data", + "objc2-core-image", + "objc2-foundation", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" +dependencies = [ + "bitflags 2.10.0", + "block2", + "objc2", + "objc2-core-location", + "objc2-foundation", +] + +[[package]] +name = "objc2-contacts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.10.0", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-core-location" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" +dependencies = [ + "block2", + "objc2", + "objc2-contacts", + "objc2-foundation", +] + [[package]] name = "objc2-encode" -version = "3.0.0" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d079845b37af429bfe5dfa76e6d087d788031045b25cfc6fd898486fd9847666" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.10.0", + "block2", + "dispatch", + "libc", + "objc2", +] + +[[package]] +name = "objc2-link-presentation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" +dependencies = [ + "block2", + "objc2", + "objc2-app-kit", + "objc2-foundation", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.10.0", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.10.0", + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-symbols" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" +dependencies = [ + "bitflags 2.10.0", + "block2", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-image", + "objc2-core-location", + "objc2-foundation", + "objc2-link-presentation", + "objc2-quartz-core", + "objc2-symbols", + "objc2-uniform-type-identifiers", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-uniform-type-identifiers" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" +dependencies = [ + "bitflags 2.10.0", + "block2", + "objc2", + "objc2-core-location", + "objc2-foundation", +] [[package]] name = "objc_exception" @@ -2221,9 +2398,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.3" +version = "2.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "989e7521a040efde50c3ab6bbadafbe15ab6dc042686926be59ac35d74607df4" +checksum = "cbcfd20a6d4eeba40179f05735784ad32bdaef05ce8e8af05f180d45bb3e7e22" dependencies = [ "memchr", "ucd-trie", @@ -2231,9 +2408,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.8.3" +version = "2.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "187da9a3030dbafabbbfb20cb323b976dc7b7ce91fcd84f2f74d6e31d378e2de" +checksum = "51f72981ade67b1ca6adc26ec221be9f463f2b5839c7508998daa17c23d94d7f" dependencies = [ "pest", "pest_generator", @@ -2241,22 +2418,22 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.3" +version = "2.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49b401d98f5757ebe97a26085998d6c0eecec4995cad6ab7fc30ffdf4b052843" +checksum = "dee9efd8cdb50d719a80088b76f81aec7c41ed6d522ee750178f83883d271625" dependencies = [ "pest", "pest_meta", "proc-macro2 1.0.103", "quote 1.0.42", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] name = "pest_meta" -version = "2.8.3" +version = "2.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72f27a2cfee9f9039c4d86faa5af122a0ac3851441a34865b8a043b46be0065a" +checksum = "bf1d70880e76bdc13ba52eafa6239ce793d85c8e43896507e43dd8984ff05b82" dependencies = [ "pest", "sha2", @@ -2302,7 +2479,7 @@ dependencies = [ "phf_shared", "proc-macro2 1.0.103", "quote 1.0.42", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -2320,6 +2497,26 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5be167a7af36ee22fe3115051bc51f6e6c7054c9348e28deb4f49bd6f705a315" +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2 1.0.103", + "quote 1.0.42", + "syn 2.0.111", +] + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -2392,9 +2589,9 @@ checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" [[package]] name = "portable-atomic" -version = "1.11.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "f59e70c4aef1e55797c2e8fd94a4f2a973fc972cfde0e0b05f683667b0cd39dd" [[package]] name = "powerfmt" @@ -2526,7 +2723,7 @@ checksum = "dc3634611dccc2110ab05a64fec77d26c5f0e0cb0c0bfecb291d9a15841aae91" dependencies = [ "bitflags 2.10.0", "compact_str", - "hashbrown 0.16.0", + "hashbrown 0.16.1", "indoc", "itertools 0.14.0", "kasuari", @@ -2577,7 +2774,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bf145c4da1f8e5c58957350985ace8facbbf394ddecc526cd33e7dcf5831c41" dependencies = [ "bitflags 2.10.0", - "hashbrown 0.16.0", + "hashbrown 0.16.1", "indoc", "instability", "itertools 0.14.0", @@ -2627,9 +2824,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.3.5" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ "bitflags 1.3.2", ] @@ -2643,6 +2840,15 @@ dependencies = [ "bitflags 2.10.0", ] +[[package]] +name = "redox_syscall" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec96166dafa0886eb81fe1c0a388bece180fbef2135f97c1e2cf8302e74b43b5" +dependencies = [ + "bitflags 2.10.0", +] + [[package]] name = "regex" version = "1.12.2" @@ -2709,7 +2915,7 @@ dependencies = [ "regex", "relative-path", "rustc_version 0.4.1", - "syn 2.0.110", + "syn 2.0.111", "unicode-ident", ] @@ -2777,9 +2983,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "62049b2877bf12821e8f9ad256ee38fdc31db7387ec2d3b3f403024de2034aea" [[package]] name = "safe_arch" @@ -2813,9 +3019,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sctk-adwaita" -version = "0.8.3" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70b31447ca297092c5a9916fc3b955203157b37c19ca8edde4f52e9843e602c7" +checksum = "b6277f0217056f77f1d8f49f2950ac6c278c0d607c45f5ee99328d792ede24ec" dependencies = [ "ab_glyph", "log", @@ -2872,7 +3078,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2 1.0.103", "quote 1.0.42", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -2985,18 +3191,18 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.6" +version = "1.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" dependencies = [ "libc", ] [[package]] name = "simd-adler32" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" [[package]] name = "siphasher" @@ -3012,9 +3218,9 @@ checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" [[package]] name = "slotmap" -version = "1.0.7" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" dependencies = [ "version_check", ] @@ -3027,9 +3233,9 @@ checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "smithay-client-toolkit" -version = "0.18.1" +version = "0.19.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "922fd3eeab3bd820d76537ce8f582b1cf951eceb5475c28500c7457d9d17f53a" +checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016" dependencies = [ "bitflags 2.10.0", "calloop", @@ -3137,7 +3343,7 @@ dependencies = [ "heck", "proc-macro2 1.0.103", "quote 1.0.42", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -3164,9 +3370,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.110" +version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a99801b5bd34ede4cf3fc688c5919368fea4e4814a4664359503e6015b280aea" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2 1.0.103", "quote 1.0.42", @@ -3290,7 +3496,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2 1.0.103", "quote 1.0.42", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -3301,7 +3507,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2 1.0.103", "quote 1.0.42", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -3384,7 +3590,7 @@ checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ "proc-macro2 1.0.103", "quote 1.0.42", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -3402,20 +3608,20 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.7.3" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ "serde_core", ] [[package]] name = "toml_edit" -version = "0.23.7" +version = "0.23.10+spec-1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6485ef6d0d9b5d0ec17244ff7eb05310113c3f316f2d14200d4de56b3cb98f8d" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.12.1", "toml_datetime", "toml_parser", "winnow", @@ -3423,9 +3629,9 @@ dependencies = [ [[package]] name = "toml_parser" -version = "1.0.4" +version = "1.0.6+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +checksum = "a3198b4b0a8e11f09dd03e133c0280504d0801269e9afa46362ffde1cbeebf44" dependencies = [ "winnow", ] @@ -3486,9 +3692,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", @@ -3498,20 +3704,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2 1.0.103", "quote 1.0.42", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -3530,9 +3736,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.20" +version = "0.3.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2054a14f5307d601f88daf0553e1cbf472acc4f2c51afab632431cdcd72124d5" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" dependencies = [ "matchers", "nu-ansi-term", @@ -3571,7 +3777,7 @@ checksum = "7bedeb0f3102bf28216523793a6303e0b5928730be796593cc5b0c36c98dbc9f" dependencies = [ "proc-macro2 1.0.103", "quote 1.0.42", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -3597,9 +3803,9 @@ dependencies = [ [[package]] name = "unescaper" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c01d12e3a56a4432a8b436f293c25f4808bdf9e9f9f98f9260bba1f1bc5a1f26" +checksum = "4064ed685c487dbc25bd3f0e9548f2e34bab9d18cefc700f9ec2dba74ba1138e" dependencies = [ "thiserror 2.0.17", ] @@ -3665,9 +3871,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.18.1" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" +checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" dependencies = [ "atomic", "getrandom", @@ -3730,7 +3936,7 @@ dependencies = [ "rayon", "walrus-macro", "wasm-encoder", - "wasmparser 0.240.0", + "wasmparser", ] [[package]] @@ -3742,7 +3948,7 @@ dependencies = [ "heck", "proc-macro2 1.0.103", "quote 1.0.42", - "syn 2.0.110", + "syn 2.0.111", ] [[package]] @@ -3762,9 +3968,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.105" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" dependencies = [ "cfg-if", "once_cell", @@ -3775,9 +3981,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-cli-support" -version = "0.2.105" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d3f73cd40cc5c6adf6a090e331f24595f87e7a5bd70ccf3db46be0e1d6e0d92" +checksum = "03794299fa80bda34aef2784a496c6440fbc75fb1977c4e05750ddcd617e5a09" dependencies = [ "anyhow", "base64", @@ -3788,14 +3994,14 @@ dependencies = [ "serde_json", "walrus", "wasm-bindgen-shared", - "wasmparser 0.214.0", + "wasmparser", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.55" +version = "0.4.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0" +checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" dependencies = [ "cfg-if", "js-sys", @@ -3806,9 +4012,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.105" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04264334509e04a7bf8690f2384ef5265f05143a4bff3889ab7a3269adab59c2" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" dependencies = [ "quote 1.0.42", "wasm-bindgen-macro-support", @@ -3816,22 +4022,22 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.105" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "420bc339d9f322e562942d52e115d57e950d12d88983a14c79b86859ee6c7ebc" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" dependencies = [ "bumpalo", "proc-macro2 1.0.103", "quote 1.0.42", - "syn 2.0.110", + "syn 2.0.111", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.105" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76f218a38c84bcb33c25ec7059b07847d465ce0e0a76b995e134a45adcb6af76" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" dependencies = [ "unicode-ident", ] @@ -3843,21 +4049,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06d642d8c5ecc083aafe9ceb32809276a304547a3a6eeecceb5d8152598bc71f" dependencies = [ "leb128fmt", - "wasmparser 0.240.0", -] - -[[package]] -name = "wasmparser" -version = "0.214.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5309c1090e3e84dad0d382f42064e9933fdaedb87e468cc239f0eabea73ddcb6" -dependencies = [ - "ahash", - "bitflags 2.10.0", - "hashbrown 0.14.5", - "indexmap 2.12.0", - "semver 1.0.27", - "serde", + "wasmparser", ] [[package]] @@ -3868,7 +4060,7 @@ checksum = "b722dcf61e0ea47440b53ff83ccb5df8efec57a69d150e4f24882e4eba7e24a4" dependencies = [ "bitflags 2.10.0", "hashbrown 0.15.5", - "indexmap 2.12.0", + "indexmap 2.12.1", "semver 1.0.27", "serde", ] @@ -3923,9 +4115,9 @@ dependencies = [ [[package]] name = "wayland-protocols" -version = "0.31.2" +version = "0.32.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f81f365b8b4a97f422ac0e8737c438024b5951734506b0e1d775c73030561f4" +checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901" dependencies = [ "bitflags 2.10.0", "wayland-backend", @@ -3935,9 +4127,9 @@ dependencies = [ [[package]] name = "wayland-protocols-plasma" -version = "0.2.0" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23803551115ff9ea9bce586860c5c5a971e360825a0309264102a9495a5ff479" +checksum = "a07a14257c077ab3279987c4f8bb987851bf57081b93710381daea94f2c2c032" dependencies = [ "bitflags 2.10.0", "wayland-backend", @@ -3948,9 +4140,9 @@ dependencies = [ [[package]] name = "wayland-protocols-wlr" -version = "0.2.0" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1f61b76b6c2d8742e10f9ba5c3737f6530b4c243132c2a2ccc8aa96fe25cd6" +checksum = "efd94963ed43cf9938a090ca4f7da58eb55325ec8200c3848963e98dc25b78ec" dependencies = [ "bitflags 2.10.0", "wayland-backend", @@ -3984,19 +4176,9 @@ dependencies = [ [[package]] name = "web-sys" -version = "0.3.82" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a1f95c0d03a47f4ae1f7a64643a6bb97465d9b740f0fa8f90ea33915c99a9a1" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "web-time" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa30049b1c872b72c89866d458eae9f20380ab280ffd1b1e18df2d3e2d98cfe0" +checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" dependencies = [ "js-sys", "wasm-bindgen", @@ -4120,7 +4302,7 @@ dependencies = [ "bitflags 2.10.0", "cfg_aliases 0.1.1", "codespan-reporting", - "indexmap 2.12.0", + "indexmap 2.12.1", "log", "naga", "once_cell", @@ -4163,7 +4345,7 @@ dependencies = [ "log", "metal", "naga", - "ndk-sys", + "ndk-sys 0.5.0+25.2.9519653", "objc", "once_cell", "parking_lot", @@ -4274,11 +4456,11 @@ dependencies = [ [[package]] name = "windows-sys" -version = "0.48.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -4323,21 +4505,6 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - [[package]] name = "windows-targets" version = "0.52.6" @@ -4377,12 +4544,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -4401,12 +4562,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -4425,12 +4580,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -4461,12 +4610,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -4485,12 +4628,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -4509,12 +4646,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -4533,12 +4664,6 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -4553,37 +4678,41 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winit" -version = "0.29.15" +version = "0.30.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d59ad965a635657faf09c8f062badd885748428933dad8e8bdd64064d92e5ca" +checksum = "c66d4b9ed69c4009f6321f762d6e61ad8a2389cd431b97cb1e146812e9e6c732" dependencies = [ "ahash", "android-activity", "atomic-waker", "bitflags 2.10.0", + "block2", "bytemuck", "calloop", - "cfg_aliases 0.1.1", + "cfg_aliases 0.2.1", + "concurrent-queue", "core-foundation 0.9.4", "core-graphics", "cursor-icon", - "icrate", + "dpi", "js-sys", "libc", - "log", "memmap2", "ndk", - "ndk-sys", "objc2", - "once_cell", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", "orbclient", "percent-encoding", + "pin-project", "raw-window-handle", - "redox_syscall 0.3.5", + "redox_syscall 0.4.1", "rustix 0.38.44", "sctk-adwaita", "smithay-client-toolkit", "smol_str", + "tracing", "unicode-segmentation", "wasm-bindgen", "wasm-bindgen-futures", @@ -4592,8 +4721,8 @@ dependencies = [ "wayland-protocols", "wayland-protocols-plasma", "web-sys", - "web-time 0.2.4", - "windows-sys 0.48.0", + "web-time", + "windows-sys 0.52.0", "x11-dl", "x11rb", "xkbcommon-dl", @@ -4601,19 +4730,19 @@ dependencies = [ [[package]] name = "winit_input_helper" -version = "0.15.3" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec8c438d722f4ce69f261e0206072b51a8ac80af1ad6bf7e3dc5c25b5d8483a3" +checksum = "8e3dcabe3fca8a9a6286b9883057feaf3aeed0a159ea27e0611e49458afb7f75" dependencies = [ - "web-time 1.1.0", + "web-time", "winit", ] [[package]] name = "winnow" -version = "0.7.13" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" dependencies = [ "memchr", ] @@ -4689,20 +4818,20 @@ checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" [[package]] name = "zerocopy" -version = "0.8.27" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.27" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" dependencies = [ "proc-macro2 1.0.103", "quote 1.0.42", - "syn 2.0.110", + "syn 2.0.111", ] diff --git a/Cargo.toml b/Cargo.toml index f4fac0e..30df8af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,10 +36,14 @@ ratatui = { version = "0.30.0-beta.0", default-features = false, optional = true # features=graphics pixels = { git = "https://github.com/parasyte/pixels.git", features = [], optional = true } -game-loop = { version = "=1.1.0", features = ["winit"], optional = true } -winit_input_helper = { version = "=0.15", optional = true } +game-loop = { version = "=1.3.0", features = ["winit"], optional = true } +winit_input_helper = { version = "=0.17.0", optional = true } +winit = { version = "=0.30.12", optional = true } pollster = { version = "0.4.0", optional = true } +# features=wasm +web-time = { version = "1.1.0", optional = true } + # features=run-wasm cargo-run-wasm = { version = "0.4.0", optional = true } @@ -57,11 +61,11 @@ debug = true [features] default = ["graphics", "tui", "pty", "demo", "vram-dump"] -wasm = ["graphics", "embed-rom", "ssu/wasm"] +wasm = ["graphics", "embed-rom", "ssu/wasm", "dep:web-time"] pc-trace = [] pty = ["ssu/pty"] tui = ["dep:ratatui", "ratatui/crossterm", "dep:i8051-debug-tui"] -graphics = ["dep:pixels", "dep:game-loop", "dep:winit_input_helper", "dep:pollster"] +graphics = ["dep:pixels", "dep:winit", "dep:winit_input_helper", "dep:pollster"] embed-rom = [] run-wasm = ["dep:cargo-run-wasm"] demo = ["dep:vt-push-parser", "dep:ratatui"] diff --git a/crates/ssu/src/session/wasm.rs b/crates/ssu/src/session/wasm.rs index 64cb02a..87eb70f 100644 --- a/crates/ssu/src/session/wasm.rs +++ b/crates/ssu/src/session/wasm.rs @@ -8,6 +8,8 @@ extern "C" {} pub struct WasmSession { read_fn: js_sys::Function, + read_buffer: js_sys::Uint8Array, + read_buffer_index: u32, write_fn: js_sys::Function, } @@ -27,7 +29,43 @@ fn to_io_error(e: impl AsRef) -> io::Error { impl WasmSession { pub fn new(read_fn: String, write_fn: String) -> io::Result { - let read_fn = js_sys::Function::from(js_sys::eval(&read_fn).map_err(to_io_error)?); + // Convert the async function into one that returns null if a promise is resolving. + let read_fn = js_sys::Function::from( + js_sys::eval( + &r#" + (function() { + const LOW_WATER_MARK = 2; + const async_fn = __FN__; + let promise = undefined; + let next = []; + let then = (value) => { + next.push(value); + if (next.length < LOW_WATER_MARK && promise === undefined) { + promise = async_fn().then(then); + } else { + promise = undefined; + } + }; + + return function() { + if (next.length === 0) { + if (promise === undefined) { + promise = async_fn().then(then); + } + return null; + } + const result = next.shift(); + if (next.length < LOW_WATER_MARK && promise === undefined) { + promise = async_fn().then(then); + } + return result; + }; + })(); + "# + .replace("__FN__", &read_fn), + ) + .map_err(to_io_error)?, + ); if !read_fn.is_function() { return Err(io::Error::other("read_fn was not a function")); } @@ -35,13 +73,19 @@ impl WasmSession { if !write_fn.is_function() { return Err(io::Error::other("write_fn was not a function")); } - Ok(Self { read_fn, write_fn }) + Ok(Self { + read_fn, + write_fn, + read_buffer: js_sys::Uint8Array::new_with_length(0), + read_buffer_index: 0, + }) } pub fn new_message_channel() -> io::Result { let array = js_sys::eval( r#" (function () { + const LOW_WATER_MARK = 2; let messageChannel = new MessageChannel(); const interval = setInterval(function () { window.parent.postMessage({ type: "ready" }, "*"); @@ -53,9 +97,7 @@ impl WasmSession { port.onmessage = function (event) { reading = false; const data = new Uint8Array(event.data.data); - for (let i = 0; i < data.length; i++) { - readQueue.push(data[i]); - } + readQueue.push(data); }; window.onmessage = function (event) { @@ -66,7 +108,7 @@ impl WasmSession { }; function js_read(data) { - if (readQueue.length === 0 && !reading) { + if (readQueue.length < LOW_WATER_MARK && !reading) { reading = true; port.postMessage({ type: "read" }); } @@ -89,12 +131,21 @@ impl WasmSession { if !write_fn.is_function() { return Err(io::Error::other("write_fn was not a function")); } - Ok(Self { read_fn, write_fn }) + Ok(Self { + read_fn, + write_fn, + read_buffer: js_sys::Uint8Array::new_with_length(0), + read_buffer_index: 0, + }) } } impl SessionEndpoint for WasmSession { fn recv(&mut self) -> Ticked { + if self.read_buffer_index < self.read_buffer.byte_length() as _ { + self.read_buffer_index += 1; + return Ticked::Byte(self.read_buffer.get_index(self.read_buffer_index - 1)); + } let b = self.read_fn.call0(&JsValue::UNDEFINED); let Ok(b) = b else { return Ticked::Idle; @@ -102,7 +153,9 @@ impl SessionEndpoint for WasmSession { if b.is_null_or_undefined() { return Ticked::Idle; } - Ticked::Byte(b.as_f64().unwrap_or_default() as u8) + self.read_buffer = js_sys::Uint8Array::from(b); + self.read_buffer_index = 1; + Ticked::Byte(self.read_buffer.get_index(0)) } fn send(&mut self, b: u8) { diff --git a/examples/wasm/index-cross-origin.html b/examples/wasm/index-cross-origin.html new file mode 100644 index 0000000..f35ae60 --- /dev/null +++ b/examples/wasm/index-cross-origin.html @@ -0,0 +1,51 @@ + + + + + + Blaze: VT420 Emulator + + + + + + + + diff --git a/examples/wasm/index.html b/examples/wasm/index.html index f35ae60..11c8db1 100644 --- a/examples/wasm/index.html +++ b/examples/wasm/index.html @@ -22,30 +22,6 @@ - - + diff --git a/examples/wasm/termtris/index.js b/examples/wasm/termtris/index.js index e928bc0..9d603aa 100644 --- a/examples/wasm/termtris/index.js +++ b/examples/wasm/termtris/index.js @@ -1,12 +1,15 @@ const TICK_INTERVAL = 52; +const BUFFER_HIGH_WATER_MARK = 256; class Termtris { _readQueue = []; + _readBufferSize = 0; _readWaker = null; _approxTime = 0; _nextTime = 0; _wasmInstance = null; _startTime; + _missedTicks = 0; constructor() { this._startTime = Date.now(); @@ -16,11 +19,14 @@ class Termtris { _tick() { this._approxTime = Date.now() - this._startTime; if (this._wasmInstance !== null) { - if (this._readQueue.length === 0) { - if (this._nextTime < this._approxTime) { + if (this._nextTime < this._approxTime) { + if (this._readBufferSize < BUFFER_HIGH_WATER_MARK) { // We could use the next tick time here but it's fine to just // call the game update more often instead. this._nextTime = this._wasmInstance.exports._update(this._approxTime) + this._approxTime; + } else { + this._missedTicks++; + console.log("missed tick", this._missedTicks); } } } @@ -28,12 +34,23 @@ class Termtris { async read() { if (this._readQueue.length > 0) { - return this._readQueue.shift(); + return this._doSyncRead(); } const readPromise = new Promise((resolve, reject) => this._readWaker = resolve); await readPromise; this._readWaker = null; - return this._readQueue.shift(); + return this._doSyncRead(); + } + + _doSyncRead() { + const result = this._readQueue.shift(); + this._readBufferSize -= result.length; + if (this._missedTicks > 0 && this._readBufferSize < BUFFER_HIGH_WATER_MARK) { + console.log("restoring tick", this._missedTicks); + this._missedTicks = 0; + this._tick(); + } + return result; } write(byte) { @@ -107,6 +124,7 @@ function loadTermtris() { console.log(new TextDecoder().decode(bytes)); if (fd == 1) { termtris._readQueue.push(bytes); + termtris._readBufferSize += bytes.length; } written += len; } diff --git a/src/host/lk201/winit.rs b/src/host/lk201/winit.rs index 3f20341..0089f9c 100644 --- a/src/host/lk201/winit.rs +++ b/src/host/lk201/winit.rs @@ -1,4 +1,4 @@ -use game_loop::winit::keyboard::{Key, KeyCode}; +use winit::keyboard::{Key, KeyCode}; use winit_input_helper::WinitInputHelper; use lk201::{LK201Sender, SpecialKey}; diff --git a/src/host/screen/framebuffer.rs b/src/host/screen/framebuffer.rs index 75727dd..c524fcf 100644 --- a/src/host/screen/framebuffer.rs +++ b/src/host/screen/framebuffer.rs @@ -137,7 +137,7 @@ impl FramebufferRender { let width = if render.row_flags.is_80 { 10 } else { 6 }; let mut offset = render.row_offset; for mut y in 0..render.row_flags.row_height as usize { - if render.row + y >= crate::host::wgpu::WIDTH as _ { + if render.row + y >= crate::host::wgpu::REAL_WIDTH as _ { break; } if c == 0 && !render.row_flags.is_80 { diff --git a/src/host/wgpu/mod.rs b/src/host/wgpu/mod.rs index 75f5151..d7046a0 100644 --- a/src/host/wgpu/mod.rs +++ b/src/host/wgpu/mod.rs @@ -1,50 +1,269 @@ #![deny(clippy::all)] #![forbid(unsafe_code)] -pub const WIDTH: u32 = 800; -pub const HEIGHT: u32 = 416; -const FPS: u32 = 60; -const TIME_STEP: Duration = Duration::from_micros(1_000_000 / FPS as u64); +pub const REAL_WIDTH: u32 = 800; +pub const REAL_HEIGHT: u32 = 416; +pub const ASPECT_RATIO: f64 = 4.0 / 3.0; +pub const WINDOW_WIDTH: u32 = REAL_WIDTH as u32; +pub const WINDOW_HEIGHT: u32 = (REAL_WIDTH as f64 / ASPECT_RATIO as f64) as u32; -use game_loop::winit; - -use game_loop::{Time, TimeTrait as _, game_loop}; use pixels::{Error, Pixels, PixelsBuilder, SurfaceTexture}; use std::sync::Arc; use std::time::Duration; -use winit::{dpi::LogicalSize, event_loop::EventLoop, window::WindowBuilder}; +use winit::application::ApplicationHandler; +use winit::dpi; +use winit::event::WindowEvent; +use winit::event_loop::EventLoopProxy; +use winit::{dpi::LogicalSize, event_loop::EventLoop, window::Window}; use winit_input_helper::WinitInputHelper; use crate::host::lk201::winit::update_keyboard; +use crate::host::wgpu::policy::FramePolicy; use lk201::LK201Sender; -use tracing::{error, info}; +use tracing::{debug, error, info}; + +mod policy; + +enum PixelsState { + None(EventLoopProxy>), + Initializing { + window: Arc, + size: Option>, + }, + Running { + window: Arc, + pixels: Pixels<'static>, + }, +} /// Uber-struct representing the entire game. struct Terminal { /// Software renderer. - pixels: Pixels<'static>, + pixels: PixelsState, /// Event manager. input: WinitInputHelper, /// Game pause state. paused: bool, /// LK201 keyboard sender. sender: LK201Sender, + /// Frame policy. + frame_policy: FramePolicy, + /// Render function. + render: Box, + /// Step function. + step: Box, } impl Terminal { - fn new(pixels: Pixels<'static>, sender: LK201Sender) -> Self { + fn new( + sender: LK201Sender, + proxy: EventLoopProxy>, + render: Box, + step: Box, + ) -> Self { Self { - pixels, + pixels: PixelsState::None(proxy), input: WinitInputHelper::new(), paused: false, + frame_policy: FramePolicy::new(), sender, + render, + step, + } + } + + fn window(&self) -> &winit::window::Window { + match &self.pixels { + PixelsState::Initializing { window, .. } => window, + PixelsState::Running { window, .. } => window, + PixelsState::None(..) => unreachable!(), } } fn update_controls(&mut self) { update_keyboard(&self.input, &self.sender); } + + fn init_window(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { + let window = match event_loop.create_window( + Window::default_attributes() + .with_title("VT420") + .with_inner_size(LogicalSize::new(REAL_WIDTH as f64, REAL_HEIGHT as f64)) + .with_min_inner_size(LogicalSize::new(REAL_WIDTH as f64, REAL_HEIGHT as f64)), + ) { + Ok(window) => window, + Err(e) => { + error!("Failed to create window: {}", e); + event_loop.exit(); + return; + } + }; + info!("Graphics: window created"); + + let PixelsState::None(proxy) = &mut self.pixels else { + unreachable!(); + }; + let proxy = proxy.clone(); + let window = Arc::new(window); + self.pixels = PixelsState::Initializing { + window: window.clone(), + size: None, + }; + let window = window.clone(); + let future = async move { + match create_pixels(window).await { + Ok(pixels) => { + info!("Graphics: sending pixels event"); + if let Err(e) = proxy.send_event(pixels) { + error!("Graphics: Event loop closed during initialization: {e}"); + return; + } + info!("Graphics: pixels event sent"); + } + Err(e) => { + log_pixels_error(e); + } + } + }; + + #[cfg(target_arch = "wasm32")] + { + wasm_bindgen_futures::spawn_local(future); + } + + #[cfg(not(target_arch = "wasm32"))] + pollster::block_on(future); + + info!("Graphics: window initialized"); + } +} + +impl ApplicationHandler> for Terminal { + fn about_to_wait(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { + self.update_controls(); + for _ in 0..self.frame_policy.updates_to_run { + (self.step)(); + } + if self.frame_policy.will_redraw { + match &mut self.pixels { + PixelsState::Running { pixels, .. } => { + (self.render)(pixels.frame_mut()); + if let Err(err) = pixels.render() { + error!("Graphics: pixels.render failed: {err}"); + self.frame_policy.on_present_failed_retry(); + } else { + self.frame_policy.on_presented(); + } + } + _ => unreachable!(), + } + } + let idle = self.frame_policy.plan_idle(); + event_loop.set_control_flow(idle.control_flow); + if idle.request_redraw { + self.window().request_redraw(); + } + self.input.end_step(); + } + + fn device_event( + &mut self, + _event_loop: &winit::event_loop::ActiveEventLoop, + _device_id: winit::event::DeviceId, + event: winit::event::DeviceEvent, + ) { + debug!("Graphics: got device event: {event:?}"); + self.input.process_device_event(&event); + } + + fn exiting(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop) {} + fn memory_warning(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop) {} + fn new_events( + &mut self, + event_loop: &winit::event_loop::ActiveEventLoop, + cause: winit::event::StartCause, + ) { + match cause { + winit::event::StartCause::Init => { + info!("Graphics: starting"); + self.init_window(event_loop); + } + winit::event::StartCause::ResumeTimeReached { .. } + | winit::event::StartCause::WaitCancelled { .. } + | winit::event::StartCause::Poll => { + self.frame_policy.plan_tick(cause); + self.input.step(); + } + } + } + fn resumed(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop) {} + fn suspended(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop) {} + fn user_event( + &mut self, + event_loop: &winit::event_loop::ActiveEventLoop, + event: Pixels<'static>, + ) { + info!("Graphics: got pixels event"); + let PixelsState::Initializing { window, size } = &mut self.pixels else { + unreachable!(); + }; + let mut pixels = event; + let window = window.clone(); + let size = size.clone(); + if let Some(size) = size { + info!( + "Graphics: resizing surface to {}x{}", + size.width, size.height + ); + if let Err(err) = pixels.resize_surface(size.width, size.height) { + error!("Graphics: pixels.resize_surface: {err}"); + event_loop.exit(); + } + } + self.pixels = PixelsState::Running { window, pixels }; + self.window().request_redraw(); + } + + fn window_event( + &mut self, + event_loop: &winit::event_loop::ActiveEventLoop, + _window_id: winit::window::WindowId, + event: WindowEvent, + ) { + self.input.process_window_event(&event); + if let Some(resize) = self.input.window_resized() { + match &mut self.pixels { + PixelsState::None(..) => { + unreachable!(); + } + PixelsState::Initializing { size, .. } => { + *size = Some(resize); + } + PixelsState::Running { pixels, .. } => { + // window_resized() returns physical size, but clamp to reasonable maximum + // texture size (most GPUs support up to 16384, but we'll use 4096 to be safe) + const MAX_TEXTURE_SIZE: u32 = 4096; + let width = resize.width.min(MAX_TEXTURE_SIZE); + let height = resize.height.min(MAX_TEXTURE_SIZE); + if let Err(err) = pixels.resize_surface(width, height) { + error!("Graphics: pixels.resize_surface: {err}"); + event_loop.exit(); + } + } + } + } + + match event { + WindowEvent::RedrawRequested => { + self.frame_policy.on_request_redraw(); + } + WindowEvent::CloseRequested => { + event_loop.exit(); + } + _ => {} + } + } } #[cfg(target_arch = "wasm32")] @@ -60,63 +279,65 @@ fn get_window_size() -> LogicalSize { size } +fn log_pixels_error(e: Error) { + match e { + Error::AdapterNotFound => { + error!("Graphics error: Adapter not found"); + } + Error::CreateSurface(e) => { + error!("Graphics error: Create surface: {}", e); + } + Error::DeviceNotFound(e) => { + error!("Graphics error: Device not found: {}", e); + } + Error::InvalidTexture(e) => { + error!("Graphics error: Invalid texture: {}", e); + } + Error::UserDefined(e) => { + error!("Graphics error: {}", e); + } + Error::Surface(e) => { + error!("Graphics error: Surface: {}", e); + } + _ => { + error!("Graphics error: Unexpected error: {}", e); + } + } +} + pub fn main( sender: LK201Sender, render: impl FnMut(&mut [u8]) + 'static, step: impl FnMut() + 'static, ) -> Result<(), Error> { - let future = main_async(sender, render, step); + let event_loop = EventLoop::>::with_user_event() + .build() + .map_err(|e| Error::UserDefined(Box::new(e)))?; + let proxy = event_loop.create_proxy(); + let mut terminal = Terminal::new(sender, proxy, Box::new(render), Box::new(step)); #[cfg(target_arch = "wasm32")] { - wasm_bindgen_futures::spawn_local(async { - let res = future.await; - if let Err(e) = res { - match e { - Error::AdapterNotFound => { - error!("Graphics error: Adapter not found"); - } - Error::CreateSurface(e) => { - error!("Graphics error: Create surface: {}", e); - } - Error::DeviceNotFound(e) => { - error!("Graphics error: Device not found: {}", e); - } - Error::InvalidTexture(e) => { - error!("Graphics error: Invalid texture: {}", e); - } - Error::UserDefined(e) => { - error!("Graphics error: {}", e); - } - Error::Surface(e) => { - error!("Graphics error: Surface: {}", e); - } - _ => { - error!("Graphics error: Unexpected error: {}", e); - } - } - } - }); - Ok(()) + use winit::platform::web::EventLoopExtWebSys; + event_loop.spawn_app(terminal); } + #[cfg(not(target_arch = "wasm32"))] - pollster::block_on(future) + event_loop + .run_app(&mut terminal) + .map_err(|e| Error::UserDefined(Box::new(e)))?; + + Ok(()) } #[cfg(target_arch = "wasm32")] -pub fn get_canvas(#[allow(unused)] window: &winit::window::Window) -> web_sys::HtmlCanvasElement { +fn get_canvas(#[allow(unused)] window: &winit::window::Window) -> web_sys::HtmlCanvasElement { use winit::platform::web::WindowExtWebSys; window.canvas().unwrap() } -pub async fn main_async( - sender: LK201Sender, - mut render: impl FnMut(&mut [u8]) + 'static, - mut step: impl FnMut() + 'static, -) -> Result<(), Error> { - let event_loop = EventLoop::new().unwrap(); - - #[cfg(target_arch = "wasm32")] +#[cfg(target_arch = "wasm32")] +fn patch_webgpu() { js_sys::eval( r#""" console.log("Patching WebGPU GPUAdapter.requestDevice"); @@ -138,163 +359,92 @@ pub async fn main_async( """#, ) .expect("Failed to evaluate WebGPU patch"); +} - let window = { - let size = LogicalSize::new(WIDTH as f64, HEIGHT as f64); - let scaled_size = LogicalSize::new(WIDTH as f64 * 2.0, HEIGHT as f64 * 2.0); - let window = WindowBuilder::new() - .with_title("VT420") - .with_inner_size(scaled_size) - .with_min_inner_size(size) - .build(&event_loop) - .unwrap(); - Arc::new(window) - }; - - info!("Graphics: window created"); - - // Attach winit canvas to body element - #[cfg(target_arch = "wasm32")] - { - use js_sys::Promise; - use wasm_bindgen::JsCast; - use wasm_bindgen::closure::Closure; - use wasm_bindgen_futures::JsFuture; - - let canvas = get_canvas(&window); - - web_sys::window() - .and_then(|win| win.document()) - .and_then(|doc| doc.body()) - .and_then(|body| body.append_child(&canvas).ok()) - .expect("couldn't append canvas to document body"); - - info!("Graphics: canvas attached to document body"); - - // Listen for resize event on browser client. Adjust winit window dimensions - // on event trigger - let closure = Closure::wrap(Box::new({ - let window = Arc::clone(&window); - move |_e: web_sys::Event| { - let _ = window.request_inner_size(get_window_size()); - } - }) as Box); +#[cfg(target_arch = "wasm32")] +pub async fn attach_canvas(window: &Arc) { + use js_sys::Promise; + use wasm_bindgen::JsCast; + use wasm_bindgen::closure::Closure; + use wasm_bindgen_futures::JsFuture; + + let canvas = get_canvas(&window); + + web_sys::window() + .and_then(|win| win.document()) + .and_then(|doc| doc.body()) + .and_then(|body| body.append_child(&canvas).ok()) + .expect("couldn't append canvas to document body"); + + info!("Graphics: canvas attached to document body"); + + // Listen for resize event on browser client. Adjust winit window dimensions + // on event trigger + let closure = Closure::wrap(Box::new({ + let window = Arc::clone(&window); + move |_e: web_sys::Event| { + let _ = window.request_inner_size(get_window_size()); + } + }) as Box); + web_sys::window() + .unwrap() + .add_event_listener_with_callback("resize", closure.as_ref().unchecked_ref()) + .unwrap(); + closure.forget(); + + // Trigger initial resize event + let _ = window.request_inner_size(get_window_size()); + + let promise = Promise::new(&mut |resolve, _reject| { + let closure = Closure::wrap(Box::new(move |_timestamp: f64| { + resolve.call0(&wasm_bindgen::JsValue::UNDEFINED).unwrap(); + }) as Box); web_sys::window() .unwrap() - .add_event_listener_with_callback("resize", closure.as_ref().unchecked_ref()) + .request_animation_frame(closure.as_ref().unchecked_ref()) .unwrap(); closure.forget(); + }); - // Trigger initial resize event - let _ = window.request_inner_size(get_window_size()); - - let promise = Promise::new(&mut |resolve, _reject| { - let closure = Closure::wrap(Box::new(move |_timestamp: f64| { - resolve.call0(&wasm_bindgen::JsValue::UNDEFINED).unwrap(); - }) as Box); - web_sys::window() - .unwrap() - .request_animation_frame(closure.as_ref().unchecked_ref()) - .unwrap(); - closure.forget(); - }); - - // Yield to the event loop until the animation frame callback has been called - // This ensures Chrome has rendered the canvas before WebGPU initialization - JsFuture::from(promise).await.ok(); - - info!("Graphics: waited for canvas to be rendered"); - } + // Yield to the event loop until the animation frame callback has been called + // This ensures Chrome has rendered the canvas before WebGPU initialization + JsFuture::from(promise).await.ok(); - let mut pixels = { - #[cfg(not(target_arch = "wasm32"))] - let window_size = window.inner_size(); - #[cfg(target_arch = "wasm32")] - let window_size = get_window_size().to_physical::(window.scale_factor()); + info!("Graphics: canvas attached"); +} - info!( - "Graphics: window size: {}x{}", - window_size.width, window_size.height - ); +async fn create_pixels(window: Arc) -> Result, Error> { + #[cfg(target_arch = "wasm32")] + attach_canvas(&window).await; - let surface_texture = SurfaceTexture::new(WIDTH, HEIGHT, Arc::clone(&window)); + #[cfg(not(target_arch = "wasm32"))] + let window_size = window.inner_size(); + #[cfg(target_arch = "wasm32")] + let window_size = get_window_size().to_physical::(window.scale_factor()); - let pixel_builder = PixelsBuilder::new(WIDTH, HEIGHT, surface_texture); - #[cfg(target_arch = "wasm32")] - let pixel_builder = { - // Web targets do not support the default texture format - let texture_format = pixels::wgpu::TextureFormat::Rgba8Unorm; - pixel_builder - .texture_format(texture_format) - .surface_texture_format(texture_format) - .wgpu_backend(pixels::wgpu::Backends::GL) - }; + info!( + "Graphics: window size: {}x{}", + window_size.width, window_size.height + ); - pixel_builder.build_async().await? + let surface_texture = SurfaceTexture::new(REAL_WIDTH, REAL_HEIGHT, Arc::clone(&window)); + + let pixel_builder = PixelsBuilder::new(REAL_WIDTH, REAL_HEIGHT, surface_texture); + #[cfg(target_arch = "wasm32")] + let pixel_builder = { + // Web targets do not support the default texture format + let texture_format = pixels::wgpu::TextureFormat::Rgba8Unorm; + pixel_builder + .texture_format(texture_format) + .surface_texture_format(texture_format) + .wgpu_backend(pixels::wgpu::Backends::GL) }; + let mut pixels = pixel_builder.build_async().await?; + info!("Graphics: pixels created"); + // Use the fill scaling mode which supports non-integer scaling. pixels.set_scaling_mode(pixels::ScalingMode::Fill); - let terminal = Terminal::new(pixels, sender); - - let res = game_loop( - event_loop, - window, - terminal, - FPS, - 0.1, - move |g| { - // Update the world - if !g.game.paused { - step(); - } - }, - move |g| { - // Drawing - // g.game.world.draw(g.game.pixels.frame_mut()); - render(g.game.pixels.frame_mut()); - if let Err(err) = g.game.pixels.render() { - error!("pixels.render: {err}"); - g.exit(); - } - - // Sleep the main thread to limit drawing to the fixed time step. - // See: https://github.com/parasyte/pixels/issues/174 - #[cfg(not(target_arch = "wasm32"))] - { - let dt = TIME_STEP.as_secs_f64() - Time::now().sub(&g.current_instant()); - if dt > 0.0 { - std::thread::sleep(Duration::from_secs_f64(dt)); - } - } - }, - |g, event| { - // Let winit_input_helper collect events to build its state. - if g.game.input.update(event) { - // Update controls - g.game.update_controls(); - - // Close events - if g.game.input.close_requested() { - g.exit(); - return; - } - - // Resize the window - if let Some(size) = g.game.input.window_resized() { - // window_resized() returns physical size, but clamp to reasonable maximum - // texture size (most GPUs support up to 16384, but we'll use 4096 to be safe) - const MAX_TEXTURE_SIZE: u32 = 4096; - let width = size.width.min(MAX_TEXTURE_SIZE); - let height = size.height.min(MAX_TEXTURE_SIZE); - if let Err(err) = g.game.pixels.resize_surface(width, height) { - error!("pixels.resize_surface: {err}"); - g.exit(); - } - } - } - }, - ); - res.map_err(|e| Error::UserDefined(Box::new(e))) + Ok(pixels) } diff --git a/src/host/wgpu/policy.rs b/src/host/wgpu/policy.rs new file mode 100644 index 0000000..0411750 --- /dev/null +++ b/src/host/wgpu/policy.rs @@ -0,0 +1,143 @@ +#[cfg(not(target_arch = "wasm32"))] +use std::time::Instant; +#[cfg(target_arch = "wasm32")] +use web_time::Instant; + +use std::time::Duration; +use winit::event_loop::ControlFlow; + +const UPDATE_FPS: u32 = 60; +const RENDER_FPS: u32 = 30; +const IDLE_RENDER_FPS: u32 = 2; +const UPDATE_STEP: Duration = Duration::from_micros(1_000_000 / UPDATE_FPS as u64); +const RENDER_STEP: Duration = Duration::from_micros(1_000_000 / RENDER_FPS as u64); +const IDLE_RENDER_STEP: Duration = Duration::from_micros(1_000_000 / IDLE_RENDER_FPS as u64); + +/// What to do at end-of-cycle (`about_to_wait`) +#[derive(Debug, Clone)] +pub struct IdlePlan { + /// Set this via `event_loop.set_control_flow(...)` + pub control_flow: ControlFlow, + /// If true, call `window.request_redraw()` + pub request_redraw: bool, +} + +pub struct FramePolicy { + // Fixed update rate (30Hz) + update_step: Duration, + next_update: Instant, + + // Render throttling + last_present: Instant, + max_render_interval: Duration, // 30fps cap when active + + dirty: bool, + + pub will_redraw: bool, + pub updates_to_run: u32, + + idle_render_enabled: bool, +} + +impl FramePolicy { + pub fn new() -> Self { + let now = Instant::now(); + + let update_step = UPDATE_STEP; + let max_render_interval = RENDER_STEP; + + Self { + update_step, + next_update: now + update_step, + + last_present: now - max_render_interval, + max_render_interval, + + dirty: true, + will_redraw: false, + updates_to_run: 0, + + idle_render_enabled: false, + } + } + + /// Call on resume/start to avoid huge catch-up steps. + pub fn reset(&mut self) { + let now = Instant::now(); + self.next_update = now + self.update_step; + self.last_present = now - self.max_render_interval; + } + + /// Turn idle rendering on/off (hook up later to inactivity detection). + pub fn set_idle_render_enabled(&mut self, enabled: bool) { + self.idle_render_enabled = enabled; + if enabled { + self.dirty = true; // ensure we eventually show something + } + } + + /// Call from `about_to_wait` (or once per cycle) to advance purely-time-driven state. + /// This may mark the frame dirty (e.g., blink toggles). + pub fn advance_timers(&mut self) { + // let now = Instant::now(); + + // self.mark_dirty(); + } + + /// Call from `about_to_wait` (or `new_events`) to decide how many 30Hz updates to run. + /// You should run updates first, then call `plan_idle(...)` to decide redraw + sleeping. + pub fn plan_tick(&mut self, cause: winit::event::StartCause) { + let now = Instant::now(); + let mut updates = 0; + + while now >= self.next_update { + updates += 1; + self.next_update += self.update_step; + + // prevent spiral-of-death; clamp catch-up + if updates >= 5 { + self.next_update = now + self.update_step; + break; + } + } + + if updates > 0 { + self.dirty = true; + } + self.updates_to_run = updates; + } + + /// Call from `about_to_wait`. + pub fn plan_idle(&mut self) -> IdlePlan { + let now = Instant::now(); + + let render_due = now.duration_since(self.last_present) >= self.max_render_interval; + + // Active render: only if dirty and due. + let mut request_redraw = self.dirty && render_due; + + // Sleep until the next "interesting" deadline + let mut next_deadline = self.next_update; + + IdlePlan { + control_flow: ControlFlow::WaitUntil(next_deadline), + request_redraw, + } + } + + /// Call after a successful draw/present (from `WindowEvent::RedrawRequested`). + pub fn on_presented(&mut self) { + self.last_present = Instant::now(); + self.dirty = false; + self.will_redraw = false; + } + + /// Call if your render failed in a retryable way; keeps dirty set so we retry. + pub fn on_present_failed_retry(&mut self) { + self.dirty = true; + } + + pub fn on_request_redraw(&mut self) { + self.will_redraw = true; + } +} diff --git a/src/machine/generic/color.rs b/src/machine/generic/color.rs index a57dfa4..00a351e 100644 --- a/src/machine/generic/color.rs +++ b/src/machine/generic/color.rs @@ -5,6 +5,12 @@ pub struct Color { } pub const DEFAULT_COLOR: Color = Color { + background: (10, 10, 10), + foreground: (207, 159, 64), + bold: (249, 218, 76), +}; + +pub const GRAYSCALE_COLOR: Color = Color { background: (10, 10, 10), foreground: (80, 80, 80), bold: (224, 224, 224), From 9cca24427d6373db4e4168c2ebd6607b530da15d Mon Sep 17 00:00:00 2001 From: Matt Mastracci Date: Sat, 20 Dec 2025 18:09:15 -0700 Subject: [PATCH 2/3] cleanup --- src/host/wgpu/mod.rs | 2 +- src/host/wgpu/policy.rs | 17 ++--------------- 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/src/host/wgpu/mod.rs b/src/host/wgpu/mod.rs index d7046a0..ef85ebf 100644 --- a/src/host/wgpu/mod.rs +++ b/src/host/wgpu/mod.rs @@ -3,13 +3,13 @@ pub const REAL_WIDTH: u32 = 800; pub const REAL_HEIGHT: u32 = 416; +// TODO: Waiting on pixels to support non-square aspect ratios pub const ASPECT_RATIO: f64 = 4.0 / 3.0; pub const WINDOW_WIDTH: u32 = REAL_WIDTH as u32; pub const WINDOW_HEIGHT: u32 = (REAL_WIDTH as f64 / ASPECT_RATIO as f64) as u32; use pixels::{Error, Pixels, PixelsBuilder, SurfaceTexture}; use std::sync::Arc; -use std::time::Duration; use winit::application::ApplicationHandler; use winit::dpi; use winit::event::WindowEvent; diff --git a/src/host/wgpu/policy.rs b/src/host/wgpu/policy.rs index 0411750..45718e8 100644 --- a/src/host/wgpu/policy.rs +++ b/src/host/wgpu/policy.rs @@ -76,16 +76,6 @@ impl FramePolicy { } } - /// Call from `about_to_wait` (or once per cycle) to advance purely-time-driven state. - /// This may mark the frame dirty (e.g., blink toggles). - pub fn advance_timers(&mut self) { - // let now = Instant::now(); - - // self.mark_dirty(); - } - - /// Call from `about_to_wait` (or `new_events`) to decide how many 30Hz updates to run. - /// You should run updates first, then call `plan_idle(...)` to decide redraw + sleeping. pub fn plan_tick(&mut self, cause: winit::event::StartCause) { let now = Instant::now(); let mut updates = 0; @@ -107,17 +97,16 @@ impl FramePolicy { self.updates_to_run = updates; } - /// Call from `about_to_wait`. pub fn plan_idle(&mut self) -> IdlePlan { let now = Instant::now(); let render_due = now.duration_since(self.last_present) >= self.max_render_interval; // Active render: only if dirty and due. - let mut request_redraw = self.dirty && render_due; + let request_redraw = self.dirty && render_due; // Sleep until the next "interesting" deadline - let mut next_deadline = self.next_update; + let next_deadline = self.next_update; IdlePlan { control_flow: ControlFlow::WaitUntil(next_deadline), @@ -125,14 +114,12 @@ impl FramePolicy { } } - /// Call after a successful draw/present (from `WindowEvent::RedrawRequested`). pub fn on_presented(&mut self) { self.last_present = Instant::now(); self.dirty = false; self.will_redraw = false; } - /// Call if your render failed in a retryable way; keeps dirty set so we retry. pub fn on_present_failed_retry(&mut self) { self.dirty = true; } From 19e3b1e13bb47df618ebce9a839fd05459f43904 Mon Sep 17 00:00:00 2001 From: Matt Mastracci Date: Sat, 20 Dec 2025 18:25:05 -0700 Subject: [PATCH 3/3] Fix for Chrome crash --- examples/wasm/termtris/index.js | 1 - src/host/wgpu/mod.rs | 17 ----------------- 2 files changed, 18 deletions(-) diff --git a/examples/wasm/termtris/index.js b/examples/wasm/termtris/index.js index 9d603aa..27c1946 100644 --- a/examples/wasm/termtris/index.js +++ b/examples/wasm/termtris/index.js @@ -26,7 +26,6 @@ class Termtris { this._nextTime = this._wasmInstance.exports._update(this._approxTime) + this._approxTime; } else { this._missedTicks++; - console.log("missed tick", this._missedTicks); } } } diff --git a/src/host/wgpu/mod.rs b/src/host/wgpu/mod.rs index ef85ebf..8bbe273 100644 --- a/src/host/wgpu/mod.rs +++ b/src/host/wgpu/mod.rs @@ -394,23 +394,6 @@ pub async fn attach_canvas(window: &Arc) { // Trigger initial resize event let _ = window.request_inner_size(get_window_size()); - - let promise = Promise::new(&mut |resolve, _reject| { - let closure = Closure::wrap(Box::new(move |_timestamp: f64| { - resolve.call0(&wasm_bindgen::JsValue::UNDEFINED).unwrap(); - }) as Box); - web_sys::window() - .unwrap() - .request_animation_frame(closure.as_ref().unchecked_ref()) - .unwrap(); - closure.forget(); - }); - - // Yield to the event loop until the animation frame callback has been called - // This ensures Chrome has rendered the canvas before WebGPU initialization - JsFuture::from(promise).await.ok(); - - info!("Graphics: canvas attached"); } async fn create_pixels(window: Arc) -> Result, Error> {