Skip to main content

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}