diff --git a/.gitignore b/.gitignore index 0bc1af3..816f622 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,4 @@ flutter/eve_parity/assets/repixelizer/ *.deb *.ipa *.dSYM/ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..cfc37b4 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,602 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "android_system_properties" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "cc" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6651c9ed80effdc7db0ff72512157f901af5e3549e341e24b1dd4887d836d838" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7648175b45a9a48536d676f68d918270699102aa8dab5496df06904c914600" + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-link", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cultcache-rs" +version = "0.2.0" +source = "git+https://github.com/GameCult/CultLib.git?rev=b3bd8dd5e5b208b2012392e9c5d6dfee2441aaa7#b3bd8dd5e5b208b2012392e9c5d6dfee2441aaa7" +dependencies = [ + "anyhow", + "chrono", + "cultcache-rs-derive", + "fs2", + "redb", + "rmp-serde", + "serde", + "serde_bytes", + "serde_json", + "uuid", + "windows-sys", +] + +[[package]] +name = "cultcache-rs-derive" +version = "0.1.0" +source = "git+https://github.com/GameCult/CultLib.git?rev=b3bd8dd5e5b208b2012392e9c5d6dfee2441aaa7#b3bd8dd5e5b208b2012392e9c5d6dfee2441aaa7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484" + +[[package]] +name = "fs2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[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.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.190" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce5d3ddc6d3fa000eb1536d85e147bfe31aacaba692ed6a876f95cb7c855be78" + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[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 = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +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 = "redb" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb338a6c67830a61bed824b78c2bb034ab1ff401a20616bd7957524f4fdc2f22" +dependencies = [ + "libc", +] + +[[package]] +name = "rmp" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c" +dependencies = [ + "num-traits", +] + +[[package]] +name = "rmp-serde" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155" +dependencies = [ + "rmp", + "serde", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.8", +] + +[[package]] +name = "serde_json" +version = "1.0.154" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7e9cc8b1b85264074fbcc02a88680c4096b1e47df8f739dceb03bf482f04bd6" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01016da373cd8f7ef12624f796309f5c31ba8d646dd08856c02cd741d823c622" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thing-contract" +version = "0.1.0" +dependencies = [ + "anyhow", + "cultcache-rs", + "rmp-serde", + "serde", +] + +[[package]] +name = "unicode-ident" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" + +[[package]] +name = "uuid" +version = "1.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cc1186384beb7dd8eedea376413fd654937285ea6c9cfbb928dc3043ea4b606" +dependencies = [ + "getrandom", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 3.0.8", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.129" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..d4018b6 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,9 @@ +[workspace] +members = ["crates/*"] +resolver = "2" + +[workspace.dependencies] +anyhow = "1" +cultcache-rs = { git = "https://github.com/GameCult/CultLib.git", rev = "b3bd8dd5e5b208b2012392e9c5d6dfee2441aaa7", package = "cultcache-rs" } +serde = { version = "1", features = ["derive"] } +rmp-serde = "1.3" diff --git a/crates/thing-contract/Cargo.toml b/crates/thing-contract/Cargo.toml new file mode 100644 index 0000000..e261137 --- /dev/null +++ b/crates/thing-contract/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "thing-contract" +version = "0.1.0" +edition = "2024" +license = "MIT" +publish = false + +[dependencies] +anyhow = { workspace = true } +cultcache-rs = { workspace = true } +serde = { workspace = true } + +[dev-dependencies] +rmp-serde = { workspace = true } diff --git a/crates/thing-contract/src/asset.rs b/crates/thing-contract/src/asset.rs new file mode 100644 index 0000000..575b212 --- /dev/null +++ b/crates/thing-contract/src/asset.rs @@ -0,0 +1,38 @@ +//! An asset catalog: the images a provider's surfaces reference by catalog id and asset id. + +use cultcache_rs::{CultSlots, DatabaseEntry}; + +use crate::keys::{ProviderId, asset_catalog_key}; + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct AssetEntry { + #[cultcache(key = 0)] + pub id: String, + #[cultcache(key = 1)] + pub media_type: String, + #[cultcache(key = 2)] + pub uri: String, + #[cultcache(key = 3)] + pub width: i32, + #[cultcache(key = 4)] + pub height: i32, +} + +#[derive(Clone, Debug, PartialEq, DatabaseEntry)] +#[cultcache(type = "gamecult.thing.asset_catalog", schema = "gamecult.thing.asset_catalog.v1")] +pub struct AssetCatalog { + #[cultcache(key = 0)] + pub provider_id: ProviderId, + #[cultcache(key = 1)] + pub catalog_id: String, + #[cultcache(key = 2)] + pub version: i64, + #[cultcache(key = 3)] + pub assets: Vec, +} + +impl AssetCatalog { + pub fn record_key(&self) -> String { + asset_catalog_key(&self.provider_id, &self.catalog_id) + } +} diff --git a/crates/thing-contract/src/command.rs b/crates/thing-contract/src/command.rs new file mode 100644 index 0000000..66344d9 --- /dev/null +++ b/crates/thing-contract/src/command.rs @@ -0,0 +1,120 @@ +//! Commands: what a surface declares, and the one envelope a consumer sends. The caller's +//! identity has no field here: the transport sets it. + +use cultcache_rs::{CultSlots, CultUnion, DatabaseEntry}; + +use crate::keys::{CommandId, NodeId, SchemaId, invocation_key}; +use crate::source::ParamName; + +/// A command a surface declares. A node may reference only a declared command. +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct CommandDecl { + #[cultcache(key = 0)] + pub id: CommandId, + #[cultcache(key = 1)] + pub payload_schema: SchemaId, + /// The input nodes whose current values travel with an invocation. + #[cultcache(key = 2)] + pub captures: Vec, + #[cultcache(key = 3)] + pub confirm: Option, +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct CommandIdRef { + #[cultcache(key = 0)] + pub id: CommandId, +} + +#[derive(Clone, Debug, PartialEq, CultUnion)] +pub enum CommandRef { + #[cultcache(key = 0)] + Id(CommandIdRef), + #[cultcache(key = 1)] + Param(ParamName), +} + +/// A typed record of the declared `payload_schema`, as its MessagePack bytes. +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct RecordPayload { + #[cultcache(key = 0)] + pub schema: SchemaId, + #[cultcache(key = 1, bytes)] + pub bytes: Vec, +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct TextValue { + #[cultcache(key = 0)] + pub value: String, +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct NumberValue { + #[cultcache(key = 0)] + pub value: f64, +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct BoolValue { + #[cultcache(key = 0)] + pub value: bool, +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct ChoiceValue { + #[cultcache(key = 0)] + pub value: String, +} + +#[derive(Clone, Debug, PartialEq, CultUnion)] +pub enum InputValue { + #[cultcache(key = 0)] + Text(TextValue), + #[cultcache(key = 1)] + Number(NumberValue), + #[cultcache(key = 2)] + Bool(BoolValue), + #[cultcache(key = 3)] + Choice(ChoiceValue), +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct CapturedInput { + #[cultcache(key = 0)] + pub node: NodeId, + #[cultcache(key = 1)] + pub value: InputValue, +} + +/// A consumer's request that a provider run a declared command. +#[derive(Clone, Debug, PartialEq, DatabaseEntry)] +#[cultcache(type = "gamecult.thing.command_invocation", schema = "gamecult.thing.command_invocation.v1")] +pub struct CommandInvocation { + /// A UUID string, unique per invocation. + #[cultcache(key = 0)] + pub invocation_id: String, + #[cultcache(key = 1)] + pub surface_key: String, + #[cultcache(key = 2)] + pub surface_version: i64, + #[cultcache(key = 3)] + pub command: CommandId, + #[cultcache(key = 4)] + pub payload: RecordPayload, + #[cultcache(key = 5)] + pub captured: Vec, + #[cultcache(key = 6)] + pub client: String, + /// RFC 3339, UTC. + #[cultcache(key = 7)] + pub issued_at: String, + #[cultcache(key = 8)] + pub delegation: Option, +} + +impl CommandInvocation { + pub fn record_key(&self) -> String { + invocation_key(&self.invocation_id) + } +} diff --git a/crates/thing-contract/src/keys.rs b/crates/thing-contract/src/keys.rs new file mode 100644 index 0000000..a59dd8e --- /dev/null +++ b/crates/thing-contract/src/keys.rs @@ -0,0 +1,63 @@ +//! Identifier newtypes and the record key of each kind. No caller spells a key as a free string. + +use serde::{Deserialize, Serialize}; + +macro_rules! id_type { + ($(#[$doc:meta])* $name:ident) => { + $(#[$doc])* + #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] + #[serde(transparent)] + pub struct $name(String); + + impl $name { + pub fn new(value: impl Into) -> Self { + Self(value.into()) + } + + pub fn as_str(&self) -> &str { + &self.0 + } + } + }; +} + +id_type!( + /// A provider of surfaces and state. + ProviderId +); +id_type!( + /// The schema id of a CultCache entry type, for example `gamecult.thing.surface.v1`. + SchemaId +); +id_type!( + /// The record key of a state record. + RecordKey +); +id_type!( + /// A node id, unique within its document after composition expansion. + NodeId +); +id_type!( + /// A command declared by a surface. + CommandId +); + +pub(crate) fn surface_key(provider: &ProviderId, label: &str) -> String { + format!("thing:surface:{}.{}", provider.as_str(), label) +} + +pub(crate) fn composition_key(publisher: &str, name: &str, version: i32) -> String { + format!("thing:composition:{publisher}.{name}.v{version}") +} + +pub(crate) fn invocation_key(invocation_id: &str) -> String { + format!("thing:invocation:{invocation_id}") +} + +pub(crate) fn result_key(invocation_id: &str, seq: i32) -> String { + format!("thing:result:{invocation_id}.{seq}") +} + +pub(crate) fn asset_catalog_key(provider: &ProviderId, catalog_id: &str) -> String { + format!("thing:assets:{}.{}", provider.as_str(), catalog_id) +} diff --git a/crates/thing-contract/src/lib.rs b/crates/thing-contract/src/lib.rs new file mode 100644 index 0000000..9249d76 --- /dev/null +++ b/crates/thing-contract/src/lib.rs @@ -0,0 +1,37 @@ +//! The `gamecult.thing.*` wire types and their bytes. This crate owns the contract: the +//! golden payloads it blesses are what every other language package and core are held to. +//! +//! Every kind is a CultCache `DatabaseEntry`; every closed set is a `CultUnion` written +//! `[armKey, [slots...]]`, the shape the C# reference reads and writes. + +mod asset; +mod command; +mod keys; +mod node; +mod result; +mod source; +mod style; + +pub use asset::*; +pub use command::*; +pub use keys::{CommandId, NodeId, ProviderId, RecordKey, SchemaId}; +pub use node::*; +pub use result::*; +pub use source::*; +pub use style::*; + +use cultcache_rs::{CultCache, CultCacheRegistry}; + +/// Registers the five kinds with a cache. +pub struct ThingContract; + +impl CultCacheRegistry for ThingContract { + fn register_entries(&self, cache: &mut CultCache) -> anyhow::Result<()> { + cache.register_entry_type::()?; + cache.register_entry_type::()?; + cache.register_entry_type::()?; + cache.register_entry_type::()?; + cache.register_entry_type::()?; + Ok(()) + } +} diff --git a/crates/thing-contract/src/node.rs b/crates/thing-contract/src/node.rs new file mode 100644 index 0000000..722076f --- /dev/null +++ b/crates/thing-contract/src/node.rs @@ -0,0 +1,454 @@ +//! Thing Script: the closed node union, the surface document that holds a tree of nodes, and the +//! composition document that names a reusable subtree. Arm keys are fixed for v1. + +use cultcache_rs::{CultSlots, CultUnion, DatabaseEntry}; + +use crate::command::{CommandDecl, CommandRef}; +use crate::keys::{NodeId, ProviderId, composition_key, surface_key}; +use crate::source::{Bind, BoolSource, CollectionSource, MemberPath, NumberSource, TextSource}; +use crate::style::{BoxStyle, Paint, Role}; + +/// What every node carries. +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct NodeCommon { + #[cultcache(key = 0)] + pub id: NodeId, + #[cultcache(key = 1)] + pub visible: BoolSource, + #[cultcache(key = 2)] + pub a11y_label: Option, +} + +#[derive(Clone, Debug, PartialEq, CultUnion)] +pub enum Node { + #[cultcache(key = 0)] + Box(BoxNode), + #[cultcache(key = 1)] + Text(TextNode), + #[cultcache(key = 2)] + Image(ImageNode), + #[cultcache(key = 3)] + Button(ButtonNode), + #[cultcache(key = 4)] + Toggle(ToggleNode), + #[cultcache(key = 5)] + Slider(SliderNode), + #[cultcache(key = 6)] + TextInput(TextInputNode), + #[cultcache(key = 7)] + Select(SelectNode), + #[cultcache(key = 8)] + Repeat(RepeatNode), + #[cultcache(key = 9)] + Overlay(OverlayNode), + #[cultcache(key = 10)] + Plot(PlotNode), + #[cultcache(key = 11)] + Embed(EmbedNode), + #[cultcache(key = 12)] + Use(UseNode), +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct BoxNode { + #[cultcache(key = 0)] + pub common: NodeCommon, + #[cultcache(key = 1)] + pub style: BoxStyle, + #[cultcache(key = 2)] + pub paint: Paint, + #[cultcache(key = 3)] + pub role: Role, + #[cultcache(key = 4)] + pub children: Vec, +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct TextNode { + #[cultcache(key = 0)] + pub common: NodeCommon, + #[cultcache(key = 1)] + pub content: TextSource, + #[cultcache(key = 2)] + pub style_token: String, + #[cultcache(key = 3)] + pub wrap: bool, + #[cultcache(key = 4)] + pub max_lines: Option, + #[cultcache(key = 5)] + pub selectable: bool, +} + +#[derive(Clone, Debug, PartialEq, CultUnion)] +pub enum ImageFit { + #[cultcache(key = 0)] + Contain, + #[cultcache(key = 1)] + Cover, + #[cultcache(key = 2)] + Fill, + #[cultcache(key = 3)] + Unscaled, +} + +/// An asset of a catalog, with the alternative text a reader without the picture needs. +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct ImageNode { + #[cultcache(key = 0)] + pub common: NodeCommon, + #[cultcache(key = 1)] + pub catalog: String, + #[cultcache(key = 2)] + pub asset: String, + #[cultcache(key = 3)] + pub fit: ImageFit, + #[cultcache(key = 4)] + pub alt: TextSource, +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct ButtonNode { + #[cultcache(key = 0)] + pub common: NodeCommon, + #[cultcache(key = 1)] + pub content: Vec, + #[cultcache(key = 2)] + pub on_activate: CommandRef, + #[cultcache(key = 3)] + pub enabled: BoolSource, +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct ToggleNode { + #[cultcache(key = 0)] + pub common: NodeCommon, + #[cultcache(key = 1)] + pub checked: BoolSource, + #[cultcache(key = 2)] + pub on_change: CommandRef, +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct SliderNode { + #[cultcache(key = 0)] + pub common: NodeCommon, + #[cultcache(key = 1)] + pub min: f64, + #[cultcache(key = 2)] + pub max: f64, + #[cultcache(key = 3)] + pub step: f64, + #[cultcache(key = 4)] + pub value: NumberSource, + #[cultcache(key = 5)] + pub commit: CommandRef, +} + +#[derive(Clone, Debug, PartialEq, CultUnion)] +pub enum InputKind { + #[cultcache(key = 0)] + Text, + #[cultcache(key = 1)] + Number, +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct TextInputNode { + #[cultcache(key = 0)] + pub common: NodeCommon, + #[cultcache(key = 1)] + pub multiline: bool, + #[cultcache(key = 2)] + pub kind: InputKind, + #[cultcache(key = 3)] + pub placeholder: TextSource, + #[cultcache(key = 4)] + pub initial: TextSource, + #[cultcache(key = 5)] + pub submit: CommandRef, +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct SelectOption { + #[cultcache(key = 0)] + pub value: String, + #[cultcache(key = 1)] + pub label: String, +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct LiteralOptions { + #[cultcache(key = 0)] + pub options: Vec, +} + +/// Options read from a collection: each item's value and label are members of the item. +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct BoundOptions { + #[cultcache(key = 0)] + pub collection: CollectionSource, + #[cultcache(key = 1)] + pub value_member: MemberPath, + #[cultcache(key = 2)] + pub label_member: MemberPath, +} + +#[derive(Clone, Debug, PartialEq, CultUnion)] +pub enum OptionList { + #[cultcache(key = 0)] + Literal(LiteralOptions), + #[cultcache(key = 1)] + Bound(BoundOptions), +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct SelectNode { + #[cultcache(key = 0)] + pub common: NodeCommon, + #[cultcache(key = 1)] + pub options: OptionList, + #[cultcache(key = 2)] + pub value: Bind, + #[cultcache(key = 3)] + pub on_change: CommandRef, +} + +/// The template is instantiated once per item of the collection, keyed by `key_member` of the item. +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct RepeatNode { + #[cultcache(key = 0)] + pub common: NodeCommon, + #[cultcache(key = 1)] + pub collection: CollectionSource, + #[cultcache(key = 2)] + pub key_member: MemberPath, + #[cultcache(key = 3)] + pub template: std::boxed::Box, +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct AnchorNode { + #[cultcache(key = 0)] + pub node: NodeId, +} + +#[derive(Clone, Debug, PartialEq, CultUnion)] +pub enum Anchor { + #[cultcache(key = 0)] + Viewport, + #[cultcache(key = 1)] + Node(AnchorNode), +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct OverlayNode { + #[cultcache(key = 0)] + pub common: NodeCommon, + #[cultcache(key = 1)] + pub anchor: Anchor, + #[cultcache(key = 2)] + pub modal: bool, + #[cultcache(key = 3)] + pub dismiss: CommandRef, + #[cultcache(key = 4)] + pub children: Vec, +} + +#[derive(Clone, Debug, PartialEq, CultUnion)] +pub enum PlotMark { + #[cultcache(key = 0)] + Line, + #[cultcache(key = 1)] + Bar, + #[cultcache(key = 2)] + Area, +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct PlotNode { + #[cultcache(key = 0)] + pub common: NodeCommon, + /// A numeric list member of a record. + #[cultcache(key = 1)] + pub series: Bind, + #[cultcache(key = 2)] + pub mark: PlotMark, + #[cultcache(key = 3)] + pub x_min: Option, + #[cultcache(key = 4)] + pub x_max: Option, + #[cultcache(key = 5)] + pub y_min: Option, + #[cultcache(key = 6)] + pub y_max: Option, +} + +/// A leaf the host draws: `slot_kind` is a name the host declares it supports. +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct EmbedNode { + #[cultcache(key = 0)] + pub common: NodeCommon, + #[cultcache(key = 1)] + pub slot_kind: String, + #[cultcache(key = 2)] + pub slot_id: String, + #[cultcache(key = 3)] + pub width: f64, + #[cultcache(key = 4)] + pub height: f64, +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct CompositionRef { + #[cultcache(key = 0)] + pub publisher: String, + #[cultcache(key = 1)] + pub name: String, + #[cultcache(key = 2)] + pub version: i32, +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct UseNode { + #[cultcache(key = 0)] + pub common: NodeCommon, + #[cultcache(key = 1)] + pub composition: CompositionRef, + #[cultcache(key = 2)] + pub args: Vec, +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct Arg { + #[cultcache(key = 0)] + pub name: String, + #[cultcache(key = 1)] + pub value: ArgValue, +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct TextArg { + #[cultcache(key = 0)] + pub value: String, +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct NumberArg { + #[cultcache(key = 0)] + pub value: f64, +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct BoolArg { + #[cultcache(key = 0)] + pub value: bool, +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct CollectionArg { + #[cultcache(key = 0)] + pub source: CollectionSource, +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct CommandArg { + #[cultcache(key = 0)] + pub command: CommandRef, +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct SlotArg { + #[cultcache(key = 0)] + pub nodes: Vec, +} + +#[derive(Clone, Debug, PartialEq, CultUnion)] +pub enum ArgValue { + #[cultcache(key = 0)] + Text(TextArg), + #[cultcache(key = 1)] + Number(NumberArg), + #[cultcache(key = 2)] + Bool(BoolArg), + #[cultcache(key = 3)] + Bind(Bind), + #[cultcache(key = 4)] + Collection(CollectionArg), + #[cultcache(key = 5)] + Command(CommandArg), + #[cultcache(key = 6)] + Slot(SlotArg), +} + +#[derive(Clone, Debug, PartialEq, CultUnion)] +pub enum ParamKind { + #[cultcache(key = 0)] + Text, + #[cultcache(key = 1)] + Number, + #[cultcache(key = 2)] + Bool, + #[cultcache(key = 3)] + Bind, + #[cultcache(key = 4)] + Collection, + #[cultcache(key = 5)] + Command, + #[cultcache(key = 6)] + Slot, +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct ParamDecl { + #[cultcache(key = 0)] + pub name: String, + #[cultcache(key = 1)] + pub kind: ParamKind, +} + +/// A provider's surface: a tree of nodes and the commands it declares. A change of structure +/// republishes the record with a higher `version`. +#[derive(Clone, Debug, PartialEq, DatabaseEntry)] +#[cultcache(type = "gamecult.thing.surface", schema = "gamecult.thing.surface.v1")] +pub struct SurfaceDocument { + #[cultcache(key = 0)] + pub provider_id: ProviderId, + #[cultcache(key = 1)] + pub label: String, + #[cultcache(key = 2)] + pub version: i64, + #[cultcache(key = 3)] + pub title: String, + #[cultcache(key = 4)] + pub root: Node, + #[cultcache(key = 5)] + pub commands: Vec, +} + +impl SurfaceDocument { + pub fn record_key(&self) -> String { + surface_key(&self.provider_id, &self.label) + } +} + +/// A reusable subtree with typed parameters. Compositions never own commands. +#[derive(Clone, Debug, PartialEq, DatabaseEntry)] +#[cultcache(type = "gamecult.thing.composition", schema = "gamecult.thing.composition.v1")] +pub struct CompositionDocument { + #[cultcache(key = 0)] + pub publisher: String, + #[cultcache(key = 1)] + pub name: String, + #[cultcache(key = 2)] + pub version: i32, + #[cultcache(key = 3)] + pub params: Vec, + #[cultcache(key = 4)] + pub body: Node, +} + +impl CompositionDocument { + pub fn record_key(&self) -> String { + composition_key(&self.publisher, &self.name, self.version) + } +} diff --git a/crates/thing-contract/src/result.rs b/crates/thing-contract/src/result.rs new file mode 100644 index 0000000..647ba11 --- /dev/null +++ b/crates/thing-contract/src/result.rs @@ -0,0 +1,185 @@ +//! The answer to a command invocation. A `CommandResult` is built only by its four +//! constructors, each of which copies the invocation's id, and is read through accessors. +//! Nothing here takes a state as a string, and nothing builds a result from a map. + +use cultcache_rs::{CultSlots, CultUnion, DatabaseEntry}; + +use crate::command::{CommandInvocation, CommandRef}; +use crate::keys::{NodeId, result_key}; + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct PendingOutcome { + #[cultcache(key = 0)] + pub message: String, +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct CompletedOutcome { + #[cultcache(key = 0)] + pub source_version: Option, + /// Whether the captured drafts of the invocation clear. + #[cultcache(key = 1)] + pub clear_drafts: bool, + #[cultcache(key = 2)] + pub effect: Option, +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct DeniedOutcome { + #[cultcache(key = 0)] + pub reason: String, + #[cultcache(key = 1)] + pub node: Option, +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct FailedOutcome { + #[cultcache(key = 0)] + pub reason: String, +} + +#[derive(Clone, Debug, PartialEq, CultUnion)] +pub enum Outcome { + #[cultcache(key = 0)] + Pending(PendingOutcome), + #[cultcache(key = 1)] + Completed(CompletedOutcome), + #[cultcache(key = 2)] + Denied(DeniedOutcome), + #[cultcache(key = 3)] + Failed(FailedOutcome), +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct OpenSurfaceEffect { + #[cultcache(key = 0)] + pub key: String, +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct OpenExternalEffect { + #[cultcache(key = 0)] + pub url: String, + /// The command run when the external step returns. + #[cultcache(key = 1)] + pub resume: CommandRef, +} + +#[derive(Clone, Debug, PartialEq, CultUnion)] +pub enum Effect { + #[cultcache(key = 0)] + OpenSurface(OpenSurfaceEffect), + #[cultcache(key = 1)] + OpenExternal(OpenExternalEffect), +} + +/// A provider's answer to one invocation. `seq` counts from 1 per invocation. +/// +/// The positive case, a result built by a constructor from the invocation it answers: +/// +/// ``` +/// use thing_contract::{CommandInvocation, CommandResult}; +/// fn answer(invocation: &CommandInvocation) -> CommandResult { +/// CommandResult::failed(invocation, 1, "refused") +/// } +/// ``` +/// +/// A struct literal does not compile, because the fields are private: +/// +/// ```compile_fail,E0451 +/// use thing_contract::{CommandResult, Outcome, FailedOutcome}; +/// let _ = CommandResult { +/// invocation_id: String::new(), +/// seq: 1, +/// outcome: Outcome::Failed(FailedOutcome { reason: String::new() }), +/// }; +/// ``` +/// +/// Neither does assigning the outcome: +/// +/// ```compile_fail,E0616 +/// use thing_contract::{CommandInvocation, CommandResult, Outcome, FailedOutcome}; +/// fn tamper(invocation: &CommandInvocation) { +/// let mut result = CommandResult::failed(invocation, 1, "refused"); +/// result.outcome = Outcome::Failed(FailedOutcome { reason: String::new() }); +/// } +/// ``` +/// +/// Nothing converts a map into a result: +/// +/// ```compile_fail,E0277 +/// use std::collections::BTreeMap; +/// use thing_contract::CommandResult; +/// let flat: BTreeMap = BTreeMap::new(); +/// let _ = CommandResult::from(flat); +/// ``` +/// +/// Nor a string state: +/// +/// ```compile_fail,E0277 +/// use thing_contract::CommandResult; +/// let _ = CommandResult::from("completed"); +/// ``` +#[derive(Clone, Debug, PartialEq, DatabaseEntry)] +#[cultcache(type = "gamecult.thing.command_result", schema = "gamecult.thing.command_result.v1")] +pub struct CommandResult { + #[cultcache(key = 0)] + invocation_id: String, + #[cultcache(key = 1)] + seq: i32, + #[cultcache(key = 2)] + outcome: Outcome, +} + +impl CommandResult { + fn answering(invocation: &CommandInvocation, seq: i32, outcome: Outcome) -> Self { + Self { invocation_id: invocation.invocation_id.clone(), seq, outcome } + } + + pub fn pending(invocation: &CommandInvocation, seq: i32, message: impl Into) -> Self { + Self::answering(invocation, seq, Outcome::Pending(PendingOutcome { message: message.into() })) + } + + pub fn completed( + invocation: &CommandInvocation, + seq: i32, + source_version: Option, + clear_drafts: bool, + effect: Option, + ) -> Self { + Self::answering( + invocation, + seq, + Outcome::Completed(CompletedOutcome { source_version, clear_drafts, effect }), + ) + } + + pub fn denied( + invocation: &CommandInvocation, + seq: i32, + reason: impl Into, + node: Option, + ) -> Self { + Self::answering(invocation, seq, Outcome::Denied(DeniedOutcome { reason: reason.into(), node })) + } + + pub fn failed(invocation: &CommandInvocation, seq: i32, reason: impl Into) -> Self { + Self::answering(invocation, seq, Outcome::Failed(FailedOutcome { reason: reason.into() })) + } + + pub fn invocation_id(&self) -> &str { + &self.invocation_id + } + + pub fn seq(&self) -> i32 { + self.seq + } + + pub fn outcome(&self) -> &Outcome { + &self.outcome + } + + pub fn record_key(&self) -> String { + result_key(&self.invocation_id, self.seq) + } +} diff --git a/crates/thing-contract/src/source.rs b/crates/thing-contract/src/source.rs new file mode 100644 index 0000000..59d48e3 --- /dev/null +++ b/crates/thing-contract/src/source.rs @@ -0,0 +1,169 @@ +//! Where a value comes from: a literal, a binding to a state record, or a composition parameter. +//! A member path is a list of names and indices, never an expression. + +use cultcache_rs::{CultSlots, CultUnion}; + +use crate::keys::{ProviderId, RecordKey, SchemaId}; + +/// A composition parameter, named. +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct ParamName { + #[cultcache(key = 0)] + pub name: String, +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct TextLiteral { + #[cultcache(key = 0)] + pub value: String, +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct NumberLiteral { + #[cultcache(key = 0)] + pub value: f64, +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct BoolLiteral { + #[cultcache(key = 0)] + pub value: bool, +} + +#[derive(Clone, Debug, PartialEq, CultUnion)] +pub enum TextSource { + #[cultcache(key = 0)] + Literal(TextLiteral), + #[cultcache(key = 1)] + Bound(Bind), + #[cultcache(key = 2)] + Param(ParamName), +} + +#[derive(Clone, Debug, PartialEq, CultUnion)] +pub enum NumberSource { + #[cultcache(key = 0)] + Literal(NumberLiteral), + #[cultcache(key = 1)] + Bound(Bind), + #[cultcache(key = 2)] + Param(ParamName), +} + +#[derive(Clone, Debug, PartialEq, CultUnion)] +pub enum BoolSource { + #[cultcache(key = 0)] + Literal(BoolLiteral), + #[cultcache(key = 1)] + Bound(Bind), + #[cultcache(key = 2)] + Param(ParamName), +} + +/// One member of a record, read through a path, optionally formatted. +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct Bind { + #[cultcache(key = 0)] + pub source: StateSource, + #[cultcache(key = 1)] + pub member: MemberPath, + #[cultcache(key = 2)] + pub format: Option, +} + +#[derive(Clone, Debug, PartialEq, CultUnion)] +pub enum StateSource { + #[cultcache(key = 0)] + Record(StateRef), + /// The current item inside a `Repeat` template. + #[cultcache(key = 1)] + Item, +} + +/// A state record. An absent provider is the publisher of the document. +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct StateRef { + #[cultcache(key = 0)] + pub provider: Option, + #[cultcache(key = 1)] + pub schema: SchemaId, + #[cultcache(key = 2)] + pub key: RecordKey, +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct MemberPath { + #[cultcache(key = 0)] + pub steps: Vec, +} + +#[derive(Clone, Debug, PartialEq, CultUnion)] +pub enum MemberStep { + #[cultcache(key = 0)] + Name(MemberName), + #[cultcache(key = 1)] + Index(MemberIndex), +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct MemberName { + #[cultcache(key = 0)] + pub name: String, +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct MemberIndex { + #[cultcache(key = 0)] + pub index: i32, +} + +#[derive(Clone, Debug, PartialEq, CultUnion)] +pub enum Format { + #[cultcache(key = 0)] + Number(NumberFormat), + #[cultcache(key = 1)] + Duration, + #[cultcache(key = 2)] + Timestamp, + #[cultcache(key = 3)] + Affix(AffixFormat), +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct NumberFormat { + #[cultcache(key = 0)] + pub precision: i32, + #[cultcache(key = 1)] + pub percent: bool, + #[cultcache(key = 2)] + pub unit: Option, +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct AffixFormat { + #[cultcache(key = 0)] + pub prefix: String, + #[cultcache(key = 1)] + pub suffix: String, +} + +/// The records of one type, or a list member of one record. +#[derive(Clone, Debug, PartialEq, CultUnion)] +pub enum CollectionSource { + #[cultcache(key = 0)] + Records(RecordsSource), + #[cultcache(key = 1)] + Member(Bind), + #[cultcache(key = 2)] + Param(ParamName), +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct RecordsSource { + #[cultcache(key = 0)] + pub provider: Option, + #[cultcache(key = 1)] + pub schema: SchemaId, + #[cultcache(key = 2)] + pub key_prefix: String, +} diff --git a/crates/thing-contract/src/style.rs b/crates/thing-contract/src/style.rs new file mode 100644 index 0000000..1869753 --- /dev/null +++ b/crates/thing-contract/src/style.rs @@ -0,0 +1,241 @@ +//! Box layout and paint as plain data. The crate holds no layout engine: core maps `BoxStyle` +//! onto its solver. + +use cultcache_rs::{CultSlots, CultUnion}; + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct Scalar { + #[cultcache(key = 0)] + pub value: f64, +} + +#[derive(Clone, Debug, PartialEq, CultUnion)] +pub enum Dimension { + #[cultcache(key = 0)] + Px(Scalar), + #[cultcache(key = 1)] + Percent(Scalar), + #[cultcache(key = 2)] + Fr(Scalar), + #[cultcache(key = 3)] + Auto, + #[cultcache(key = 4)] + Content, +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct Size2 { + #[cultcache(key = 0)] + pub width: Dimension, + #[cultcache(key = 1)] + pub height: Dimension, +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct Edges { + #[cultcache(key = 0)] + pub top: Dimension, + #[cultcache(key = 1)] + pub right: Dimension, + #[cultcache(key = 2)] + pub bottom: Dimension, + #[cultcache(key = 3)] + pub left: Dimension, +} + +#[derive(Clone, Debug, PartialEq, CultUnion)] +pub enum Display { + #[cultcache(key = 0)] + Flex, + #[cultcache(key = 1)] + Grid, + #[cultcache(key = 2)] + Block, +} + +#[derive(Clone, Debug, PartialEq, CultUnion)] +pub enum Direction { + #[cultcache(key = 0)] + Row, + #[cultcache(key = 1)] + Column, + #[cultcache(key = 2)] + RowReverse, + #[cultcache(key = 3)] + ColumnReverse, +} + +#[derive(Clone, Debug, PartialEq, CultUnion)] +pub enum Wrap { + #[cultcache(key = 0)] + NoWrap, + #[cultcache(key = 1)] + Wrap, + #[cultcache(key = 2)] + WrapReverse, +} + +#[derive(Clone, Debug, PartialEq, CultUnion)] +pub enum Position { + #[cultcache(key = 0)] + Relative, + #[cultcache(key = 1)] + Absolute, +} + +#[derive(Clone, Debug, PartialEq, CultUnion)] +pub enum Overflow { + #[cultcache(key = 0)] + Visible, + #[cultcache(key = 1)] + Clip, + #[cultcache(key = 2)] + Scroll, +} + +#[derive(Clone, Debug, PartialEq, CultUnion)] +pub enum ItemAlign { + #[cultcache(key = 0)] + Auto, + #[cultcache(key = 1)] + Start, + #[cultcache(key = 2)] + End, + #[cultcache(key = 3)] + Center, + #[cultcache(key = 4)] + Stretch, + #[cultcache(key = 5)] + Baseline, +} + +#[derive(Clone, Debug, PartialEq, CultUnion)] +pub enum ContentAlign { + #[cultcache(key = 0)] + Auto, + #[cultcache(key = 1)] + Start, + #[cultcache(key = 2)] + End, + #[cultcache(key = 3)] + Center, + #[cultcache(key = 4)] + Stretch, + #[cultcache(key = 5)] + SpaceBetween, + #[cultcache(key = 6)] + SpaceAround, + #[cultcache(key = 7)] + SpaceEvenly, +} + +/// The layout of a box. `gap` is the column gap in `width` and the row gap in `height`. +/// `grid_columns` and `grid_rows` are the grid tracks and are read only when `display` is Grid. +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct BoxStyle { + #[cultcache(key = 0)] + pub display: Display, + #[cultcache(key = 1)] + pub direction: Direction, + #[cultcache(key = 2)] + pub wrap: Wrap, + #[cultcache(key = 3)] + pub gap: Size2, + #[cultcache(key = 4)] + pub padding: Edges, + #[cultcache(key = 5)] + pub margin: Edges, + #[cultcache(key = 6)] + pub size: Size2, + #[cultcache(key = 7)] + pub min_size: Size2, + #[cultcache(key = 8)] + pub max_size: Size2, + #[cultcache(key = 9)] + pub position: Position, + #[cultcache(key = 10)] + pub inset: Edges, + #[cultcache(key = 11)] + pub overflow: Overflow, + #[cultcache(key = 12)] + pub align_items: ItemAlign, + #[cultcache(key = 13)] + pub align_self: ItemAlign, + #[cultcache(key = 14)] + pub align_content: ContentAlign, + #[cultcache(key = 15)] + pub justify_content: ContentAlign, + #[cultcache(key = 16)] + pub grow: f64, + #[cultcache(key = 17)] + pub shrink: f64, + #[cultcache(key = 18)] + pub basis: Dimension, + #[cultcache(key = 19)] + pub grid_columns: Vec, + #[cultcache(key = 20)] + pub grid_rows: Vec, +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct ThemeToken { + #[cultcache(key = 0)] + pub name: String, +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct Rgba { + #[cultcache(key = 0)] + pub r: u8, + #[cultcache(key = 1)] + pub g: u8, + #[cultcache(key = 2)] + pub b: u8, + #[cultcache(key = 3)] + pub a: u8, +} + +#[derive(Clone, Debug, PartialEq, CultUnion)] +pub enum ColorRef { + #[cultcache(key = 0)] + Token(ThemeToken), + #[cultcache(key = 1)] + Rgba(Rgba), +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct Border { + #[cultcache(key = 0)] + pub color: ColorRef, + #[cultcache(key = 1)] + pub width: f64, +} + +#[derive(Clone, Debug, PartialEq, CultSlots)] +pub struct Paint { + #[cultcache(key = 0)] + pub background: Option, + #[cultcache(key = 1)] + pub border: Option, + #[cultcache(key = 2)] + pub radius: f64, +} + +/// The accessibility role of a box. The accessibility tree itself is core's. +#[derive(Clone, Debug, PartialEq, CultUnion)] +pub enum Role { + #[cultcache(key = 0)] + Group, + #[cultcache(key = 1)] + Region, + #[cultcache(key = 2)] + List, + #[cultcache(key = 3)] + ListItem, + #[cultcache(key = 4)] + Heading, + #[cultcache(key = 5)] + Dialog, + #[cultcache(key = 6)] + Toolbar, +}