Signal-System mit Inky und KV-Store
This commit is contained in:
Generated
+582
@@ -0,0 +1,582 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.102"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c"
|
||||
|
||||
[[package]]
|
||||
name = "as-any"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0f477b951e452a0b6b4a10b53ccd569042d1d01729b519e02074a9c0958a063"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
|
||||
|
||||
[[package]]
|
||||
name = "bladeink"
|
||||
version = "1.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29b7852038d956c822e9f9091cf8aa14954d54056c1fb242e9ee5e6a0882fbce"
|
||||
dependencies = [
|
||||
"as-any",
|
||||
"getrandom",
|
||||
"rand",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.20.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||
|
||||
[[package]]
|
||||
name = "chacha20"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"rand_core",
|
||||
"wasip2",
|
||||
"wasip3",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||
dependencies = [
|
||||
"foldhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||
|
||||
[[package]]
|
||||
name = "id-arena"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954"
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.17.1",
|
||||
"serde",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2025f20d7a4fa7785846e7b63d10a76d3f1cee98ee5cb79ea59703f95e42162"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"futures-util",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "leb128fmt"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.186"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||
|
||||
[[package]]
|
||||
name = "prettyplease"
|
||||
version = "0.2.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.106"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207"
|
||||
dependencies = [
|
||||
"chacha20",
|
||||
"getrandom",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69"
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
|
||||
dependencies = [
|
||||
"serde_core",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_core"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.228"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.150"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"memchr",
|
||||
"serde",
|
||||
"serde_core",
|
||||
"zmij",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9628de9b8791db39ceda2b119bbe13134770b56c138ec1d3af810d045c04f9bd"
|
||||
dependencies = [
|
||||
"strum_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab85eea0270ee17587ed4156089e10b9e6880ee688791d45a905f5b1ca36f664"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.117"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
|
||||
|
||||
[[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 0.57.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasip3"
|
||||
version = "0.4.0+wasi-0.3.0-rc-2026-01-06"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5"
|
||||
dependencies = [
|
||||
"wit-bindgen 0.51.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.123"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a254a4b10c19a76f09a27640e7ffbf9bc30bf67e16a3bf28aaefa4920fe81563"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
"rustversion",
|
||||
"wasm-bindgen-macro",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.123"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24a40fc75b0ec6f3746ceb10d36f53a93dcd68a93b11b6445983945d79eba0dc"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.123"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "908f34bd9b9ce3d4caf07b72dfab63d61504d156856c6bd3cd87fa350cf3985b"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.123"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7acbf7616c27b194bbb550bf77ed0c2c3e5b7fd1260a93082b95fb7f47959b92"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-encoder"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319"
|
||||
dependencies = [
|
||||
"leb128fmt",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-metadata"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"indexmap",
|
||||
"wasm-encoder",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasmparser"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"hashbrown 0.15.5",
|
||||
"indexmap",
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wds"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bladeink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "web-time"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
|
||||
dependencies = [
|
||||
"wit-bindgen-rust-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.57.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-core"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck",
|
||||
"wit-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"heck",
|
||||
"indexmap",
|
||||
"prettyplease",
|
||||
"syn",
|
||||
"wasm-metadata",
|
||||
"wit-bindgen-core",
|
||||
"wit-component",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rust-macro"
|
||||
version = "0.51.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"prettyplease",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wit-bindgen-core",
|
||||
"wit-bindgen-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-component"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags",
|
||||
"indexmap",
|
||||
"log",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"wasm-encoder",
|
||||
"wasm-metadata",
|
||||
"wasmparser",
|
||||
"wit-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-parser"
|
||||
version = "0.244.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"id-arena",
|
||||
"indexmap",
|
||||
"log",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"unicode-xid",
|
||||
"wasmparser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zmij"
|
||||
version = "1.0.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
|
||||
@@ -4,3 +4,4 @@ version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
bladeink = "1.2.5"
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
VAR cube_clicks = 0
|
||||
|
||||
Du hast den Würfel {cube_clicks} mal berührt.
|
||||
|
||||
{ cube_clicks >= 3:
|
||||
Er fühlt sich langsam abgenutzt an.
|
||||
}
|
||||
-> END
|
||||
@@ -0,0 +1 @@
|
||||
{"inkVersion":21,"root":[["^Du hast den Würfel ","ev",{"VAR?":"cube_clicks"},"out","/ev","^ mal berührt.","\n","ev",{"VAR?":"cube_clicks"},3,">=","/ev",[{"->":".^.b","c":true},{"b":["\n","^Er fühlt sich langsam abgenutzt an.","\n",{"->":"0.13"},null]}],"nop","\n","end",["done",{"#n":"g-0"}],null],"done",{"global decl":["ev",0,{"VAR=":"cube_clicks"},"/ev","end",null]}],"listDefs":{}}
|
||||
@@ -0,0 +1,46 @@
|
||||
VAR mushrooms_collected = 0
|
||||
VAR quest_mushroom_active = false
|
||||
VAR quest_mushroom_done = false
|
||||
|
||||
{
|
||||
- quest_mushroom_done:
|
||||
# color whisper
|
||||
Danke nochmal für die Pilze. Das Rezept ist fast fertig.
|
||||
-> ende
|
||||
|
||||
- quest_mushroom_active && mushrooms_collected >= 10:
|
||||
# color whisper
|
||||
Du hast {mushrooms_collected} Pilze dabei — mehr als genug!
|
||||
* [Hier, nimm sie.]
|
||||
~ quest_mushroom_done = true
|
||||
# color whisper
|
||||
Wunderbar, vielen Dank! Das wird ein gutes Süppchen.
|
||||
-> ende
|
||||
* [Lass mich noch ein paar behalten.]
|
||||
# color whisper
|
||||
Wie du willst. Komm wieder, wenn du sie loswerden möchtest.
|
||||
-> ende
|
||||
|
||||
- quest_mushroom_active:
|
||||
# color whisper
|
||||
Du hast schon {mushrooms_collected} von 10 Pilzen. Mach ruhig weiter.
|
||||
-> ende
|
||||
|
||||
- else:
|
||||
# color whisper
|
||||
Hallo! Ich bräuchte 10 Pilze für ein Rezept. Hättest du Lust, welche zu sammeln?
|
||||
* [Klar, ich helfe.]
|
||||
~ quest_mushroom_active = true
|
||||
# color whisper
|
||||
Danke! Ich warte hier.
|
||||
-> ende
|
||||
* [Vielleicht später.]
|
||||
# color whisper
|
||||
Schade. Komm gerne nochmal vorbei.
|
||||
-> ende
|
||||
}
|
||||
|
||||
= ende
|
||||
* [Bis dann]
|
||||
# person_farewell
|
||||
-> END
|
||||
@@ -0,0 +1 @@
|
||||
{"inkVersion":21,"root":[[["ev",{"VAR?":"quest_mushroom_done"},"/ev",{"->":".^.b","c":true},{"b":["\n","#","^color whisper","/#","^Danke nochmal für die Pilze. Das Rezept ist fast fertig.","\n",{"->":"ende"},{"->":"0.4"},null]}],["ev",{"VAR?":"quest_mushroom_active"},{"VAR?":"mushrooms_collected"},10,">=","&&","/ev",{"->":".^.b","c":true},{"b":["\n","#","^color whisper","/#","^Du hast ","ev",{"VAR?":"mushrooms_collected"},"out","/ev","^ Pilze dabei — mehr als genug!","\n","ev","str","^Hier, nimm sie.","/str","/ev",{"*":".^.c-0","flg":20},"ev","str","^Lass mich noch ein paar behalten.","/str","/ev",{"*":".^.c-1","flg":20},{"->":"0.4"},{"c-0":["\n","ev",true,"/ev",{"VAR=":"quest_mushroom_done","re":true},"#","^color whisper","/#","^Wunderbar, vielen Dank! Das wird ein gutes Süppchen.","\n",{"->":"ende"},{"#f":5}],"c-1":["\n","#","^color whisper","/#","^Wie du willst. Komm wieder, wenn du sie loswerden möchtest.","\n",{"->":"ende"},{"#f":5}]}]}],["ev",{"VAR?":"quest_mushroom_active"},"/ev",{"->":".^.b","c":true},{"b":["\n","#","^color whisper","/#","^Du hast schon ","ev",{"VAR?":"mushrooms_collected"},"out","/ev","^ von 10 Pilzen. Mach ruhig weiter.","\n",{"->":"ende"},{"->":"0.4"},null]}],[{"->":".^.b"},{"b":["\n","#","^color whisper","/#","^Hallo! Ich bräuchte 10 Pilze für ein Rezept. Hättest du Lust, welche zu sammeln?","\n","ev","str","^Klar, ich helfe.","/str","/ev",{"*":".^.c-0","flg":20},"ev","str","^Vielleicht später.","/str","/ev",{"*":".^.c-1","flg":20},{"->":"0.4"},{"c-0":["\n","ev",true,"/ev",{"VAR=":"quest_mushroom_active","re":true},"#","^color whisper","/#","^Danke! Ich warte hier.","\n",{"->":"ende"},{"#f":5}],"c-1":["\n","#","^color whisper","/#","^Schade. Komm gerne nochmal vorbei.","\n",{"->":"ende"},{"#f":5}]}]}],"nop","\n",["done",{"#n":"g-0"}],null],"done",{"ende":[["ev","str","^Bis dann","/str","/ev",{"*":".^.c-0","flg":20},{"c-0":["\n","#","^person_farewell","/#","end",{"#f":5}]}],null],"global decl":["ev",0,{"VAR=":"mushrooms_collected"},false,{"VAR=":"quest_mushroom_active"},false,{"VAR=":"quest_mushroom_done"},"/ev","end",null]}],"listDefs":{}}
|
||||
@@ -0,0 +1,83 @@
|
||||
# color suzanne
|
||||
Hallo! Ich bin Suzanne, der Affe von Blender.
|
||||
Möchtest du wissen, wie diese Engine funktioniert?
|
||||
* [Klar, leg los]
|
||||
-> menu
|
||||
* [Nein, danke.]
|
||||
-> ende
|
||||
|
||||
=== menu ===
|
||||
# color suzanne
|
||||
Worüber soll ich plaudern?
|
||||
* [Ink by Inkle] -> story
|
||||
* [Renderer & Pixel] -> render
|
||||
* [Welt & Physik] -> welt
|
||||
* [Interaktion & Picking] -> interakt
|
||||
* [Audio] -> audio
|
||||
* [Warum so wenig Pixel?] -> pixel
|
||||
* [Nichts weiter, danke] -> ende
|
||||
|
||||
=== render ===
|
||||
# color suzanne
|
||||
Alles, was du siehst, wird als ein Pixelbuffer von der CPU gerendert. Der Pixelbuffer ist einfach eine Liste von Bytes, die einen Frame beschreibt. Jedes Pixel hat dabei genau einen Byte, also 8 Bit speicherplatz. Deshalb haben wir auch nur 256 unterschiedliche Farbwerte pro Frame.
|
||||
|
||||
Texturen sind 8-bit indexed TGAs, die durch eine 256-Farben-Palette laufen. Das ist ein simples Dateiformat, das viele Grafikprogramme unterstützen und das sehr einfach zu verarbeiten ist.
|
||||
Farb-Paletten sind auch TGA-Dateien, mit 16x16 Pixeln im normalen 24 Bit RGB-Raum. Diese werden von der Engine gelesen und als Farbpalette festgelegt.
|
||||
|
||||
Schalte Mal den Wireframe-Modus ist im ESC-Menü ein!
|
||||
|
||||
* [Zurück zum Menü] -> menu
|
||||
|
||||
=== welt ===
|
||||
# color suzanne
|
||||
Geometrie kommt aus OBJ-Dateien, die z.B. in Blender bearbeitet werden. Dabei gibt es Konventionen:
|
||||
|
||||
Mesh-Namen mit Prefix steuern die Rolle: VIS_ ist nur sichtbar, COL_ ist nur
|
||||
Kollision, INT_ ist nur Trigger (weder sichtbar, noch kollision). Alle anderen Meshes sind sichtbar und kollidieren.
|
||||
|
||||
Materialien werden in assets/materials/materials.toml definiert. Dort liegen auch die Texturen.
|
||||
|
||||
* [Zurück zum Menü] -> menu
|
||||
|
||||
=== interakt ===
|
||||
# color suzanne
|
||||
|
||||
Diese Engine nutzt ein Signal-System mit Triggern und Actions.
|
||||
|
||||
In signals.toml werden Trigger definiert und Actions zugewiesen. Schau dir gern die Datei Mal an.
|
||||
|
||||
Jedes Objekt kann einen eigenen Trigger bei Spieler-Interaktion haben. Dieser Trigger heißt einfach wie das Objekt. VIS_mushroom hat also den Trigger [VIS_mushroom], der Actions auslöst.
|
||||
|
||||
* [Zurück zum Menü] -> menu
|
||||
|
||||
=== story ===
|
||||
# color suzanne
|
||||
Dialoge und Interaktionen werden in Inkscript geschrieben. Die Engine liest kompilierte .ink.json-Dateien.
|
||||
|
||||
Im backend läuft bladeink als Interpreter. Dadurch ist der komplette Inky-Standard unterstützt. Color-Tags ändern die Textfarbe. Alle anderen Tags werden als Signals in der Engine dispatched.
|
||||
|
||||
Die Engine hat einen Variablen-Speicher, der mit dem Variablenspeicher vor und nach jedem Dialog synchronisiert wird. Variablen im Inkscript sind also global verfügbar und können durch Signals gesteuert werden.
|
||||
|
||||
* [Zurück zum Menü] -> menu
|
||||
|
||||
=== audio ===
|
||||
# color suzanne
|
||||
Die Sound-Engine ist noch sehr rudimentär. WAV-Dateien werden durch die Engine geladen. Die Sounds können durch Signals gesteuert werden. Es gibt noch keine 3D-Effekte.
|
||||
|
||||
* [Zurück zum Menü] -> menu
|
||||
|
||||
=== pixel ===
|
||||
# color suzanne
|
||||
Diese Engine unterstützt beliebige Auflösung. Aktuell sind es 320x240px also qVGA. Ich find das schick!
|
||||
Aber 640x480px haben auch noch einen guten Retro-Look. Bei 1920p brauchen wir dann schon bessere Assets, sonst wirds hässlich.
|
||||
|
||||
Starte die Engine im Terminal mit ./irl3d --resolution VGA
|
||||
|
||||
* [Zurück zum Menü] -> menu
|
||||
|
||||
=== ende ===
|
||||
# color suzanne
|
||||
Schön, dass du dich hier umschaust. Viel Spaß noch!
|
||||
* [Bis dann]
|
||||
# suzanne_farewell
|
||||
-> END
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,53 @@
|
||||
# Signal → Action Mapping. Trigger feuern Signals (Strings); hier wird
|
||||
# definiert, was bei jedem Signal passiert. Signal-Name = Trigger-Quelle:
|
||||
# für Objekt-Interaktion ist es der Instance-Name ohne Blender-Suffix.
|
||||
#
|
||||
# Actions sind Strings im Format "verb args". Verben:
|
||||
# start_ink <script> Story laden (Frontend treibt sie)
|
||||
# debug_log <text> eprintln!
|
||||
# set <name> <value> KV setzen (true/false/i32/f32/string)
|
||||
# inc <name> [<delta>] KV-Integer +Delta (Default +1)
|
||||
# clear <name> KV-Eintrag entfernen
|
||||
# hide_object <name> Effect: Instance ausblenden
|
||||
# play_sound <file> Effect: SFX abspielen
|
||||
# load_palette <file> Effect: Farbschema laden
|
||||
#
|
||||
# Reserviertes Signal `[init]` feuert einmal vor der Game-Loop —
|
||||
# idealer Ort für KV-Defaults.
|
||||
|
||||
[init]
|
||||
actions = [
|
||||
"set cube_clicks 0",
|
||||
"set mushrooms_collected 0",
|
||||
"set quest_mushroom_active false",
|
||||
"set quest_mushroom_done false",
|
||||
]
|
||||
|
||||
[Mushroom]
|
||||
actions = [
|
||||
"inc mushrooms_collected",
|
||||
"play_sound pickup.wav",
|
||||
"hide_object $self",
|
||||
]
|
||||
|
||||
[Person]
|
||||
actions = [
|
||||
"play_sound greet.wav",
|
||||
"start_ink person.ink.json",
|
||||
]
|
||||
|
||||
[Suzanne]
|
||||
actions = [
|
||||
"start_ink suzanne.ink.json",
|
||||
]
|
||||
|
||||
[Cube]
|
||||
actions = [
|
||||
"inc cube_clicks",
|
||||
"start_ink cube.ink.json",
|
||||
]
|
||||
|
||||
[person_farewell]
|
||||
actions = [
|
||||
"debug_log Person hat sich verabschiedet",
|
||||
]
|
||||
@@ -0,0 +1,26 @@
|
||||
//! Asset-Pfadauflösung: Distribution-Layout (assets/ neben der Binary)
|
||||
//! oder Dev-Layout (assets/ im CWD, also Projektroot bei `cargo run`).
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
static BASE: OnceLock<PathBuf> = OnceLock::new();
|
||||
|
||||
fn base() -> &'static PathBuf {
|
||||
BASE.get_or_init(|| {
|
||||
if let Ok(exe) = std::env::current_exe() {
|
||||
if let Some(dir) = exe.parent() {
|
||||
if dir.join("assets").exists() {
|
||||
return dir.to_path_buf();
|
||||
}
|
||||
}
|
||||
}
|
||||
std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."))
|
||||
})
|
||||
}
|
||||
|
||||
/// Wandelt einen relativen Pfad wie `"assets/signals.toml"` in einen
|
||||
/// absoluten Pfad relativ zum Asset-Basisverzeichnis um.
|
||||
pub fn path(rel: &str) -> String {
|
||||
base().join(rel).to_string_lossy().into_owned()
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
//! Owner des dispatch-relevanten Spielzustands und der Borrow-View, durch
|
||||
//! die Signal-Actions und Ink-Story-Steps darauf zugreifen.
|
||||
//!
|
||||
//! `Game` hält die Slots, die Actions lesen oder mutieren dürfen.
|
||||
//! `ActionCtx` ist der schmale `&mut`-View; er wird über `Game::action_ctx()`
|
||||
//! konstruiert und durch die Dispatch-Pipeline gereicht.
|
||||
//!
|
||||
//! Verben, deren Ziel-Subsystem (Szene, Audio, Renderer) noch nicht
|
||||
//! existiert oder den Kern nichts angeht, produzieren [`Effect`]-Werte
|
||||
//! statt direkt zu wirken. Frontends konsumieren die Queue: die CLI druckt
|
||||
//! sie, die Engine führt sie später aus. So bleibt der Kern headless
|
||||
//! testbar und kennt keines der Subsysteme.
|
||||
|
||||
use crate::ink;
|
||||
use crate::kv::Store;
|
||||
use crate::signals::Signals;
|
||||
|
||||
/// Von Actions angeforderte Wirkung außerhalb des Kerns. Die Queue in
|
||||
/// `Game::effects` gehört nach jedem Dispatch geleert (konsumiert).
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum Effect {
|
||||
/// Instance unsichtbar + kollisionslos + nicht mehr interagierbar machen.
|
||||
HideObject(String),
|
||||
/// WAV unter `assets/audio/<name>` als SFX abspielen.
|
||||
PlaySound(String),
|
||||
/// Palette/Farbschema unter `assets/palettes/<name>` laden.
|
||||
LoadPalette(String),
|
||||
}
|
||||
|
||||
pub struct Game {
|
||||
pub kv: Store,
|
||||
pub signals: Signals,
|
||||
pub story: Option<ink::Story>,
|
||||
pub effects: Vec<Effect>,
|
||||
}
|
||||
|
||||
pub struct ActionCtx<'a> {
|
||||
pub kv: &'a mut Store,
|
||||
pub signals: &'a Signals,
|
||||
pub story: &'a mut Option<ink::Story>,
|
||||
pub effects: &'a mut Vec<Effect>,
|
||||
/// Auslöser-Identifier: bei Objekt-Interaktion der volle Instance-Name
|
||||
/// (mit Blender-Suffix, z.B. `Mushroom.005`). `$self` in Action-Args
|
||||
/// wird damit substituiert. `None` bei Signalen ohne Quell-Instance
|
||||
/// (z.B. `init`, Story-Tags).
|
||||
pub instance_name: Option<String>,
|
||||
}
|
||||
|
||||
impl Game {
|
||||
pub fn new(signals: Signals) -> Self {
|
||||
Self { kv: Store::new(), signals, story: None, effects: Vec::new() }
|
||||
}
|
||||
|
||||
pub fn action_ctx(&mut self, instance_name: Option<String>) -> ActionCtx<'_> {
|
||||
ActionCtx {
|
||||
kv: &mut self.kv,
|
||||
signals: &self.signals,
|
||||
story: &mut self.story,
|
||||
effects: &mut self.effects,
|
||||
instance_name,
|
||||
}
|
||||
}
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
//! Dünner Adapter um die `bladeink`-Crate, gekoppelt an unseren `kv::Store`.
|
||||
//!
|
||||
//! Bei jedem `cont()` läuft ein bidirektionaler Sync:
|
||||
//! - vor `continue`: KV-Werte ins `variables_state` der Story spiegeln
|
||||
//! (Skript sieht aktuelle Spielvariablen)
|
||||
//! - nach `continue`: alle KV-Keys, die das Skript kennt, zurücklesen
|
||||
//! (Ink-`~ var = …`-Writes landen im KV)
|
||||
//!
|
||||
//! Variablen die der KV kennt, das Skript aber nicht als `VAR` deklariert,
|
||||
//! lehnt bladeink beim `set_variable` ab — wir ignorieren das still.
|
||||
//! Variablen die nur im Skript existieren, werden beim Read-Back ignoriert
|
||||
//! (KV bleibt minimal: nur was Rust kennt).
|
||||
|
||||
use std::fs::read_to_string;
|
||||
|
||||
use bladeink::story::Story as BladeStory;
|
||||
|
||||
use crate::kv::Store;
|
||||
|
||||
pub enum StoryState {
|
||||
Text(String),
|
||||
Choice { prompt: String, options: Vec<String> },
|
||||
End,
|
||||
}
|
||||
|
||||
pub struct Story {
|
||||
inner: BladeStory,
|
||||
tags: Vec<String>,
|
||||
}
|
||||
|
||||
impl Story {
|
||||
pub fn load(path: &str, kv: &Store) -> Self {
|
||||
let src = read_to_string(path)
|
||||
.unwrap_or_else(|e| panic!("ink load {}: {}", path, e));
|
||||
let inner = BladeStory::new(&src)
|
||||
.unwrap_or_else(|e| panic!("ink parse {}: {:?}", path, e));
|
||||
let mut s = Self { inner, tags: Vec::new() };
|
||||
s.sync_from_kv(kv);
|
||||
s
|
||||
}
|
||||
|
||||
pub fn take_tags(&mut self) -> Vec<String> { std::mem::take(&mut self.tags) }
|
||||
|
||||
pub fn cont(&mut self, kv: &mut Store) -> StoryState {
|
||||
self.sync_from_kv(kv);
|
||||
|
||||
// continue_maximally ruft intern reset_output() vor jedem cont() —
|
||||
// damit überschriebe der Tag der letzten Zeile alle vorherigen.
|
||||
// Wir loopen selbst und sammeln Tags nach jedem Schritt ein, sonst
|
||||
// gehen z.B. `# set has_key true` am Knoten-Anfang bei mehrzeiligem
|
||||
// Text verloren.
|
||||
let mut text = String::new();
|
||||
while self.inner.can_continue() {
|
||||
let chunk = self.inner.cont()
|
||||
.unwrap_or_else(|e| panic!("ink cont: {:?}", e));
|
||||
text.push_str(&chunk);
|
||||
if let Ok(t) = self.inner.get_current_tags() {
|
||||
self.tags.extend(t);
|
||||
}
|
||||
}
|
||||
let text = text.trim().to_string();
|
||||
|
||||
self.sync_to_kv(kv);
|
||||
|
||||
let choices = self.inner.get_current_choices();
|
||||
if !choices.is_empty() {
|
||||
let options = choices.iter().map(|c| c.text.clone()).collect();
|
||||
return StoryState::Choice { prompt: text, options };
|
||||
}
|
||||
if !text.is_empty() { return StoryState::Text(text); }
|
||||
StoryState::End
|
||||
}
|
||||
|
||||
pub fn choose(&mut self, i: usize) {
|
||||
self.inner.choose_choice_index(i)
|
||||
.unwrap_or_else(|e| panic!("ink choose {}: {:?}", i, e));
|
||||
}
|
||||
|
||||
fn sync_from_kv(&mut self, kv: &Store) {
|
||||
for (name, value) in kv {
|
||||
// set_variable schlägt fehl wenn der Name kein deklariertes
|
||||
// VAR im Skript ist — still ignorieren.
|
||||
let _ = self.inner.set_variable(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
fn sync_to_kv(&self, kv: &mut Store) {
|
||||
let keys: Vec<String> = kv.keys().cloned().collect();
|
||||
for name in keys {
|
||||
if let Some(v) = self.inner.get_variable(&name) {
|
||||
kv.insert(name, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
//! Persistenter Game-State Key-Value-Store.
|
||||
//!
|
||||
//! Single Source of Truth für alle Spielvariablen (Quest-Flags, Inventar-
|
||||
//! Counter, Story-Beats). Ink-Skripte haben eigene `variables_state`-Stores;
|
||||
//! die werden bei jedem `cont()` mit diesem KV synchronisiert (siehe
|
||||
//! [`crate::ink`]). Damit der KV authoritativ bleibt: externe Actions
|
||||
//! (`set`, `inc`, `clear`) ändern *immer* den KV, niemals direkt die Story.
|
||||
//!
|
||||
//! Wir nutzen `bladeink::ValueType` als Value, damit die Sync-Konvertierung
|
||||
//! gratis ist (kein Mapping zwischen Rust- und Ink-Typen nötig).
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use bladeink::value_type::ValueType;
|
||||
|
||||
pub type Store = HashMap<String, ValueType>;
|
||||
|
||||
/// Parst einen Action-Arg-String zu einem `ValueType`. Reihenfolge:
|
||||
/// `true`/`false` → Bool, dann i32, dann f32, sonst String.
|
||||
pub fn parse_value(s: &str) -> ValueType {
|
||||
let s = s.trim();
|
||||
match s {
|
||||
"true" => return ValueType::Bool(true),
|
||||
"false" => return ValueType::Bool(false),
|
||||
_ => {}
|
||||
}
|
||||
if let Ok(i) = s.parse::<i32>() { return ValueType::Int(i); }
|
||||
if let Ok(f) = s.parse::<f32>() { return ValueType::Float(f); }
|
||||
ValueType::new(s)
|
||||
}
|
||||
|
||||
/// Menschenlesbare Darstellung für REPL-Dumps und Debug-Logs.
|
||||
pub fn format_value(v: &ValueType) -> String {
|
||||
match v {
|
||||
ValueType::Bool(b) => b.to_string(),
|
||||
ValueType::Int(i) => i.to_string(),
|
||||
ValueType::Float(f) => f.to_string(),
|
||||
other => other.coerce_to_string().unwrap_or_else(|_| "<?>".into()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn apply_set(args: &str, store: &mut Store) {
|
||||
let Some((name, value)) = args.split_once(char::is_whitespace) else {
|
||||
eprintln!("[kv] set: expected 'name value', got {:?}", args);
|
||||
return;
|
||||
};
|
||||
store.insert(name.trim().to_string(), parse_value(value));
|
||||
}
|
||||
|
||||
pub fn apply_inc(args: &str, store: &mut Store) {
|
||||
let (name, delta) = args.split_once(char::is_whitespace).unwrap_or((args, "1"));
|
||||
let name = name.trim();
|
||||
let delta = delta.trim().parse::<i32>().unwrap_or(1);
|
||||
let cur = store.get(name).and_then(|v| v.coerce_to_int().ok()).unwrap_or(0);
|
||||
store.insert(name.to_string(), ValueType::Int(cur + delta));
|
||||
}
|
||||
|
||||
pub fn apply_clear(args: &str, store: &mut Store) {
|
||||
store.remove(args.trim());
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_value_types() {
|
||||
assert!(matches!(parse_value("true"), ValueType::Bool(true)));
|
||||
assert!(matches!(parse_value("false"), ValueType::Bool(false)));
|
||||
assert!(matches!(parse_value("42"), ValueType::Int(42)));
|
||||
assert!(matches!(parse_value("-7"), ValueType::Int(-7)));
|
||||
assert!(matches!(parse_value("1.5"), ValueType::Float(f) if f == 1.5));
|
||||
assert!(matches!(parse_value("hallo"), ValueType::String(_)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_inc_clear_roundtrip() {
|
||||
let mut s = Store::new();
|
||||
apply_set("clicks 3", &mut s);
|
||||
assert_eq!(s["clicks"].coerce_to_int().unwrap(), 3);
|
||||
|
||||
apply_inc("clicks", &mut s);
|
||||
assert_eq!(s["clicks"].coerce_to_int().unwrap(), 4);
|
||||
|
||||
apply_inc("clicks -2", &mut s);
|
||||
assert_eq!(s["clicks"].coerce_to_int().unwrap(), 2);
|
||||
|
||||
// inc auf unbekannten Key startet bei 0
|
||||
apply_inc("fresh 5", &mut s);
|
||||
assert_eq!(s["fresh"].coerce_to_int().unwrap(), 5);
|
||||
|
||||
apply_clear("clicks", &mut s);
|
||||
assert!(!s.contains_key("clicks"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_without_value_is_noop() {
|
||||
let mut s = Store::new();
|
||||
apply_set("only_name", &mut s);
|
||||
assert!(s.is_empty());
|
||||
}
|
||||
}
|
||||
+141
-1
@@ -1,3 +1,143 @@
|
||||
//! wds — Weirdcore Dating Simulator.
|
||||
//!
|
||||
//! CLI-Phase: Die Kern-Systeme (Signal/Action-Dispatcher, KV-Store,
|
||||
//! Ink-Stories) laufen headless; diese REPL ist ihr erstes Frontend.
|
||||
//! Der Renderer kommt später dazu (siehe notes/renderer-plan.md) und
|
||||
//! konsumiert dieselben Schnittstellen — insbesondere `StoryState` und
|
||||
//! die `Effect`-Queue.
|
||||
|
||||
use std::io::{self, Write};
|
||||
|
||||
mod assets;
|
||||
mod game;
|
||||
mod ink;
|
||||
mod kv;
|
||||
mod signals;
|
||||
mod story_ctrl;
|
||||
|
||||
use game::{Effect, Game};
|
||||
use ink::StoryState;
|
||||
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
let signals_path = assets::path("assets/signals.toml");
|
||||
let table = signals::load_signals(&signals_path);
|
||||
println!("wds-Konsole — {} Signale aus {}. `help` für Befehle.",
|
||||
table.len(), signals_path);
|
||||
let mut game = Game::new(table);
|
||||
|
||||
// Reserviertes `[init]`-Signal: KV-Defaults setzen, bevor Stories laufen.
|
||||
fire(&mut game, "init", None);
|
||||
|
||||
while let Some(line) = read_line("> ") {
|
||||
let line = line.trim();
|
||||
match line {
|
||||
"" => {}
|
||||
"quit" | "exit" => break,
|
||||
"help" => print_help(),
|
||||
"kv" => dump_kv(&game),
|
||||
"reload" => {
|
||||
game.signals = signals::load_signals(&signals_path);
|
||||
println!("{} Signale geladen.", game.signals.len());
|
||||
}
|
||||
_ => {
|
||||
if let Some(name) = line.strip_prefix("use ") {
|
||||
// Objekt-Interaktion simulieren: Signal ist der gestrippte
|
||||
// Name, $self der volle — wie der LMB-Klick-Pfad in irl3d.
|
||||
let name = name.trim();
|
||||
let key = signals::signal_key(name).to_string();
|
||||
fire(&mut game, &key, Some(name.to_string()));
|
||||
} else {
|
||||
fire(&mut game, line, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn print_help() {
|
||||
println!(" <signal|action> Signal feuern oder Action direkt ausführen");
|
||||
println!(" (z.B. `Cube`, `set has_key true`, `inc foo 3`)");
|
||||
println!(" use <instance> Objekt-Interaktion simulieren — `use Mushroom.005`");
|
||||
println!(" feuert Signal `Mushroom` mit $self = Mushroom.005");
|
||||
println!(" kv KV-Store anzeigen");
|
||||
println!(" reload signals.toml neu laden");
|
||||
println!(" quit beenden");
|
||||
}
|
||||
|
||||
/// Signal dispatchen, Effects ausgeben und — falls eine Action eine Story
|
||||
/// gestartet hat — den Dialog-Loop fahren.
|
||||
fn fire(game: &mut Game, signal: &str, instance: Option<String>) {
|
||||
{
|
||||
let mut ctx = game.action_ctx(instance);
|
||||
signals::dispatch(signal, &mut ctx);
|
||||
}
|
||||
drain_effects(game);
|
||||
if game.story.is_some() {
|
||||
run_story(game);
|
||||
}
|
||||
}
|
||||
|
||||
/// Dialog-Loop: Story schrittweise treiben, Tags zurück in den Dispatcher,
|
||||
/// Choices über nummerierte Eingabe. Läuft bis `End` (oder EOF).
|
||||
fn run_story(game: &mut Game) {
|
||||
let mut sel: Option<usize> = None;
|
||||
loop {
|
||||
let state = {
|
||||
let mut ctx = game.action_ctx(None);
|
||||
let (state, tags) = story_ctrl::advance(sel, &mut ctx);
|
||||
for t in &tags { signals::dispatch(t, &mut ctx); }
|
||||
state
|
||||
};
|
||||
drain_effects(game);
|
||||
match state {
|
||||
None | Some(StoryState::End) => break,
|
||||
Some(StoryState::Text(text)) => {
|
||||
println!("\n{text}");
|
||||
if read_line(" [Enter] ").is_none() { break; }
|
||||
sel = None;
|
||||
}
|
||||
Some(StoryState::Choice { prompt, options }) => {
|
||||
if !prompt.is_empty() { println!("\n{prompt}"); }
|
||||
for (i, o) in options.iter().enumerate() {
|
||||
println!(" {}) {o}", i + 1);
|
||||
}
|
||||
sel = loop {
|
||||
let Some(line) = read_line(" wahl> ") else { return; };
|
||||
if let Ok(n) = line.trim().parse::<usize>() {
|
||||
if (1..=options.len()).contains(&n) { break Some(n - 1); }
|
||||
}
|
||||
println!(" (1..{})", options.len());
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn drain_effects(game: &mut Game) {
|
||||
for e in game.effects.drain(..) {
|
||||
match e {
|
||||
Effect::HideObject(n) => println!("[effect] hide_object {n}"),
|
||||
Effect::PlaySound(n) => println!("[effect] play_sound {n}"),
|
||||
Effect::LoadPalette(n) => println!("[effect] load_palette {n}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dump_kv(game: &Game) {
|
||||
let mut keys: Vec<&String> = game.kv.keys().collect();
|
||||
keys.sort();
|
||||
for k in keys {
|
||||
println!(" {k} = {}", kv::format_value(&game.kv[k]));
|
||||
}
|
||||
}
|
||||
|
||||
/// Zeile von stdin lesen; `None` bei EOF (Ctrl-D) oder Lesefehler.
|
||||
fn read_line(prompt: &str) -> Option<String> {
|
||||
print!("{prompt}");
|
||||
io::stdout().flush().ok();
|
||||
let mut buf = String::new();
|
||||
match io::stdin().read_line(&mut buf) {
|
||||
Ok(0) | Err(_) => None,
|
||||
Ok(_) => Some(buf.trim_end().to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
+196
@@ -0,0 +1,196 @@
|
||||
//! Signal → Action Dispatcher.
|
||||
//!
|
||||
//! Triggers (Objekt-Interaktion, Zone-Enter, Ink-Tag, Konsole) feuern
|
||||
//! *Signals* als Strings. Eine zentrale Table in `assets/signals.toml` mappt
|
||||
//! jedes Signal auf eine Liste von Actions. Actions sind selbst wieder
|
||||
//! Strings im Format `"verb args"` — dieselbe Vokabular-Familie wie
|
||||
//! Ink-Tags, sodass beide auf dieselbe Execute-Pipeline laufen.
|
||||
//!
|
||||
//! Builtin-Verben:
|
||||
//! `start_ink <script>` Story aus `assets/interactions/` in den
|
||||
//! Story-Slot laden; das Frontend treibt sie
|
||||
//! dann über `story_ctrl::advance`.
|
||||
//! `debug_log <text>` eprintln!
|
||||
//! `set <name> <value>` KV setzen (true/false/i32/f32/string)
|
||||
//! `inc <name> [<delta>]` KV-Integer inkrementieren (Default +1)
|
||||
//! `clear <name>` KV-Eintrag entfernen
|
||||
//! `hide_object <name>` → Effect::HideObject
|
||||
//! `play_sound <file>` → Effect::PlaySound
|
||||
//! `load_palette <file>` → Effect::LoadPalette
|
||||
//!
|
||||
//! Parameter-Substitution: vor dem Parsen ersetzt `execute` `$self` in den
|
||||
//! Action-Args durch `ctx.instance_name`. Damit kann eine generische Action
|
||||
//! wie `hide_object $self` für viele duplizierte Instances wirken, ohne pro
|
||||
//! Instance einen Eintrag in signals.toml zu brauchen.
|
||||
//!
|
||||
//! Dispatch-Reihenfolge: erst Signal-Lookup; steht der Name in der Table,
|
||||
//! laufen alle gemappten Actions. Sonst wird der String direkt als
|
||||
//! Builtin-Aufruf probiert — so wirken Ink-Tags wie `# set has_key true`
|
||||
//! ohne Umweg über signals.toml. Unbekannte Verben werden still ignoriert.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::fs::read_to_string;
|
||||
|
||||
use crate::game::{ActionCtx, Effect};
|
||||
use crate::kv;
|
||||
use crate::story_ctrl;
|
||||
|
||||
pub type Actions = Vec<String>;
|
||||
pub type Signals = HashMap<String, Actions>;
|
||||
|
||||
/// Strippt Blenders Duplikat-Suffix (`.NNN` mit reinen Ziffern am Ende),
|
||||
/// damit `Mushroom.001`, `Mushroom.042` etc. alle auf `[Mushroom]` in
|
||||
/// signals.toml mappen. Andere Punkte im Namen bleiben unberührt, solange
|
||||
/// der letzte Token nicht rein numerisch ist.
|
||||
pub fn signal_key(name: &str) -> &str {
|
||||
if let Some(dot) = name.rfind('.') {
|
||||
let tail = &name[dot + 1..];
|
||||
if !tail.is_empty() && tail.chars().all(|c| c.is_ascii_digit()) {
|
||||
return &name[..dot];
|
||||
}
|
||||
}
|
||||
name
|
||||
}
|
||||
|
||||
pub fn dispatch(signal: &str, ctx: &mut ActionCtx) {
|
||||
// Signal-Name in der Table? Dann alle gemappten Actions ausführen.
|
||||
if let Some(actions) = ctx.signals.get(signal) {
|
||||
let actions = actions.clone(); // entkoppelt vom ctx.signals-Borrow
|
||||
for a in &actions { execute(a, ctx); }
|
||||
return;
|
||||
}
|
||||
// Sonst direkt als Builtin probieren (Ink-Tag-Pfad).
|
||||
execute(signal, ctx);
|
||||
}
|
||||
|
||||
fn execute(cmd: &str, ctx: &mut ActionCtx) {
|
||||
// $self → ctx.instance_name. Substituieren bevor wir Verb/Args splitten,
|
||||
// damit Tokens wie `hide_object $self` einheitlich funktionieren. Ohne
|
||||
// instance_name (z.B. `init`-Signal) bleibt `$self` stehen — die Action
|
||||
// zielt dann ins Leere, was für Effects ein No-Op beim Konsumenten ist.
|
||||
let cmd_owned: String;
|
||||
let cmd_ref: &str = if cmd.contains("$self") {
|
||||
if let Some(name) = ctx.instance_name.as_deref() {
|
||||
cmd_owned = cmd.replace("$self", name);
|
||||
&cmd_owned
|
||||
} else { cmd }
|
||||
} else { cmd };
|
||||
let (verb, args) = cmd_ref.split_once(char::is_whitespace).unwrap_or((cmd_ref, ""));
|
||||
let args = args.trim();
|
||||
match verb {
|
||||
"start_ink" => story_ctrl::start(args, ctx),
|
||||
"debug_log" => eprintln!("[signal] {args}"),
|
||||
"set" => kv::apply_set(args, ctx.kv),
|
||||
"inc" => kv::apply_inc(args, ctx.kv),
|
||||
"clear" => kv::apply_clear(args, ctx.kv),
|
||||
"hide_object" => ctx.effects.push(Effect::HideObject(args.to_string())),
|
||||
"play_sound" => ctx.effects.push(Effect::PlaySound(args.to_string())),
|
||||
"load_palette" => ctx.effects.push(Effect::LoadPalette(args.to_string())),
|
||||
_ => { /* Unbekannter Verb/Tag → still ignorieren */ }
|
||||
}
|
||||
}
|
||||
|
||||
/// Parst das signals.toml-Subset: `[signal]`-Header gefolgt von einem
|
||||
/// `actions = [ "…", … ]`-Array. Bewusst handgerollt statt toml-Crate —
|
||||
/// das Format ist trivial und die Engine bleibt dependency-arm.
|
||||
pub fn parse_signals(src: &str) -> Signals {
|
||||
let mut map: Signals = HashMap::new();
|
||||
let mut cur = String::new();
|
||||
let mut in_arr = false;
|
||||
for line in src.lines() {
|
||||
let line = line.trim();
|
||||
if line.is_empty() || line.starts_with('#') { continue; }
|
||||
if !in_arr && line.starts_with('[') {
|
||||
cur = line.trim_matches(|c| c == '[' || c == ']').to_string();
|
||||
map.entry(cur.clone()).or_default();
|
||||
continue;
|
||||
}
|
||||
if !in_arr && line.starts_with("actions") && line.contains('[') {
|
||||
in_arr = true;
|
||||
continue;
|
||||
}
|
||||
if in_arr {
|
||||
if line.starts_with(']') { in_arr = false; continue; }
|
||||
let s = line.trim_end_matches(',').trim().trim_matches('"');
|
||||
if s.is_empty() { continue; }
|
||||
map.entry(cur.clone()).or_default().push(s.to_string());
|
||||
}
|
||||
}
|
||||
map
|
||||
}
|
||||
|
||||
/// Lädt `signals.toml`. Fehlende Datei → leere Table (kein Fehler), damit
|
||||
/// ein Projekt ohne Signals lauffähig bleibt.
|
||||
pub fn load_signals(path: &str) -> Signals {
|
||||
match read_to_string(path) {
|
||||
Ok(src) => parse_signals(&src),
|
||||
Err(_) => Signals::new(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::game::Game;
|
||||
|
||||
#[test]
|
||||
fn signal_key_strips_blender_suffix() {
|
||||
assert_eq!(signal_key("Mushroom.005"), "Mushroom");
|
||||
assert_eq!(signal_key("Mushroom"), "Mushroom");
|
||||
assert_eq!(signal_key("my.thing.with.dots"), "my.thing.with.dots");
|
||||
assert_eq!(signal_key("name."), "name.");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_signals_table() {
|
||||
let src = r#"
|
||||
# Kommentar
|
||||
[init]
|
||||
actions = [
|
||||
"set foo 1",
|
||||
"debug_log hi",
|
||||
]
|
||||
|
||||
[Empty]
|
||||
actions = [
|
||||
]
|
||||
"#;
|
||||
let s = parse_signals(src);
|
||||
assert_eq!(s["init"], vec!["set foo 1", "debug_log hi"]);
|
||||
assert!(s["Empty"].is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dispatch_runs_actions_and_queues_effects() {
|
||||
let mut signals = Signals::new();
|
||||
signals.insert("pickup".into(), vec![
|
||||
"inc items".into(),
|
||||
"play_sound pickup.wav".into(),
|
||||
"hide_object $self".into(),
|
||||
]);
|
||||
let mut game = Game::new(signals);
|
||||
|
||||
let mut ctx = game.action_ctx(Some("Mushroom.005".into()));
|
||||
dispatch("pickup", &mut ctx);
|
||||
|
||||
assert_eq!(game.kv["items"].coerce_to_int().unwrap(), 1);
|
||||
assert_eq!(game.effects, vec![
|
||||
Effect::PlaySound("pickup.wav".into()),
|
||||
Effect::HideObject("Mushroom.005".into()),
|
||||
]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unknown_signal_falls_through_to_builtin() {
|
||||
let mut game = Game::new(Signals::new());
|
||||
let mut ctx = game.action_ctx(None);
|
||||
// Ink-Tag-Pfad: kein Table-Eintrag, direkt als Verb ausführen.
|
||||
dispatch("set has_key true", &mut ctx);
|
||||
assert!(game.kv["has_key"].coerce_to_bool().unwrap());
|
||||
|
||||
// Unbekanntes Verb ist ein stilles No-Op.
|
||||
let mut ctx = game.action_ctx(None);
|
||||
dispatch("color story_a", &mut ctx);
|
||||
assert!(game.effects.is_empty());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
//! Story-Lifecycle: Ink-Skript in den Story-Slot laden und schrittweise
|
||||
//! treiben. Frontend-agnostisch — anders als in irl3d, wo `StoryState`
|
||||
//! direkt in UI-Panels gegossen wurde, konsumiert hier der Aufrufer
|
||||
//! (CLI-REPL jetzt, Panel-UI später) den Zustand selbst.
|
||||
//!
|
||||
//! Tags eines Schritts werden als `Vec<String>` zurückgegeben — der
|
||||
//! Aufrufer entscheidet, ob/wie er sie via `signals::dispatch` weiterreicht
|
||||
//! (vermeidet den Modul-Zyklus signals → story_ctrl → signals).
|
||||
|
||||
use crate::assets;
|
||||
use crate::game::ActionCtx;
|
||||
use crate::ink::{Story, StoryState};
|
||||
|
||||
/// Lädt ein Ink-Skript (Pfad relativ zu `assets/interactions/`) in den
|
||||
/// Story-Slot. Der erste Schritt passiert noch nicht — das Frontend ruft
|
||||
/// dafür `advance(None, …)`.
|
||||
pub fn start(script: &str, ctx: &mut ActionCtx) {
|
||||
let path = assets::path(&format!("assets/interactions/{script}"));
|
||||
*ctx.story = Some(Story::load(&path, ctx.kv));
|
||||
}
|
||||
|
||||
/// Treibt die laufende Story einen Schritt weiter. `sel` ist die
|
||||
/// Choice-Auswahl bzw. `None` beim ersten Schritt oder Text-Weiterklicken.
|
||||
/// Bei `End` wird der Story-Slot geleert. Rückgabe: neuer Zustand (oder
|
||||
/// `None` wenn gar keine Story läuft) plus Tags zum Dispatchen.
|
||||
pub fn advance(sel: Option<usize>, ctx: &mut ActionCtx) -> (Option<StoryState>, Vec<String>) {
|
||||
let Some(st) = ctx.story.as_mut() else { return (None, Vec::new()); };
|
||||
if let Some(i) = sel { st.choose(i); }
|
||||
let state = st.cont(ctx.kv);
|
||||
let tags = st.take_tags();
|
||||
if matches!(state, StoryState::End) { *ctx.story = None; }
|
||||
(Some(state), tags)
|
||||
}
|
||||
Reference in New Issue
Block a user