use ezu_style as spec;
use serde_json::Value;
use xxhash_rust::xxh3::Xxh3;
use crate::eval::{EvalCtx, EvalError};
use crate::port::{PortKind, PortSpec};
use crate::registry::{Connection, FactoryCtx, FactoryError};
use crate::value::{PortValue, ScalarValue};
pub const ACCEPTS_SCALAR: &[PortKind] = &[PortKind::Scalar];
pub trait ScalarType: Copy {
const NAME: &'static str;
fn matches_kind(kind: spec::ParamKind) -> bool;
fn from_scalar(v: ScalarValue) -> Option<Self>;
fn from_json(v: &Value) -> Option<Self>;
fn hash_into(&self, h: &mut Xxh3);
fn clamp_decl(self, _min: Option<f64>, _max: Option<f64>) -> Self {
self
}
}
impl ScalarType for f64 {
const NAME: &'static str = "number";
fn matches_kind(kind: spec::ParamKind) -> bool {
kind == spec::ParamKind::Number
}
fn from_scalar(v: ScalarValue) -> Option<Self> {
v.as_number()
}
fn from_json(v: &Value) -> Option<Self> {
v.as_f64()
}
fn hash_into(&self, h: &mut Xxh3) {
h.update(&self.to_le_bytes());
}
fn clamp_decl(self, min: Option<f64>, max: Option<f64>) -> Self {
let mut v = self;
if let Some(m) = min {
v = v.max(m);
}
if let Some(m) = max {
v = v.min(m);
}
v
}
}
impl ScalarType for [f32; 4] {
const NAME: &'static str = "color";
fn matches_kind(kind: spec::ParamKind) -> bool {
kind == spec::ParamKind::Color
}
fn from_scalar(v: ScalarValue) -> Option<Self> {
v.as_color()
}
fn from_json(v: &Value) -> Option<Self> {
spec::parse_hex_color(v.as_str()?)
}
fn hash_into(&self, h: &mut Xxh3) {
for c in self {
h.update(&c.to_le_bytes());
}
}
}
impl ScalarType for bool {
const NAME: &'static str = "bool";
fn matches_kind(kind: spec::ParamKind) -> bool {
kind == spec::ParamKind::Bool
}
fn from_scalar(v: ScalarValue) -> Option<Self> {
v.as_bool()
}
fn from_json(v: &Value) -> Option<Self> {
v.as_bool()
}
fn hash_into(&self, h: &mut Xxh3) {
h.update(&[*self as u8]);
}
}
#[derive(Debug, Clone)]
pub enum In<T> {
Const(T),
Param {
name: String,
fallback: T,
min: Option<f64>,
max: Option<f64>,
},
Port {
ix: usize,
name: &'static str,
},
}
impl<T: ScalarType> In<T> {
pub fn get(&self, ctx: &EvalCtx<'_>, inputs: &[Option<PortValue>]) -> Result<T, EvalError> {
match self {
In::Const(v) => Ok(*v),
In::Param {
name,
fallback,
min,
max,
} => {
let v = match ctx.params.get(name) {
None => *fallback,
Some(sv) => T::from_scalar(sv).ok_or_else(|| {
EvalError::Other(format!(
"param `${name}`: expected {}, got {}",
T::NAME,
sv.kind_name()
))
})?,
};
Ok(v.clamp_decl(*min, *max))
}
In::Port { ix, name } => {
let v = inputs
.get(*ix)
.and_then(|o| o.as_ref())
.ok_or_else(|| EvalError::MissingInput((*name).into()))?;
let PortValue::Scalar(sv) = v else {
return Err(EvalError::Other(format!(
"port `{name}`: expected a scalar, got {}",
v.kind()
)));
};
T::from_scalar(*sv).ok_or_else(|| {
EvalError::Other(format!(
"port `{name}`: expected {}, got {}",
T::NAME,
sv.kind_name()
))
})
}
}
}
pub fn param_hash(&self, h: &mut Xxh3) {
match self {
In::Const(v) => {
h.update(b"c");
v.hash_into(h);
}
In::Param { name, fallback, .. } => {
h.update(b"p");
h.update(name.as_bytes());
fallback.hash_into(h);
}
In::Port { ix, .. } => {
h.update(b"@");
h.update(&(*ix as u64).to_le_bytes());
}
}
}
pub fn static_bound(&self) -> Option<f64>
where
T: Into<f64>,
{
match self {
In::Const(v) => Some((*v).into()),
In::Param { max, .. } => *max,
In::Port { .. } => None,
}
}
}
pub fn parse_param_value(
decls: &indexmap::IndexMap<String, spec::ParamDecl>,
name: &str,
raw: &str,
) -> Result<ScalarValue, String> {
let decl = decls
.get(name)
.ok_or_else(|| format!("unknown param `{name}`"))?;
match decl.kind {
spec::ParamKind::Number => {
let v: f64 = raw
.parse()
.map_err(|_| format!("param `{name}`: `{raw}` is not a number"))?;
if let Some(m) = decl.min {
if v < m {
return Err(format!("param `{name}`: {v} is below min {m}"));
}
}
if let Some(m) = decl.max {
if v > m {
return Err(format!("param `{name}`: {v} is above max {m}"));
}
}
Ok(ScalarValue::Number(v))
}
spec::ParamKind::Bool => match raw {
"true" | "1" => Ok(ScalarValue::Bool(true)),
"false" | "0" => Ok(ScalarValue::Bool(false)),
_ => Err(format!("param `{name}`: expected true/false, got `{raw}`")),
},
spec::ParamKind::Color => spec::parse_hex_color(raw)
.map(ScalarValue::Color)
.ok_or_else(|| format!("param `{name}`: `{raw}` is not a `#rrggbb[aa]` color")),
}
}
#[derive(Debug, Default)]
pub struct InParts {
pub ports: Vec<PortSpec>,
pub connections: Vec<Connection>,
pub param_refs: Vec<String>,
}
pub struct InReader<'a, 'c> {
fields: &'a serde_json::Map<String, Value>,
ctx: &'a FactoryCtx<'c>,
parts: InParts,
next_port: usize,
}
impl<'a, 'c> InReader<'a, 'c> {
pub fn new(
fields: &'a serde_json::Map<String, Value>,
ctx: &'a FactoryCtx<'c>,
fixed_ports: usize,
) -> Self {
Self {
fields,
ctx,
parts: InParts::default(),
next_port: fixed_ports,
}
}
pub fn number(&mut self, name: &'static str) -> Result<In<f64>, FactoryError> {
self.read(name, None)
}
pub fn number_or(&mut self, name: &'static str, default: f64) -> Result<In<f64>, FactoryError> {
self.read(name, Some(default))
}
pub fn color(&mut self, name: &'static str) -> Result<In<[f32; 4]>, FactoryError> {
self.read(name, None)
}
pub fn color_or(
&mut self,
name: &'static str,
default: [f32; 4],
) -> Result<In<[f32; 4]>, FactoryError> {
self.read(name, Some(default))
}
pub fn color_opt(&mut self, name: &'static str) -> Result<Option<In<[f32; 4]>>, FactoryError> {
if !self.fields.contains_key(name) {
return Ok(None);
}
Ok(Some(self.read(name, None)?))
}
pub fn bool_or(&mut self, name: &'static str, default: bool) -> Result<In<bool>, FactoryError> {
self.read(name, Some(default))
}
fn read<T: ScalarType>(
&mut self,
name: &'static str,
default: Option<T>,
) -> Result<In<T>, FactoryError> {
let Some(v) = self.fields.get(name) else {
return default
.map(In::Const)
.ok_or_else(|| FactoryError::MissingField(name.to_string()));
};
if let Some(s) = v.as_str() {
match spec::FieldRef::classify(s) {
spec::FieldRef::Node(id) => {
let ix = self.next_port;
self.next_port += 1;
self.parts.ports.push(PortSpec {
name,
accepts: ACCEPTS_SCALAR,
optional: false,
});
self.parts.connections.push(Connection {
port: name.to_string(),
src: id.to_string(),
});
return Ok(In::Port { ix, name });
}
spec::FieldRef::Param(p) => {
let decl = self
.ctx
.params
.get(p)
.ok_or_else(|| FactoryError::UnknownParam(p.to_string()))?;
if !T::matches_kind(decl.kind) {
return Err(FactoryError::BadField {
field: name.into(),
msg: format!(
"param `${p}` is declared `{:?}`, but this field needs a {}",
decl.kind,
T::NAME
),
});
}
let fallback =
T::from_json(&decl.default).ok_or_else(|| FactoryError::BadField {
field: name.into(),
msg: format!("param `${p}` default is not a valid {}", T::NAME),
})?;
self.parts.param_refs.push(p.to_string());
return Ok(In::Param {
name: p.to_string(),
fallback,
min: decl.min,
max: decl.max,
});
}
spec::FieldRef::Literal(_) => {} }
}
T::from_json(v)
.map(In::Const)
.ok_or_else(|| FactoryError::BadField {
field: name.into(),
msg: format!("expected {} literal, `$param`, or `@node`", T::NAME),
})
}
pub fn finish(self) -> InParts {
self.parts
}
}