Skip to main content

ezu_graph/
value.rs

1//! `PortValue` — the runtime values flowing along DAG edges.
2
3use std::sync::Arc;
4
5use crate::buf::{OpaqueValue, RasterBuf, ScalarField};
6use crate::port::PortKind;
7
8/// One value flowing along an edge. Cloning is cheap (Arc / Copy).
9#[derive(Debug, Clone)]
10pub enum PortValue {
11    Features(OpaqueValue),
12    Raster(Arc<RasterBuf>),
13    Sprite(Arc<RasterBuf>),
14    Brush(OpaqueValue),
15    /// Label placement candidates or decisions (see [`PortKind::Labels`]).
16    Labels(OpaqueValue),
17    Scalar(ScalarValue),
18    ScalarField(Arc<ScalarField>),
19}
20
21impl PortValue {
22    pub fn kind(&self) -> PortKind {
23        match self {
24            PortValue::Features(_) => PortKind::Features,
25            PortValue::Raster(_) => PortKind::Raster,
26            PortValue::Sprite(_) => PortKind::Sprite,
27            PortValue::Brush(_) => PortKind::Brush,
28            PortValue::Labels(_) => PortKind::Labels,
29            PortValue::Scalar(_) => PortKind::Scalar,
30            PortValue::ScalarField(_) => PortKind::ScalarField,
31        }
32    }
33
34    pub fn as_scalar_field(&self) -> Option<&Arc<ScalarField>> {
35        if let PortValue::ScalarField(f) = self {
36            Some(f)
37        } else {
38            None
39        }
40    }
41
42    pub fn as_raster(&self) -> Option<&Arc<RasterBuf>> {
43        if let PortValue::Raster(r) = self {
44            Some(r)
45        } else {
46            None
47        }
48    }
49
50    pub fn as_sprite(&self) -> Option<&Arc<RasterBuf>> {
51        if let PortValue::Sprite(s) = self {
52            Some(s)
53        } else {
54            None
55        }
56    }
57
58    /// Approximate heap bytes held by this value's payload.
59    ///
60    /// Exact for the pixel-carrying variants (raster, sprite, scalar
61    /// field) — the ones that dominate render-time memory. Type-erased
62    /// payloads (features, brushes, labels) report `0` because their
63    /// concrete types live in other crates; treat the number as "bytes
64    /// in pixel buffers", which is what memory reports care about.
65    /// The interned blank raster reports `0`: it is one allocation shared
66    /// by every holder, so charging it per holder would badly overstate
67    /// what a render or a cache is really costing.
68    pub fn approx_bytes(&self) -> usize {
69        match self {
70            PortValue::Raster(r) | PortValue::Sprite(r) => {
71                if RasterBuf::is_interned_blank(r) {
72                    0
73                } else {
74                    r.pixels.len()
75                }
76            }
77            PortValue::ScalarField(f) => f.values.len() * std::mem::size_of::<f32>(),
78            _ => 0,
79        }
80    }
81
82    pub fn as_scalar(&self) -> Option<&ScalarValue> {
83        if let PortValue::Scalar(s) = self {
84            Some(s)
85        } else {
86            None
87        }
88    }
89}
90
91/// A constant value carried on a `Scalar` port.
92#[derive(Debug, Clone, Copy, PartialEq)]
93pub enum ScalarValue {
94    /// Straight (non-premultiplied) sRGB-encoded RGBA, components in
95    /// `[0, 1]` — the same convention as a parsed `#rrggbb[aa]`
96    /// literal. Consumers linearize / premultiply as needed.
97    Color([f32; 4]),
98    Number(f64),
99    Bool(bool),
100}
101
102impl ScalarValue {
103    pub fn as_color(&self) -> Option<[f32; 4]> {
104        if let ScalarValue::Color(c) = self {
105            Some(*c)
106        } else {
107            None
108        }
109    }
110
111    pub fn as_number(&self) -> Option<f64> {
112        if let ScalarValue::Number(n) = self {
113            Some(*n)
114        } else {
115            None
116        }
117    }
118
119    pub fn as_bool(&self) -> Option<bool> {
120        if let ScalarValue::Bool(b) = self {
121            Some(*b)
122        } else {
123            None
124        }
125    }
126
127    /// Short kind name for error messages (`number` / `color` / `bool`).
128    pub fn kind_name(&self) -> &'static str {
129        match self {
130            ScalarValue::Color(_) => "color",
131            ScalarValue::Number(_) => "number",
132            ScalarValue::Bool(_) => "bool",
133        }
134    }
135
136    /// Feed this value into a cache-key hasher. Stable across runs.
137    pub fn hash_into(&self, h: &mut xxhash_rust::xxh3::Xxh3) {
138        match self {
139            ScalarValue::Color(c) => {
140                h.update(b"C");
141                for ch in c {
142                    h.update(&ch.to_le_bytes());
143                }
144            }
145            ScalarValue::Number(n) => {
146                h.update(b"N");
147                h.update(&n.to_le_bytes());
148            }
149            ScalarValue::Bool(b) => {
150                h.update(b"B");
151                h.update(&[*b as u8]);
152            }
153        }
154    }
155}