Skip to main content

concinnity_core/behavior/
value.rs

1// The values a behavior body computes over, and the two operator vocabularies
2// its arithmetic and comparisons are expressed in.
3
4use crate::components::BehaviorLiteral;
5use crate::ecs::{Entity, TraceVal};
6
7/// A value flowing through a behavior body.
8#[derive(Clone, Copy, Debug, PartialEq)]
9pub enum Val {
10    /// A boolean.
11    Bool(bool),
12    /// A signed integer.
13    Int(i32),
14    /// A float.
15    Float(f32),
16    /// A 3-component vector.
17    Vec3([f32; 3]),
18    /// An entity handle.
19    Entity(Entity),
20}
21
22impl Val {
23    pub(crate) fn as_bool(self) -> Option<bool> {
24        match self {
25            Val::Bool(b) => Some(b),
26            _ => None,
27        }
28    }
29
30    pub(crate) fn as_entity(self) -> Option<Entity> {
31        match self {
32            Val::Entity(e) => Some(e),
33            _ => None,
34        }
35    }
36
37    pub(crate) fn as_vec3(self) -> Option<[f32; 3]> {
38        match self {
39            Val::Vec3(v) => Some(v),
40            _ => None,
41        }
42    }
43
44    /// The scalar reading of a numeric value, for mixed vector/scalar forms.
45    pub fn as_f32(self) -> Option<f32> {
46        match self {
47            Val::Int(i) => Some(i as f32),
48            Val::Float(f) => Some(f),
49            _ => None,
50        }
51    }
52
53    /// The runtime value an authored literal denotes.
54    pub fn from_literal(lit: &BehaviorLiteral) -> Val {
55        match *lit {
56            BehaviorLiteral::Bool(b) => Val::Bool(b),
57            BehaviorLiteral::Int(i) => Val::Int(i),
58            BehaviorLiteral::Float(f) => Val::Float(f),
59            BehaviorLiteral::Vec3(v) => Val::Vec3(v),
60        }
61    }
62
63    /// The authored form, for persistence. Entities have no authored form and
64    /// are never persisted, so they save as their declared-away default.
65    pub fn to_literal(self) -> BehaviorLiteral {
66        match self {
67            Val::Bool(b) => BehaviorLiteral::Bool(b),
68            Val::Int(i) => BehaviorLiteral::Int(i),
69            Val::Float(f) => BehaviorLiteral::Float(f),
70            Val::Vec3(v) => BehaviorLiteral::Vec3(v),
71            Val::Entity(_) => BehaviorLiteral::Int(0),
72        }
73    }
74
75    /// The cross-boundary form execution tracing publishes.
76    pub fn to_trace(self) -> TraceVal {
77        match self {
78            Val::Bool(b) => TraceVal::Bool(b),
79            Val::Int(i) => TraceVal::Int(i),
80            Val::Float(f) => TraceVal::Float(f),
81            Val::Vec3(v) => TraceVal::Vec3(v),
82            Val::Entity(e) => TraceVal::Entity(e.to_bits()),
83        }
84    }
85
86    /// Whether two values are the same shape, so a restored save can be
87    /// rejected when the world's declaration changed type under it.
88    pub fn same_type(self, other: Val) -> bool {
89        core::mem::discriminant(&self) == core::mem::discriminant(&other)
90    }
91}
92
93/// How two numbers combine.
94#[derive(Clone, Copy, Debug)]
95pub enum Arith {
96    /// Sum.
97    Add,
98    /// Difference.
99    Sub,
100    /// Product.
101    Mul,
102    /// Quotient.
103    Div,
104}
105
106/// How two values compare.
107#[derive(Clone, Copy, Debug)]
108pub enum Cmp {
109    /// Equal.
110    Eq,
111    /// Not equal.
112    Ne,
113    /// Less than.
114    Lt,
115    /// Less than or equal.
116    Le,
117    /// Greater than.
118    Gt,
119    /// Greater than or equal.
120    Ge,
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126    use core::num::NonZeroU32;
127
128    fn entity() -> Entity {
129        Entity::new(7, NonZeroU32::MIN)
130    }
131
132    // Each reader answers for its own shape and declines every other, so a
133    // mistyped expression yields nothing rather than a coerced guess.
134    #[test]
135    fn each_reader_answers_only_for_its_own_shape() {
136        assert_eq!(Val::Bool(true).as_bool(), Some(true));
137        assert_eq!(Val::Int(1).as_bool(), None);
138
139        assert_eq!(Val::Entity(entity()).as_entity(), Some(entity()));
140        assert_eq!(Val::Int(1).as_entity(), None);
141
142        assert_eq!(Val::Vec3([1.0; 3]).as_vec3(), Some([1.0; 3]));
143        assert_eq!(Val::Int(1).as_vec3(), None);
144    }
145
146    // The scalar reading is the one that widens: an int and a float both read
147    // as numbers, and nothing else does.
148    #[test]
149    fn the_scalar_reading_covers_both_numeric_shapes() {
150        assert_eq!(Val::Int(3).as_f32(), Some(3.0));
151        assert_eq!(Val::Float(0.5).as_f32(), Some(0.5));
152        assert_eq!(Val::Bool(true).as_f32(), None);
153        assert_eq!(Val::Vec3([1.0; 3]).as_f32(), None);
154        assert_eq!(Val::Entity(entity()).as_f32(), None);
155    }
156
157    #[test]
158    fn a_literal_round_trips_through_its_runtime_value() {
159        for lit in [
160            BehaviorLiteral::Bool(true),
161            BehaviorLiteral::Int(-2),
162            BehaviorLiteral::Float(1.5),
163            BehaviorLiteral::Vec3([1.0, 2.0, 3.0]),
164        ] {
165            assert_eq!(Val::from_literal(&lit).to_literal(), lit);
166        }
167    }
168
169    // An entity is a runtime-only identity with no authored form, so it saves
170    // as the declared-away default rather than as a handle a later run would
171    // misread.
172    #[test]
173    fn an_entity_persists_as_the_declared_away_default() {
174        assert_eq!(Val::Entity(entity()).to_literal(), BehaviorLiteral::Int(0));
175    }
176
177    #[test]
178    fn every_value_has_a_trace_form() {
179        assert_eq!(Val::Bool(true).to_trace(), TraceVal::Bool(true));
180        assert_eq!(Val::Int(-2).to_trace(), TraceVal::Int(-2));
181        assert_eq!(Val::Float(1.5).to_trace(), TraceVal::Float(1.5));
182        assert_eq!(
183            Val::Vec3([1.0, 2.0, 3.0]).to_trace(),
184            TraceVal::Vec3([1.0, 2.0, 3.0])
185        );
186        assert_eq!(
187            Val::Entity(entity()).to_trace(),
188            TraceVal::Entity(entity().to_bits())
189        );
190    }
191
192    // Shape, not value: a restored save whose declaration changed type under
193    // it is rejected, while one that merely changed value is not.
194    #[test]
195    fn same_type_compares_shape_rather_than_value() {
196        assert!(Val::Int(1).same_type(Val::Int(9)));
197        assert!(!Val::Int(1).same_type(Val::Float(1.0)));
198        assert!(!Val::Bool(true).same_type(Val::Int(1)));
199    }
200}