ezu_graph/eval.rs
1//! Evaluation context, asset loader, and error types used during
2//! `Node::eval`.
3
4use std::collections::HashMap;
5use std::sync::Arc;
6
7use crate::buf::{OpaqueValue, RasterBuf, ScalarField, SpriteSheet};
8use crate::value::ScalarValue;
9
10/// Tile coordinate (z/x/y in TMS-ish form; the meaning is up to the host).
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
12pub struct TileId {
13 pub z: u8,
14 pub x: u32,
15 pub y: u32,
16}
17
18/// Per-render canvas geometry. The padded buffer is the actual size all
19/// `Raster` ports must produce; the final tile is the inner `tile_size`
20/// region.
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22pub struct CanvasInfo {
23 pub tile_size: u32,
24 pub pad: u32,
25}
26
27impl CanvasInfo {
28 pub fn padded_size(&self) -> u32 {
29 self.tile_size + 2 * self.pad
30 }
31}
32
33/// One asset fetched by an [`AssetLoader`].
34///
35/// The shape is uniform across input kinds (images, brushes, feature
36/// layers, …) so every source-style node consumes the host through the
37/// same trait — like a shader sampling a typed uniform binding.
38/// `Features` carries a type-erased payload; by convention the
39/// concrete type is `Arc<ezu_features::FeatureLayer>`. `Font` and
40/// `Glyphs` are likewise type-erased (by convention
41/// `Arc<ezu_core::text::Font>` / `Arc<ezu_core::text::SdfFontStack>`)
42/// so this crate gains no font or raster-drawing dependencies.
43#[derive(Debug, Clone)]
44pub enum Asset {
45 Image(Arc<RasterBuf>),
46 Brush(OpaqueValue),
47 Features(OpaqueValue),
48 ScalarField(Arc<ScalarField>),
49 /// A sprite atlas + name→rect index; the `icon` node crops named rects.
50 Sprite(Arc<SpriteSheet>),
51 /// A loaded font face; the `text` node shapes and draws with it.
52 Font(OpaqueValue),
53 /// A glyph-PBF (SDF) fontstack; the `text` node's compat backend.
54 Glyphs(OpaqueValue),
55}
56
57#[derive(Debug, thiserror::Error)]
58pub enum AssetError {
59 #[error("asset not found: `{0}`")]
60 NotFound(String),
61 #[error("asset decode failed for `{src}`: {msg}")]
62 Decode { src: String, msg: String },
63 #[error("asset error: {0}")]
64 Other(String),
65}
66
67/// Pluggable backend for resolving named asset bindings (images,
68/// brushes, tile features, …). Names without a scheme prefix
69/// (`<source>` or `<source>.<layer>`) are by convention tile-scoped —
70/// the host rebinds them per render. Asset srcs carry a scheme
71/// (`builtin:`, `file:`, `http(s)://`) and are document-scoped.
72///
73/// `hash` returns a stable content/identity hash the evaluator folds
74/// into every consuming node's cache key, so changes in a bound asset
75/// invalidate caches automatically. Implementations may return `0` if
76/// the binding never changes (document-scoped, fixed disk file, etc.).
77pub trait AssetLoader: Send + Sync {
78 fn load(&self, name: &str) -> Result<Asset, AssetError>;
79
80 /// Content/identity hash for cache invalidation. The default of
81 /// `0` is safe for assets that never change for the lifetime of a
82 /// loader (typical for in-memory image / brush banks).
83 fn hash(&self, _name: &str) -> u128 {
84 0
85 }
86}
87
88/// A no-op asset loader. Every load returns `NotFound`. Useful for
89/// tests of graphs that don't touch any asset.
90pub struct NoAssets;
91impl AssetLoader for NoAssets {
92 fn load(&self, name: &str) -> Result<Asset, AssetError> {
93 Err(AssetError::NotFound(name.to_string()))
94 }
95}
96
97/// Resolved parameter values for one render. Nodes look up `$name`
98/// references here.
99#[derive(Debug, Default, Clone)]
100pub struct ParamValues {
101 pub values: HashMap<String, ScalarValue>,
102}
103
104impl ParamValues {
105 pub fn new() -> Self {
106 Self::default()
107 }
108
109 pub fn set(&mut self, name: impl Into<String>, value: ScalarValue) {
110 self.values.insert(name.into(), value);
111 }
112
113 pub fn get(&self, name: &str) -> Option<ScalarValue> {
114 self.values.get(name).copied()
115 }
116}
117
118/// Read-only environment a node sees during `eval`.
119pub struct EvalCtx<'a> {
120 pub tile: TileId,
121 pub canvas: CanvasInfo,
122 pub assets: &'a dyn AssetLoader,
123 pub params: &'a ParamValues,
124 /// Deterministic root seed for this render. World-anchored nodes
125 /// hash this with world coordinates to produce per-feature seeds.
126 pub rng_seed: u64,
127}
128
129#[derive(Debug, thiserror::Error)]
130pub enum EvalError {
131 #[error("input port `{0}` was not supplied")]
132 MissingInput(String),
133 #[error("input `{port}` has wrong kind: expected {expected:?}, got {got:?}")]
134 InputKindMismatch {
135 port: String,
136 expected: crate::port::PortKind,
137 got: crate::port::PortKind,
138 },
139 #[error(transparent)]
140 Asset(#[from] AssetError),
141 #[error("{0}")]
142 Other(String),
143}