concinnity_world/spec/
mod.rs1pub mod asset;
11
12mod json;
13
14pub use json::{arg_value_to_json, spec_args, spec_to_value};
15
16#[derive(Clone, Debug, PartialEq)]
21pub enum ArgValue {
22 Null,
24 Bool(bool),
26 Int(i64),
28 Float(f64),
30 Str(String),
32 Array(Vec<ArgValue>),
34 Object(Vec<(String, ArgValue)>),
36}
37
38impl ArgValue {
39 pub fn floats(vals: &[f32]) -> ArgValue {
41 ArgValue::Array(vals.iter().map(|&v| ArgValue::Float(v as f64)).collect())
42 }
43}
44
45impl From<bool> for ArgValue {
46 fn from(v: bool) -> Self {
47 ArgValue::Bool(v)
48 }
49}
50impl From<i64> for ArgValue {
51 fn from(v: i64) -> Self {
52 ArgValue::Int(v)
53 }
54}
55impl From<u32> for ArgValue {
56 fn from(v: u32) -> Self {
57 ArgValue::Int(v as i64)
58 }
59}
60impl From<usize> for ArgValue {
61 fn from(v: usize) -> Self {
62 ArgValue::Int(v as i64)
63 }
64}
65impl From<f32> for ArgValue {
66 fn from(v: f32) -> Self {
67 ArgValue::Float(v as f64)
68 }
69}
70impl From<f64> for ArgValue {
71 fn from(v: f64) -> Self {
72 ArgValue::Float(v)
73 }
74}
75impl From<&str> for ArgValue {
76 fn from(v: &str) -> Self {
77 ArgValue::Str(String::from(v))
78 }
79}
80impl From<String> for ArgValue {
81 fn from(v: String) -> Self {
82 ArgValue::Str(v)
83 }
84}
85impl<const N: usize> From<[f32; N]> for ArgValue {
86 fn from(v: [f32; N]) -> Self {
87 ArgValue::floats(&v)
88 }
89}
90
91#[derive(Clone, Debug, PartialEq)]
93pub struct AssetSpec {
94 pub name: String,
97 pub asset_type: &'static str,
99 pub fields: Vec<(String, ArgValue)>,
101}
102
103impl AssetSpec {
104 pub fn new(name: impl Into<String>, asset_type: &'static str) -> Self {
107 AssetSpec {
108 name: name.into(),
109 asset_type,
110 fields: Vec::new(),
111 }
112 }
113
114 pub fn set(mut self, key: impl Into<String>, value: impl Into<ArgValue>) -> Self {
117 self.fields.push((key.into(), value.into()));
118 self
119 }
120
121 pub fn args(&self) -> ArgValue {
123 ArgValue::Object(self.fields.clone())
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 #[test]
132 fn set_records_fields_in_order() {
133 let spec = AssetSpec::new("lamp", "PointLight")
134 .set("intensity", 8.0f32)
135 .set("range", 20.0f32);
136 assert_eq!(spec.name, "lamp");
137 assert_eq!(spec.asset_type, "PointLight");
138 assert_eq!(
139 spec.fields
140 .iter()
141 .map(|(k, _)| k.as_str())
142 .collect::<Vec<_>>(),
143 vec!["intensity", "range"]
144 );
145 }
146
147 #[test]
148 fn conversions_pick_the_expected_variant() {
149 assert_eq!(ArgValue::from(true), ArgValue::Bool(true));
150 assert_eq!(ArgValue::from(48u32), ArgValue::Int(48));
151 assert_eq!(ArgValue::from(1.5f32), ArgValue::Float(1.5));
152 assert_eq!(ArgValue::from("sky"), ArgValue::Str("sky".to_string()));
153 assert_eq!(
154 ArgValue::from([1.0f32, 0.0, 0.0]),
155 ArgValue::Array(vec![
156 ArgValue::Float(1.0),
157 ArgValue::Float(0.0),
158 ArgValue::Float(0.0),
159 ])
160 );
161 }
162
163 #[test]
164 fn args_wraps_fields_as_an_object() {
165 let spec = AssetSpec::new("s", "Sprite").set("visible", false);
166 assert_eq!(
167 spec.args(),
168 ArgValue::Object(vec![("visible".to_string(), ArgValue::Bool(false))])
169 );
170 }
171}