pub mod asset;
mod json;
pub use json::{arg_value_to_json, spec_args, spec_to_value};
#[derive(Clone, Debug, PartialEq)]
pub enum ArgValue {
Null,
Bool(bool),
Int(i64),
Float(f64),
Str(String),
Array(Vec<ArgValue>),
Object(Vec<(String, ArgValue)>),
}
impl ArgValue {
pub(crate) fn floats(vals: &[f32]) -> ArgValue {
ArgValue::Array(vals.iter().map(|&v| ArgValue::Float(v as f64)).collect())
}
}
impl From<bool> for ArgValue {
fn from(v: bool) -> Self {
ArgValue::Bool(v)
}
}
impl From<i64> for ArgValue {
fn from(v: i64) -> Self {
ArgValue::Int(v)
}
}
impl From<u32> for ArgValue {
fn from(v: u32) -> Self {
ArgValue::Int(v as i64)
}
}
impl From<usize> for ArgValue {
fn from(v: usize) -> Self {
ArgValue::Int(v as i64)
}
}
impl From<f32> for ArgValue {
fn from(v: f32) -> Self {
ArgValue::Float(v as f64)
}
}
impl From<f64> for ArgValue {
fn from(v: f64) -> Self {
ArgValue::Float(v)
}
}
impl From<&str> for ArgValue {
fn from(v: &str) -> Self {
ArgValue::Str(String::from(v))
}
}
impl From<String> for ArgValue {
fn from(v: String) -> Self {
ArgValue::Str(v)
}
}
impl<const N: usize> From<[f32; N]> for ArgValue {
fn from(v: [f32; N]) -> Self {
ArgValue::floats(&v)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct AssetSpec {
pub name: String,
pub asset_type: &'static str,
pub fields: Vec<(String, ArgValue)>,
}
impl AssetSpec {
pub(crate) fn new(name: impl Into<String>, asset_type: &'static str) -> Self {
AssetSpec {
name: name.into(),
asset_type,
fields: Vec::new(),
}
}
pub fn set(mut self, key: impl Into<String>, value: impl Into<ArgValue>) -> Self {
self.fields.push((key.into(), value.into()));
self
}
#[cfg(test)]
pub(crate) fn args(&self) -> ArgValue {
ArgValue::Object(self.fields.clone())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn set_records_fields_in_order() {
let spec = AssetSpec::new("lamp", "PointLight")
.set("intensity", 8.0f32)
.set("range", 20.0f32);
assert_eq!(spec.name, "lamp");
assert_eq!(spec.asset_type, "PointLight");
assert_eq!(
spec.fields
.iter()
.map(|(k, _)| k.as_str())
.collect::<Vec<_>>(),
vec!["intensity", "range"]
);
}
#[test]
fn conversions_pick_the_expected_variant() {
assert_eq!(ArgValue::from(true), ArgValue::Bool(true));
assert_eq!(ArgValue::from(48u32), ArgValue::Int(48));
assert_eq!(ArgValue::from(1.5f32), ArgValue::Float(1.5));
assert_eq!(ArgValue::from("sky"), ArgValue::Str("sky".to_string()));
assert_eq!(
ArgValue::from([1.0f32, 0.0, 0.0]),
ArgValue::Array(vec![
ArgValue::Float(1.0),
ArgValue::Float(0.0),
ArgValue::Float(0.0),
])
);
}
#[test]
fn args_wraps_fields_as_an_object() {
let spec = AssetSpec::new("s", "Sprite").set("visible", false);
assert_eq!(
spec.args(),
ArgValue::Object(vec![("visible".to_string(), ArgValue::Bool(false))])
);
}
}