Skip to main content

concinnity_core/components/
variables.rs

1// Variables schema: the world's shared, typed variable table.
2
3use alloc::string::String;
4use alloc::vec::Vec;
5
6use crate::components::BehaviorLiteral;
7use crate::ecs::asset_id::AssetId;
8
9/// The world's shared variables: the state [Behavior](#behavior)s read with
10/// `var` and write with `set`, and the state a `save` node persists.
11///
12/// Declaring this asset makes the table authoritative: every variable a
13/// behavior names must appear here, and its declared value fixes both the
14/// variable's type and its starting value. A world without a `Variables` asset
15/// keeps every variable implicit and integer-typed, so declaring one is how a
16/// world opts into typed variables and into catching misspelled names at build
17/// time.
18///
19/// Variables are world-scoped and shared. Per-entity state belongs in a
20/// behavior's `locals`, which are typed the same way but private to one entity
21/// and never persisted.
22#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
23#[serde(default)]
24pub struct Variables {
25    /// Asset identity; injected via `inject_name`. Not part of `args`.
26    #[serde(skip)]
27    pub asset_id: AssetId,
28    /// Every variable the world declares.
29    pub vars: Vec<VariableDecl>,
30}
31
32/// One variable declared by the world's [Variables](#variables). The declared
33/// value fixes both the variable's type and the value it holds at world start.
34#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
35#[serde(default)]
36pub struct VariableDecl {
37    /// The name behaviors read and write the variable by.
38    pub name: String,
39    /// The variable's type and starting value.
40    pub value: BehaviorLiteral,
41}
42
43#[cfg(test)]
44mod tests {
45    use super::*;
46
47    #[test]
48    fn defaults_declare_nothing() {
49        assert!(Variables::default().vars.is_empty());
50    }
51
52    #[test]
53    fn typed_declarations_parse() {
54        let v: Variables = serde_json::from_str(
55            r#"{"vars":[{"name":"health","value":{"float":100.0}},
56                       {"name":"spawn","value":{"vec3":[0,1,0]}}]}"#,
57        )
58        .expect("variables parse");
59        assert_eq!(v.vars.len(), 2);
60        assert_eq!(v.vars[0].name, "health");
61        assert_eq!(v.vars[0].value, BehaviorLiteral::Float(100.0));
62        assert_eq!(v.vars[1].value, BehaviorLiteral::Vec3([0.0, 1.0, 0.0]));
63    }
64
65    #[test]
66    fn round_trips_through_postcard() {
67        let v: Variables =
68            serde_json::from_str(r#"{"vars":[{"name":"n","value":{"int":3}}]}"#).unwrap();
69        let bytes = postcard::to_allocvec(&v).expect("encodes");
70        let back: Variables = postcard::from_bytes(&bytes).expect("decodes");
71        assert_eq!(back.vars[0].name, "n");
72        assert_eq!(back.vars[0].value, BehaviorLiteral::Int(3));
73    }
74}