refactor: stream simulation state over WebSockets
This commit is contained in:
parent
e4a4cb0190
commit
ef47209c79
|
|
@ -40,12 +40,6 @@ version = "1.0.104"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
|
checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ascii"
|
|
||||||
version = "1.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-lock"
|
name = "async-lock"
|
||||||
version = "3.4.2"
|
version = "3.4.2"
|
||||||
|
|
@ -74,6 +68,12 @@ dependencies = [
|
||||||
"syn 3.0.6",
|
"syn 3.0.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "atomic-waker"
|
||||||
|
version = "1.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "attribute-derive"
|
name = "attribute-derive"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
|
|
@ -109,6 +109,61 @@ version = "1.5.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "axum"
|
||||||
|
version = "0.8.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90"
|
||||||
|
dependencies = [
|
||||||
|
"axum-core",
|
||||||
|
"base64",
|
||||||
|
"bytes",
|
||||||
|
"form_urlencoded",
|
||||||
|
"futures-util",
|
||||||
|
"http",
|
||||||
|
"http-body",
|
||||||
|
"http-body-util",
|
||||||
|
"hyper",
|
||||||
|
"hyper-util",
|
||||||
|
"itoa",
|
||||||
|
"matchit",
|
||||||
|
"memchr",
|
||||||
|
"mime",
|
||||||
|
"percent-encoding",
|
||||||
|
"pin-project-lite",
|
||||||
|
"serde_core",
|
||||||
|
"serde_json",
|
||||||
|
"serde_path_to_error",
|
||||||
|
"serde_urlencoded",
|
||||||
|
"sha1",
|
||||||
|
"sync_wrapper",
|
||||||
|
"tokio",
|
||||||
|
"tokio-tungstenite",
|
||||||
|
"tower",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "axum-core"
|
||||||
|
version = "0.5.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"futures-core",
|
||||||
|
"http",
|
||||||
|
"http-body",
|
||||||
|
"http-body-util",
|
||||||
|
"mime",
|
||||||
|
"pin-project-lite",
|
||||||
|
"sync_wrapper",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base16"
|
name = "base16"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
|
|
@ -121,6 +176,12 @@ version = "0.22.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "2.13.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block-buffer"
|
name = "block-buffer"
|
||||||
version = "0.10.4"
|
version = "0.10.4"
|
||||||
|
|
@ -164,12 +225,6 @@ version = "1.0.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
|
checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "chunked_transfer"
|
|
||||||
version = "1.5.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "codee"
|
name = "codee"
|
||||||
version = "0.3.5"
|
version = "0.3.5"
|
||||||
|
|
@ -305,6 +360,12 @@ dependencies = [
|
||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "data-encoding"
|
||||||
|
version = "2.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive-where"
|
name = "derive-where"
|
||||||
version = "1.7.0"
|
version = "1.7.0"
|
||||||
|
|
@ -519,6 +580,18 @@ dependencies = [
|
||||||
"version_check",
|
"version_check",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.3.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"r-efi 5.3.0",
|
||||||
|
"wasip2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.4.3"
|
version = "0.4.3"
|
||||||
|
|
@ -528,7 +601,7 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"libc",
|
"libc",
|
||||||
"r-efi",
|
"r-efi 6.0.0",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -606,6 +679,41 @@ dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "http-body"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"http",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "http-body-util"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"futures-core",
|
||||||
|
"http",
|
||||||
|
"http-body",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "http-range-header"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "httparse"
|
||||||
|
version = "1.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "httpdate"
|
name = "httpdate"
|
||||||
version = "1.0.3"
|
version = "1.0.3"
|
||||||
|
|
@ -625,6 +733,40 @@ dependencies = [
|
||||||
"throw_error",
|
"throw_error",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hyper"
|
||||||
|
version = "1.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2c3e324da4c95177d6291d4c8730197c0d1822f8a9766814a4a44fa5ab797c9c"
|
||||||
|
dependencies = [
|
||||||
|
"atomic-waker",
|
||||||
|
"bytes",
|
||||||
|
"futures-core",
|
||||||
|
"http",
|
||||||
|
"http-body",
|
||||||
|
"httparse",
|
||||||
|
"httpdate",
|
||||||
|
"itoa",
|
||||||
|
"pin-project-lite",
|
||||||
|
"smallvec",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hyper-util"
|
||||||
|
version = "0.1.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ddc03d96684f9226b8a787cdb71488417b53ab5ea8fdb1dac946cb9431cc8bff"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"http",
|
||||||
|
"http-body",
|
||||||
|
"hyper",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tokio",
|
||||||
|
"tower-service",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "icu_collections"
|
name = "icu_collections"
|
||||||
version = "2.3.0"
|
version = "2.3.0"
|
||||||
|
|
@ -802,7 +944,7 @@ dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"either_of",
|
"either_of",
|
||||||
"futures",
|
"futures",
|
||||||
"getrandom",
|
"getrandom 0.4.3",
|
||||||
"hydration_context",
|
"hydration_context",
|
||||||
"leptos_config",
|
"leptos_config",
|
||||||
"leptos_dom",
|
"leptos_dom",
|
||||||
|
|
@ -984,12 +1126,45 @@ dependencies = [
|
||||||
"regex-automata",
|
"regex-automata",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "matchit"
|
||||||
|
version = "0.8.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.8.3"
|
version = "2.8.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mime"
|
||||||
|
version = "0.3.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mime_guess"
|
||||||
|
version = "2.0.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
|
||||||
|
dependencies = [
|
||||||
|
"mime",
|
||||||
|
"unicase",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mio"
|
||||||
|
version = "1.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1788edb87fdc09c7e26304471e2f5be8cdefb1b6930d6e3985fc02ff53bf86ee"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "musli"
|
name = "musli"
|
||||||
version = "0.0.124"
|
version = "0.0.124"
|
||||||
|
|
@ -1200,6 +1375,15 @@ dependencies = [
|
||||||
"zerovec",
|
"zerovec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ppv-lite86"
|
||||||
|
version = "0.2.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||||
|
dependencies = [
|
||||||
|
"zerocopy",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prettyplease"
|
name = "prettyplease"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
|
|
@ -1273,12 +1457,47 @@ dependencies = [
|
||||||
"syn 3.0.6",
|
"syn 3.0.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "r-efi"
|
||||||
|
version = "5.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "r-efi"
|
name = "r-efi"
|
||||||
version = "6.0.0"
|
version = "6.0.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.9.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41"
|
||||||
|
dependencies = [
|
||||||
|
"rand_chacha",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_chacha"
|
||||||
|
version = "0.9.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||||
|
dependencies = [
|
||||||
|
"ppv-lite86",
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.9.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom 0.3.4",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reactive_graph"
|
name = "reactive_graph"
|
||||||
version = "0.2.15"
|
version = "0.2.15"
|
||||||
|
|
@ -1568,6 +1787,17 @@ dependencies = [
|
||||||
"zmij",
|
"zmij",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_path_to_error"
|
||||||
|
version = "0.1.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"serde",
|
||||||
|
"serde_core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_qs"
|
name = "serde_qs"
|
||||||
version = "0.15.0"
|
version = "0.15.0"
|
||||||
|
|
@ -1588,6 +1818,18 @@ dependencies = [
|
||||||
"serde_core",
|
"serde_core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_urlencoded"
|
||||||
|
version = "0.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
|
||||||
|
dependencies = [
|
||||||
|
"form_urlencoded",
|
||||||
|
"itoa",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "server_fn"
|
name = "server_fn"
|
||||||
version = "0.8.13"
|
version = "0.8.13"
|
||||||
|
|
@ -1646,6 +1888,17 @@ dependencies = [
|
||||||
"syn 3.0.6",
|
"syn 3.0.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sha1"
|
||||||
|
version = "0.10.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a978451301f4db1d02937a4ab3ccce137717b81826e79b7d49ffe3244a13c3b8"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"cpufeatures",
|
||||||
|
"digest",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha2"
|
name = "sha2"
|
||||||
version = "0.10.9"
|
version = "0.10.9"
|
||||||
|
|
@ -1676,8 +1929,10 @@ checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
||||||
name = "sim"
|
name = "sim"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"axum",
|
||||||
"rune",
|
"rune",
|
||||||
"tiny_http",
|
"tokio",
|
||||||
|
"tower-http",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1707,6 +1962,16 @@ version = "1.16.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e"
|
checksum = "f9395f0f0eee849a9b707b2f06bb92a6a422090e2123bb2ef8e87a0e61892a8e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "socket2"
|
||||||
|
version = "0.6.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "stable_deref_trait"
|
name = "stable_deref_trait"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
|
|
@ -1735,6 +2000,12 @@ dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sync_wrapper"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "synstructure"
|
name = "synstructure"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
|
|
@ -1851,18 +2122,6 @@ dependencies = [
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tiny_http"
|
|
||||||
version = "0.12.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82"
|
|
||||||
dependencies = [
|
|
||||||
"ascii",
|
|
||||||
"chunked_transfer",
|
|
||||||
"httpdate",
|
|
||||||
"log",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tinystr"
|
name = "tinystr"
|
||||||
version = "0.8.4"
|
version = "0.8.4"
|
||||||
|
|
@ -1879,7 +2138,49 @@ version = "1.53.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e95f91fcc7a621e8b030f6aa23c71fe9838ae2fb4d8118b75602a328f5144044"
|
checksum = "e95f91fcc7a621e8b030f6aa23c71fe9838ae2fb4d8118b75602a328f5144044"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"libc",
|
||||||
|
"mio",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
|
"socket2",
|
||||||
|
"tokio-macros",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-macros"
|
||||||
|
version = "2.7.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 3.0.6",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-tungstenite"
|
||||||
|
version = "0.29.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f72a05e828585856dacd553fba484c242c46e391fb0e58917c942ee9202915c"
|
||||||
|
dependencies = [
|
||||||
|
"futures-util",
|
||||||
|
"log",
|
||||||
|
"tokio",
|
||||||
|
"tungstenite",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-util"
|
||||||
|
version = "0.7.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"futures-core",
|
||||||
|
"futures-sink",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1921,12 +2222,66 @@ version = "1.1.2+spec-1.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2"
|
checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower"
|
||||||
|
version = "0.5.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-util",
|
||||||
|
"pin-project-lite",
|
||||||
|
"sync_wrapper",
|
||||||
|
"tokio",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower-http"
|
||||||
|
version = "0.6.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"bytes",
|
||||||
|
"futures-core",
|
||||||
|
"futures-util",
|
||||||
|
"http",
|
||||||
|
"http-body",
|
||||||
|
"http-body-util",
|
||||||
|
"http-range-header",
|
||||||
|
"httpdate",
|
||||||
|
"mime",
|
||||||
|
"mime_guess",
|
||||||
|
"percent-encoding",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower-layer"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower-service"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing"
|
name = "tracing"
|
||||||
version = "0.1.44"
|
version = "0.1.44"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
|
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"log",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"tracing-core",
|
"tracing-core",
|
||||||
]
|
]
|
||||||
|
|
@ -1970,6 +2325,22 @@ dependencies = [
|
||||||
"tracing-log",
|
"tracing-log",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tungstenite"
|
||||||
|
version = "0.29.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6c01152af293afb9c7c2a57e4b559c5620b421f6d133261c60dd2d0cdb38e6b8"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"data-encoding",
|
||||||
|
"http",
|
||||||
|
"httparse",
|
||||||
|
"log",
|
||||||
|
"rand",
|
||||||
|
"sha1",
|
||||||
|
"thiserror 2.0.21",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "twox-hash"
|
name = "twox-hash"
|
||||||
version = "2.1.5"
|
version = "2.1.5"
|
||||||
|
|
@ -2012,11 +2383,18 @@ dependencies = [
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"leptos",
|
"leptos",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"wasm-bindgen-futures",
|
"wasm-bindgen-futures",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicase"
|
||||||
|
version = "2.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "357cc3acc6a036009fd6c973ed009037c732d60d0b4f6c673e9041497482a28f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.26"
|
version = "1.0.26"
|
||||||
|
|
@ -2065,7 +2443,7 @@ version = "1.27.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "97277d36b9c3ace13e58fa6e753f8b0bbbf302a18dd193240d70f9e29681059a"
|
checksum = "97277d36b9c3ace13e58fa6e753f8b0bbbf302a18dd193240d70f9e29681059a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"getrandom",
|
"getrandom 0.4.3",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
@ -2092,6 +2470,21 @@ dependencies = [
|
||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.11.1+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasip2"
|
||||||
|
version = "1.0.4+wasi-0.2.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487"
|
||||||
|
dependencies = [
|
||||||
|
"wit-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.129"
|
version = "0.2.129"
|
||||||
|
|
@ -2235,6 +2628,12 @@ dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wit-bindgen"
|
||||||
|
version = "0.57.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "writeable"
|
name = "writeable"
|
||||||
version = "0.6.4"
|
version = "0.6.4"
|
||||||
|
|
|
||||||
|
|
@ -8,4 +8,6 @@ members = ["ui"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rune = "0.14.2"
|
rune = "0.14.2"
|
||||||
tiny_http = "0.12"
|
axum = { version = "0.8", features = ["ws"] }
|
||||||
|
tokio = { version = "1", features = ["rt-multi-thread", "macros", "net", "sync", "time"] }
|
||||||
|
tower-http = { version = "0.6", features = ["fs"] }
|
||||||
|
|
|
||||||
301
src/main.rs
301
src/main.rs
|
|
@ -1,12 +1,16 @@
|
||||||
mod world;
|
mod world;
|
||||||
|
|
||||||
use std::io::Read;
|
use axum::Router;
|
||||||
use std::path::Path;
|
use axum::extract::ws::{Message, WebSocket, WebSocketUpgrade};
|
||||||
use std::sync::mpsc::{self, Sender};
|
use axum::extract::{DefaultBodyLimit, Request, State};
|
||||||
|
use axum::http::{StatusCode, header};
|
||||||
|
use axum::middleware::{self, Next};
|
||||||
|
use axum::response::{Html, IntoResponse, Response};
|
||||||
|
use axum::routing::{get, post};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::thread;
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tiny_http::{Header, Method, Request, Response, Server};
|
use tokio::sync::{mpsc, oneshot, watch};
|
||||||
|
use tower_http::services::ServeDir;
|
||||||
use world::{DEFAULT_SCRIPT, World};
|
use world::{DEFAULT_SCRIPT, World};
|
||||||
|
|
||||||
// Minimal shell; the whole UI is the Leptos app in ui/ compiled to wasm.
|
// Minimal shell; the whole UI is the Leptos app in ui/ compiled to wasm.
|
||||||
|
|
@ -17,144 +21,181 @@ try { const m = await import('/pkg/ui.js'); await m.default(); }
|
||||||
catch (e) { document.body.textContent = 'Failed to load the UI (did you run ui/build.sh?): ' + e; }
|
catch (e) { document.body.textContent = 'Failed to load the UI (did you run ui/build.sh?): ' + e; }
|
||||||
</script></body></html>"#;
|
</script></body></html>"#;
|
||||||
|
|
||||||
fn serve_static(req: Request, dir: &Path, rel: &str) {
|
enum Msg {
|
||||||
let rel = rel.split('?').next().unwrap_or("");
|
Edit(String),
|
||||||
let safe = !rel.is_empty()
|
Script(String, oneshot::Sender<Result<(), String>>),
|
||||||
&& rel
|
}
|
||||||
.split('/')
|
|
||||||
.all(|c| !c.is_empty() && c != ".." && c != "." && !c.contains('\\'));
|
#[derive(Clone)]
|
||||||
let ctype = match rel.rsplit('.').next() {
|
struct App {
|
||||||
Some("js") => "application/javascript",
|
terrain: Arc<Mutex<String>>,
|
||||||
Some("wasm") => "application/wasm",
|
script: Arc<Mutex<String>>,
|
||||||
Some("css") => "text/css",
|
state: watch::Receiver<Arc<str>>,
|
||||||
_ => "application/octet-stream",
|
tx: mpsc::UnboundedSender<Msg>,
|
||||||
};
|
}
|
||||||
match safe
|
|
||||||
.then(|| std::fs::read(dir.join(rel)))
|
/// The Rune VM is not Send, so the world lives on this thread and publishes JSON snapshots.
|
||||||
.and_then(Result::ok)
|
fn run_sim(
|
||||||
{
|
seed: u64,
|
||||||
Some(bytes) => {
|
mut rx: mpsc::UnboundedReceiver<Msg>,
|
||||||
let h = Header::from_bytes("Content-Type", ctype).unwrap();
|
terrain: Arc<Mutex<String>>,
|
||||||
let _ = req.respond(Response::from_data(bytes).with_header(h));
|
script: Arc<Mutex<String>>,
|
||||||
|
state_tx: watch::Sender<Arc<str>>,
|
||||||
|
) {
|
||||||
|
let mut world = World::new(seed);
|
||||||
|
let mut rev = u32::MAX;
|
||||||
|
loop {
|
||||||
|
while let Ok(msg) = rx.try_recv() {
|
||||||
|
match msg {
|
||||||
|
Msg::Edit(cmd) => world.apply(&cmd),
|
||||||
|
Msg::Script(src, reply) => {
|
||||||
|
let r = world.set_script(&src);
|
||||||
|
if r.is_ok() {
|
||||||
|
*script.lock().unwrap() = src;
|
||||||
|
}
|
||||||
|
let _ = reply.send(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
None => {
|
if !world.paused {
|
||||||
let _ = req.respond(
|
for _ in 0..world.speed {
|
||||||
Response::from_string("not found (build the UI: see ui/build.sh)")
|
world.step();
|
||||||
.with_status_code(404),
|
}
|
||||||
);
|
}
|
||||||
|
if world.terrain_rev != rev {
|
||||||
|
rev = world.terrain_rev;
|
||||||
|
*terrain.lock().unwrap() = world.terrain_json();
|
||||||
|
}
|
||||||
|
state_tx.send_replace(Arc::from(world.state_json()));
|
||||||
|
std::thread::sleep(Duration::from_millis(100));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Rejects cross-site requests (including WebSocket upgrades), which could otherwise edit the world.
|
||||||
|
async fn same_origin(req: Request, next: Next) -> Response {
|
||||||
|
let h = req.headers();
|
||||||
|
if let Some(origin) = h.get(header::ORIGIN).and_then(|v| v.to_str().ok()) {
|
||||||
|
let host = h
|
||||||
|
.get(header::HOST)
|
||||||
|
.and_then(|v| v.to_str().ok())
|
||||||
|
.unwrap_or("");
|
||||||
|
let authority = origin.split_once("://").map_or(origin, |(_, rest)| rest);
|
||||||
|
if authority != host {
|
||||||
|
return StatusCode::FORBIDDEN.into_response();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next.run(req).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn json(body: String) -> impl IntoResponse {
|
||||||
|
([(header::CONTENT_TYPE, "application/json")], body)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn index() -> Html<&'static str> {
|
||||||
|
Html(INDEX)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn terrain(State(app): State<App>) -> impl IntoResponse {
|
||||||
|
json(app.terrain.lock().unwrap().clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn state(State(app): State<App>) -> impl IntoResponse {
|
||||||
|
json(app.state.borrow().to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_script(State(app): State<App>) -> String {
|
||||||
|
app.script.lock().unwrap().clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn post_script(State(app): State<App>, body: String) -> Response {
|
||||||
|
let (rtx, rrx) = oneshot::channel();
|
||||||
|
let _ = app.tx.send(Msg::Script(body, rtx));
|
||||||
|
match tokio::time::timeout(Duration::from_secs(5), rrx).await {
|
||||||
|
Ok(Ok(Ok(()))) => "ok".into_response(),
|
||||||
|
Ok(Ok(Err(e))) => (StatusCode::BAD_REQUEST, e).into_response(),
|
||||||
|
_ => (StatusCode::SERVICE_UNAVAILABLE, "timed out").into_response(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn post_edit(State(app): State<App>, body: String) -> StatusCode {
|
||||||
|
if body.len() > 256 {
|
||||||
|
return StatusCode::PAYLOAD_TOO_LARGE;
|
||||||
|
}
|
||||||
|
let _ = app.tx.send(Msg::Edit(body));
|
||||||
|
StatusCode::OK
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn ws(upgrade: WebSocketUpgrade, State(app): State<App>) -> Response {
|
||||||
|
upgrade.on_upgrade(move |socket| client(socket, app))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pushes every new snapshot to the client; text frames from the client are edit commands.
|
||||||
|
async fn client(mut socket: WebSocket, app: App) {
|
||||||
|
let mut rx = app.state.clone();
|
||||||
|
loop {
|
||||||
|
let snapshot = rx.borrow_and_update().to_string();
|
||||||
|
if socket.send(Message::Text(snapshot.into())).await.is_err() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
loop {
|
||||||
|
tokio::select! {
|
||||||
|
changed = rx.changed() => {
|
||||||
|
if changed.is_err() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
msg = socket.recv() => match msg {
|
||||||
|
Some(Ok(Message::Text(t))) if t.len() <= 256 => {
|
||||||
|
let _ = app.tx.send(Msg::Edit(t.as_str().to_owned()));
|
||||||
|
}
|
||||||
|
Some(Ok(Message::Close(_))) | Some(Err(_)) | None => return,
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Msg {
|
#[tokio::main]
|
||||||
Edit(String),
|
async fn main() {
|
||||||
Script(String, Sender<Result<(), String>>),
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
let seed = std::env::args()
|
let seed = std::env::args()
|
||||||
.nth(1)
|
.nth(1)
|
||||||
.and_then(|s| s.parse().ok())
|
.and_then(|s| s.parse().ok())
|
||||||
.unwrap_or(42u64);
|
.unwrap_or(42u64);
|
||||||
let addr = std::env::var("SIM_ADDR").unwrap_or_else(|_| "127.0.0.1:7878".into());
|
let addr = std::env::var("SIM_ADDR").unwrap_or_else(|_| "127.0.0.1:7878".into());
|
||||||
// The Rune VM is not Send, so the world lives on the sim thread and publishes JSON snapshots.
|
let ui_dir = std::env::var("SIM_UI_DIR").unwrap_or_else(|_| "ui/pkg".into());
|
||||||
let terrain = Arc::new(Mutex::new(String::new()));
|
|
||||||
let state = Arc::new(Mutex::new(String::new()));
|
let terrain_buf = Arc::new(Mutex::new(String::new()));
|
||||||
let script = Arc::new(Mutex::new(DEFAULT_SCRIPT.to_string()));
|
let script = Arc::new(Mutex::new(DEFAULT_SCRIPT.to_string()));
|
||||||
let (tx, rx) = mpsc::channel::<Msg>();
|
let (tx, rx) = mpsc::unbounded_channel();
|
||||||
|
let (state_tx, state_rx) = watch::channel(Arc::<str>::from("{}"));
|
||||||
{
|
{
|
||||||
let (terrain, state, script) = (
|
let (terrain_buf, script) = (Arc::clone(&terrain_buf), Arc::clone(&script));
|
||||||
Arc::clone(&terrain),
|
std::thread::spawn(move || run_sim(seed, rx, terrain_buf, script, state_tx));
|
||||||
Arc::clone(&state),
|
|
||||||
Arc::clone(&script),
|
|
||||||
);
|
|
||||||
thread::spawn(move || {
|
|
||||||
let mut world = World::new(seed);
|
|
||||||
let mut rev = u32::MAX;
|
|
||||||
loop {
|
|
||||||
while let Ok(msg) = rx.try_recv() {
|
|
||||||
match msg {
|
|
||||||
Msg::Edit(cmd) => world.apply(&cmd),
|
|
||||||
Msg::Script(src, reply) => {
|
|
||||||
let r = world.set_script(&src);
|
|
||||||
if r.is_ok() {
|
|
||||||
*script.lock().unwrap() = src;
|
|
||||||
}
|
|
||||||
let _ = reply.send(r);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !world.paused {
|
|
||||||
for _ in 0..world.speed {
|
|
||||||
world.step();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if world.terrain_rev != rev {
|
|
||||||
rev = world.terrain_rev;
|
|
||||||
*terrain.lock().unwrap() = world.terrain_json();
|
|
||||||
}
|
|
||||||
*state.lock().unwrap() = world.state_json();
|
|
||||||
thread::sleep(Duration::from_millis(100));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let server = Server::http(&addr).expect("failed to bind");
|
let app = App {
|
||||||
let ui_dir = Arc::new(std::path::PathBuf::from(
|
terrain: terrain_buf,
|
||||||
std::env::var("SIM_UI_DIR").unwrap_or_else(|_| "ui/pkg".into()),
|
script,
|
||||||
));
|
state: state_rx,
|
||||||
|
tx,
|
||||||
|
};
|
||||||
|
let router = Router::new()
|
||||||
|
.route("/", get(index))
|
||||||
|
.route("/api/terrain", get(terrain))
|
||||||
|
.route("/api/state", get(state))
|
||||||
|
.route("/api/script", get(get_script).post(post_script))
|
||||||
|
.route("/api/edit", post(post_edit))
|
||||||
|
.route("/ws", get(ws))
|
||||||
|
.nest_service("/pkg", ServeDir::new(ui_dir))
|
||||||
|
.layer(DefaultBodyLimit::max(65536))
|
||||||
|
.layer(middleware::from_fn(same_origin))
|
||||||
|
.with_state(app);
|
||||||
|
|
||||||
|
let listener = tokio::net::TcpListener::bind(&addr)
|
||||||
|
.await
|
||||||
|
.expect("failed to bind");
|
||||||
println!("Simulation running at http://{addr}");
|
println!("Simulation running at http://{addr}");
|
||||||
for mut req in server.incoming_requests() {
|
axum::serve(listener, router).await.unwrap();
|
||||||
let (terrain, state, script) = (
|
|
||||||
Arc::clone(&terrain),
|
|
||||||
Arc::clone(&state),
|
|
||||||
Arc::clone(&script),
|
|
||||||
);
|
|
||||||
let tx = tx.clone();
|
|
||||||
let ui_dir = Arc::clone(&ui_dir);
|
|
||||||
thread::spawn(move || {
|
|
||||||
let post = *req.method() == Method::Post;
|
|
||||||
if !post {
|
|
||||||
if let Some(rel) = req.url().strip_prefix("/pkg/").map(str::to_owned) {
|
|
||||||
return serve_static(req, &ui_dir, &rel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let mut input = String::new();
|
|
||||||
if post {
|
|
||||||
let _ = req.as_reader().take(65536).read_to_string(&mut input);
|
|
||||||
}
|
|
||||||
let (status, body, ctype) = match (post, req.url()) {
|
|
||||||
(false, "/") => (200, INDEX.to_string(), "text/html; charset=utf-8"),
|
|
||||||
(false, "/api/terrain") => {
|
|
||||||
(200, terrain.lock().unwrap().clone(), "application/json")
|
|
||||||
}
|
|
||||||
(false, "/api/state") => (200, state.lock().unwrap().clone(), "application/json"),
|
|
||||||
(false, "/api/script") => (
|
|
||||||
200,
|
|
||||||
script.lock().unwrap().clone(),
|
|
||||||
"text/plain; charset=utf-8",
|
|
||||||
),
|
|
||||||
(true, "/api/edit") if input.len() <= 256 => {
|
|
||||||
let _ = tx.send(Msg::Edit(input));
|
|
||||||
(200, "ok".to_string(), "text/plain")
|
|
||||||
}
|
|
||||||
(true, "/api/script") => {
|
|
||||||
let (rtx, rrx) = mpsc::channel();
|
|
||||||
let _ = tx.send(Msg::Script(input, rtx));
|
|
||||||
match rrx.recv_timeout(Duration::from_secs(5)) {
|
|
||||||
Ok(Ok(())) => (200, "ok".to_string(), "text/plain"),
|
|
||||||
Ok(Err(e)) => (400, e, "text/plain; charset=utf-8"),
|
|
||||||
Err(_) => (503, "timed out".to_string(), "text/plain"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => (404, "not found".to_string(), "text/plain"),
|
|
||||||
};
|
|
||||||
let header = Header::from_bytes("Content-Type", ctype).unwrap();
|
|
||||||
let _ = req.respond(
|
|
||||||
Response::from_string(body)
|
|
||||||
.with_status_code(status)
|
|
||||||
.with_header(header),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,9 @@ leptos = { version = "0.8", features = ["csr"] }
|
||||||
wasm-bindgen = "0.2"
|
wasm-bindgen = "0.2"
|
||||||
wasm-bindgen-futures = "0.4"
|
wasm-bindgen-futures = "0.4"
|
||||||
js-sys = "0.3"
|
js-sys = "0.3"
|
||||||
web-sys = { version = "0.3", features = ["CanvasRenderingContext2d", "HtmlCanvasElement", "HtmlElement", "MouseEvent", "Element", "DomRect", "HtmlDivElement", "Window", "Storage"] }
|
web-sys = { version = "0.3", features = ["CanvasRenderingContext2d", "HtmlCanvasElement", "HtmlElement", "MouseEvent", "WebSocket", "MessageEvent", "Location", "Element", "DomRect", "HtmlDivElement", "Window", "Storage"] }
|
||||||
gloo-net = { version = "0.6", default-features = false, features = ["http", "json"] }
|
gloo-net = { version = "0.6", default-features = false, features = ["http", "json"] }
|
||||||
gloo-timers = { version = "0.3", features = ["futures"] }
|
gloo-timers = { version = "0.3", features = ["futures"] }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
console_error_panic_hook = "0.1"
|
console_error_panic_hook = "0.1"
|
||||||
|
serde_json = "1"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,55 @@
|
||||||
use gloo_net::http::Request;
|
use gloo_net::http::Request;
|
||||||
|
use gloo_timers::future::TimeoutFuture;
|
||||||
use leptos::task::spawn_local;
|
use leptos::task::spawn_local;
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use wasm_bindgen::JsCast;
|
||||||
|
use wasm_bindgen::prelude::Closure;
|
||||||
|
use web_sys::{MessageEvent, WebSocket};
|
||||||
|
|
||||||
|
thread_local! {
|
||||||
|
static SOCKET: RefCell<Option<WebSocket>> = const { RefCell::new(None) };
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Opens the live socket and calls `on_state` with each pushed snapshot; reconnects after a drop.
|
||||||
|
pub fn connect(on_state: impl Fn(String) + 'static) {
|
||||||
|
let Some(loc) = web_sys::window().map(|w| w.location()) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let proto = if loc.protocol().is_ok_and(|p| p == "https:") {
|
||||||
|
"wss"
|
||||||
|
} else {
|
||||||
|
"ws"
|
||||||
|
};
|
||||||
|
let Ok(host) = loc.host() else { return };
|
||||||
|
open(format!("{proto}://{host}/ws"), Rc::new(on_state));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open(url: String, on_state: Rc<dyn Fn(String)>) {
|
||||||
|
let Ok(ws) = WebSocket::new(&url) else { return };
|
||||||
|
let cb = on_state.clone();
|
||||||
|
let onmessage = Closure::<dyn FnMut(MessageEvent)>::new(move |e: MessageEvent| {
|
||||||
|
if let Some(text) = e.data().as_string() {
|
||||||
|
cb(text);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ws.set_onmessage(Some(onmessage.as_ref().unchecked_ref()));
|
||||||
|
onmessage.forget();
|
||||||
|
|
||||||
|
let url2 = url.clone();
|
||||||
|
let onclose = Closure::<dyn FnMut()>::new(move || {
|
||||||
|
SOCKET.with(|s| *s.borrow_mut() = None);
|
||||||
|
let (url, on_state) = (url2.clone(), on_state.clone());
|
||||||
|
spawn_local(async move {
|
||||||
|
TimeoutFuture::new(1000).await;
|
||||||
|
open(url, on_state);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
ws.set_onclose(Some(onclose.as_ref().unchecked_ref()));
|
||||||
|
onclose.forget();
|
||||||
|
SOCKET.with(|s| *s.borrow_mut() = Some(ws));
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn get_json<T: DeserializeOwned>(url: &str) -> Option<T> {
|
pub async fn get_json<T: DeserializeOwned>(url: &str) -> Option<T> {
|
||||||
Request::get(url).send().await.ok()?.json().await.ok()
|
Request::get(url).send().await.ok()?.json().await.ok()
|
||||||
|
|
@ -17,9 +66,17 @@ pub async fn post(url: &str, body: String) -> Option<(bool, String)> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn edit(cmd: String) {
|
pub fn edit(cmd: String) {
|
||||||
spawn_local(async move {
|
let sent = SOCKET.with(|s| {
|
||||||
let _ = post("/api/edit", cmd).await;
|
s.borrow()
|
||||||
|
.as_ref()
|
||||||
|
.filter(|w| w.ready_state() == WebSocket::OPEN)
|
||||||
|
.is_some_and(|w| w.send_with_str(&cmd).is_ok())
|
||||||
});
|
});
|
||||||
|
if !sent {
|
||||||
|
spawn_local(async move {
|
||||||
|
let _ = post("/api/edit", cmd).await;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn storage() -> Option<web_sys::Storage> {
|
fn storage() -> Option<web_sys::Storage> {
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ mod canvas;
|
||||||
mod editor;
|
mod editor;
|
||||||
mod model;
|
mod model;
|
||||||
|
|
||||||
use gloo_timers::future::TimeoutFuture;
|
|
||||||
use leptos::mount::mount_to_body;
|
use leptos::mount::mount_to_body;
|
||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
use leptos::task::spawn_local;
|
use leptos::task::spawn_local;
|
||||||
|
|
@ -28,20 +27,22 @@ fn App() -> impl IntoView {
|
||||||
// (selection counter, people at the time of selection); the counter forces editor rows to rebuild.
|
// (selection counter, people at the time of selection); the counter forces editor rows to rebuild.
|
||||||
let editing = RwSignal::new((0u64, Vec::<Person>::new()));
|
let editing = RwSignal::new((0u64, Vec::<Person>::new()));
|
||||||
|
|
||||||
spawn_local(async move {
|
// The server pushes a snapshot per tick; terrain is refetched only when its revision changes.
|
||||||
let mut rev = None;
|
let last_rev = StoredValue::new(None::<u32>);
|
||||||
loop {
|
api::connect(move |text| {
|
||||||
if let Some(s) = api::get_json::<State>("/api/state").await {
|
let Ok(s) = serde_json::from_str::<State>(&text) else {
|
||||||
if rev != Some(s.rev) || terrain.with_untracked(|t| t.is_none()) {
|
return;
|
||||||
if let Some(t) = api::get_json::<Terrain>("/api/terrain").await {
|
};
|
||||||
terrain.set(Some(t));
|
if last_rev.get_value() != Some(s.rev) {
|
||||||
rev = Some(s.rev);
|
last_rev.set_value(Some(s.rev));
|
||||||
}
|
spawn_local(async move {
|
||||||
|
match api::get_json::<Terrain>("/api/terrain").await {
|
||||||
|
Some(t) => terrain.set(Some(t)),
|
||||||
|
None => last_rev.set_value(None),
|
||||||
}
|
}
|
||||||
state.set(Some(s));
|
});
|
||||||
}
|
|
||||||
TimeoutFuture::new(200).await;
|
|
||||||
}
|
}
|
||||||
|
state.set(Some(s));
|
||||||
});
|
});
|
||||||
|
|
||||||
let canvas = NodeRef::<leptos::html::Canvas>::new();
|
let canvas = NodeRef::<leptos::html::Canvas>::new();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue