Skip to main content

dioxus_core_template/
data.rs

1use super::{DecodedTemplateOp, TemplateAnchor, TemplatePath};
2use crate::op::TemplateOp;
3
4/// A static layout of a UI tree.
5///
6/// Templates describe the stable parts of a view while runtime values provide
7/// the dynamic nodes and attributes for each render.
8#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
9#[derive(Clone, Copy, Eq)]
10pub struct Template {
11    /// Flat static template operations.
12    #[cfg_attr(
13        feature = "serialize",
14        serde(deserialize_with = "super::serialization::deserialize_leaky")
15    )]
16    ops: &'static [TemplateOp],
17
18    /// Static strings referenced by static string operations.
19    #[cfg_attr(
20        feature = "serialize",
21        serde(deserialize_with = "super::serialization::deserialize_strings_leaky")
22    )]
23    strings: &'static [&'static str],
24
25    /// Dynamic node and attribute ranges in document order, each anchored to a static element.
26    #[cfg_attr(
27        feature = "serialize",
28        serde(deserialize_with = "super::serialization::deserialize_leaky")
29    )]
30    anchors: &'static [TemplateAnchor],
31
32    /// Compile-time hash of template content for reliable cross-crate comparison.
33    /// This ensures identical templates compare equal regardless of optimization levels.
34    ///
35    /// Uses xxh64 (64-bit hash). By the birthday paradox, collision probability is:
36    /// P ≈ 1 - e^(-n²/(2 × 2^64)) where n = number of templates.
37    ///
38    /// - 1,000 templates: P ≈ 2.7 × 10^-14 (essentially zero)
39    /// - 10,000 templates: P ≈ 2.7 × 10^-12 (essentially zero)
40    /// - 1 million templates: P ≈ 0.000003%
41    /// - 50% collision chance requires ~5 billion templates
42    ///
43    /// For any realistic application, collision probability is negligible.
44    hash: u64,
45}
46
47/// A static element or text node inside a [`Template`].
48#[derive(Clone, Copy)]
49pub enum StaticTemplateNode<'a> {
50    /// A static template element.
51    Element(StaticTemplateElement<'a>),
52    /// A static template text node.
53    Text(StaticTemplateText<'a>),
54}
55
56/// A static element inside a [`Template`].
57#[derive(Clone, Copy)]
58pub struct StaticTemplateElement<'a> {
59    template: &'a Template,
60    op: usize,
61}
62
63impl<'a> StaticTemplateElement<'a> {
64    /// Return the flat template op that starts this element.
65    pub fn op(self) -> usize {
66        self.op
67    }
68
69    /// Return the element tag.
70    pub fn tag(self) -> &'static str {
71        self.template
72            .element_meta_at_op(self.op)
73            .expect("static element")
74            .0
75    }
76
77    /// Return the element namespace.
78    pub fn namespace(self) -> Option<&'static str> {
79        self.template
80            .element_meta_at_op(self.op)
81            .expect("static element")
82            .1
83    }
84
85    /// Iterate static attributes on this element.
86    pub fn attributes(self) -> StaticTemplateAttributeIter<'a> {
87        let (cursor, end, _) = self
88            .template
89            .element_attr_child_ops(self.op)
90            .expect("static element");
91        StaticTemplateAttributeIter {
92            template: self.template,
93            cursor,
94            end,
95        }
96    }
97
98    /// Iterate static child nodes of this element.
99    pub fn children(self) -> StaticTemplateNodeIter<'a> {
100        let (_, cursor, end) = self
101            .template
102            .element_attr_child_ops(self.op)
103            .expect("static element");
104        StaticTemplateNodeIter {
105            template: self.template,
106            cursor,
107            end,
108        }
109    }
110
111    /// Find a static attr fallback value for a key in this element.
112    pub fn attribute_value(
113        self,
114        key: (&'static str, Option<&'static str>),
115    ) -> Option<&'static str> {
116        self.attributes()
117            .filter(|attr| (attr.name, attr.namespace) == key)
118            .map(|attr| attr.value)
119            .last()
120    }
121}
122
123/// A static text node inside a [`Template`].
124#[derive(Clone, Copy)]
125pub struct StaticTemplateText<'a> {
126    template: &'a Template,
127    op: usize,
128}
129
130impl StaticTemplateText<'_> {
131    /// Return the flat template op that starts this text node.
132    pub fn op(self) -> usize {
133        self.op
134    }
135
136    /// Return the text value.
137    pub fn text(self) -> &'static str {
138        self.template
139            .static_text_at_op(self.op)
140            .expect("static text")
141    }
142}
143
144/// A static attribute on a template element.
145#[derive(Clone, Copy, Debug, PartialEq, Eq)]
146pub struct StaticTemplateAttribute {
147    /// Attribute name.
148    pub name: &'static str,
149    /// Attribute value.
150    pub value: &'static str,
151    /// Attribute namespace.
152    pub namespace: Option<&'static str>,
153}
154
155/// Iterator over static template nodes.
156#[derive(Clone, Copy)]
157pub struct StaticTemplateNodeIter<'a> {
158    template: &'a Template,
159    cursor: usize,
160    end: usize,
161}
162
163impl<'a> Iterator for StaticTemplateNodeIter<'a> {
164    type Item = StaticTemplateNode<'a>;
165
166    fn next(&mut self) -> Option<Self::Item> {
167        while self.cursor < self.end {
168            let op = self.cursor;
169            self.cursor = self.template.next_sibling_op(op);
170            if let Some(node) = self.template.static_node(op) {
171                return Some(node);
172            }
173        }
174        None
175    }
176}
177
178/// Iterator over static template attributes.
179#[derive(Clone, Copy)]
180pub struct StaticTemplateAttributeIter<'a> {
181    template: &'a Template,
182    cursor: usize,
183    end: usize,
184}
185
186impl Iterator for StaticTemplateAttributeIter<'_> {
187    type Item = StaticTemplateAttribute;
188
189    fn next(&mut self) -> Option<Self::Item> {
190        while self.cursor < self.end {
191            let op = self.cursor;
192            self.cursor += self.template.attr_op_len(op).unwrap_or(1);
193            if let Some((name, value, namespace)) = self.template.static_attr_at_op(op) {
194                return Some(StaticTemplateAttribute {
195                    name,
196                    value,
197                    namespace,
198                });
199            }
200        }
201        None
202    }
203}
204
205impl std::fmt::Debug for Template {
206    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
207        f.debug_struct("Template").finish_non_exhaustive()
208    }
209}
210
211impl Template {
212    /// Create a new template.
213    pub(crate) const fn new(
214        ops: &'static [TemplateOp],
215        strings: &'static [&'static str],
216        anchors: &'static [TemplateAnchor],
217    ) -> Self {
218        Self {
219            ops,
220            strings,
221            anchors,
222            hash: Self::compute_hash(ops, strings, anchors),
223        }
224    }
225
226    /// Get the template static string pool.
227    pub const fn strings(&self) -> &'static [&'static str] {
228        self.strings
229    }
230
231    /// Iterate decoded template operations.
232    pub fn decoded_ops(&self) -> impl ExactSizeIterator<Item = DecodedTemplateOp> + '_ {
233        self.ops.iter().map(|op| op.decode())
234    }
235
236    /// Get dynamic slot anchors in document order.
237    pub const fn anchors(&self) -> &'static [TemplateAnchor] {
238        self.anchors
239    }
240
241    /// Return a static node by flat template op.
242    pub fn static_node(&self, op: usize) -> Option<StaticTemplateNode<'_>> {
243        self.static_element(op)
244            .map(StaticTemplateNode::Element)
245            .or_else(|| self.static_text(op).map(StaticTemplateNode::Text))
246    }
247
248    /// Return a static element by flat template op.
249    pub fn static_element(&self, op: usize) -> Option<StaticTemplateElement<'_>> {
250        self.element_meta_at_op(op)
251            .is_some()
252            .then_some(StaticTemplateElement { template: self, op })
253    }
254
255    /// Return a static text node by flat template op.
256    pub fn static_text(&self, op: usize) -> Option<StaticTemplateText<'_>> {
257        self.static_text_at_op(op)
258            .is_some()
259            .then_some(StaticTemplateText { template: self, op })
260    }
261
262    /// Get a static string from this template's string pool.
263    fn string(&self, id: u16) -> &'static str {
264        self.strings[id as usize]
265    }
266
267    /// Decode an element op into its subtree length and namespace presence.
268    fn enter_meta(&self, op: usize) -> Option<(usize, bool)> {
269        match self.ops.get(op).map(|op| op.decode()) {
270            Some(DecodedTemplateOp::Enter { skip, namespace }) => Some((skip as usize, namespace)),
271            _ => None,
272        }
273    }
274
275    /// Return the static string referenced by an op.
276    fn static_string_at_op(&self, op: usize) -> Option<&'static str> {
277        match self.ops.get(op).map(|op| op.decode()) {
278            Some(DecodedTemplateOp::Static(id)) => Some(self.string(id)),
279            _ => None,
280        }
281    }
282
283    fn element_meta_at_op(&self, op: usize) -> Option<(&'static str, Option<&'static str>)> {
284        let (_, has_namespace) = self.enter_meta(op)?;
285        let tag = self.static_string_at_op(op + 1)?;
286        let namespace = has_namespace
287            .then(|| self.static_string_at_op(op + 2))
288            .flatten();
289        Some((tag, namespace))
290    }
291
292    fn element_children_start(&self, op: usize) -> Option<usize> {
293        let (_, has_namespace) = self.enter_meta(op)?;
294        Some(op + if has_namespace { 3 } else { 2 })
295    }
296
297    fn static_attr_at_op(
298        &self,
299        op: usize,
300    ) -> Option<(&'static str, &'static str, Option<&'static str>)> {
301        let namespace = match self.ops.get(op).map(|op| op.decode()) {
302            Some(DecodedTemplateOp::Attr { namespace }) => namespace,
303            _ => return None,
304        };
305        let name = self.static_string_at_op(op + 1)?;
306        let value = self.static_string_at_op(op + 2)?;
307        let namespace = namespace
308            .then(|| self.static_string_at_op(op + 3))
309            .flatten();
310        Some((name, value, namespace))
311    }
312
313    fn static_text_at_op(&self, op: usize) -> Option<&'static str> {
314        (self.ops.get(op).map(|op| op.decode()) == Some(DecodedTemplateOp::Text))
315            .then(|| self.static_string_at_op(op + 1))
316            .flatten()
317    }
318
319    fn attr_op_len(&self, op: usize) -> Option<usize> {
320        match self.ops.get(op).map(|op| op.decode()) {
321            Some(DecodedTemplateOp::Attr { namespace: true }) => Some(4),
322            Some(DecodedTemplateOp::Attr { .. }) => Some(3),
323            _ => None,
324        }
325    }
326
327    fn element_end(&self, op: usize) -> Option<usize> {
328        let (skip, _) = self.enter_meta(op)?;
329        Some(op + skip)
330    }
331
332    fn element_attr_child_ops(&self, element_op: usize) -> Option<(usize, usize, usize)> {
333        let attr_start = self.element_children_start(element_op)?;
334        let mut cursor = attr_start;
335        let end = self.element_end(element_op)?;
336        while cursor < end {
337            if let Some(len) = self.attr_op_len(cursor) {
338                cursor += len;
339            } else {
340                break;
341            }
342        }
343        Some((attr_start, cursor, end))
344    }
345
346    /// Return the number of rendered root children in this template.
347    pub fn root_count(&self) -> usize {
348        let mut count = 0;
349        for anchor in self.anchors.iter().copied() {
350            if anchor.parent_element_op_index().is_some() {
351                continue;
352            }
353
354            let path = anchor.static_path();
355            if !path.is_root() && !path.is_empty() {
356                continue;
357            }
358
359            if !anchor.nodes().is_empty() {
360                count += 1;
361            }
362
363            if !anchor.is_last_static_node() && path.is_root() {
364                count += 1;
365            }
366        }
367
368        count
369    }
370
371    /// Return a static node by compact template path.
372    pub fn static_node_at_path(&self, path: TemplatePath) -> Option<StaticTemplateNode<'_>> {
373        if path.is_empty() {
374            return None;
375        }
376
377        let mut start = 0;
378        let mut end = self.ops.len();
379        let path_depth = path.depth();
380
381        for (depth, segment) in path.segments().enumerate() {
382            let op = self.static_node_op_in_range(start, end, segment)?;
383            if depth + 1 == path_depth {
384                return self.static_node(op);
385            }
386
387            let (_, child_start, child_end) = self.element_attr_child_ops(op)?;
388            start = child_start;
389            end = child_end;
390        }
391
392        None
393    }
394
395    fn static_node_op_in_range(&self, start: usize, end: usize, target: usize) -> Option<usize> {
396        let mut cursor = start;
397        let mut index = 0;
398        while cursor < end {
399            let op = cursor;
400            cursor = self.next_sibling_op(op);
401            if self.static_node(op).is_none() {
402                continue;
403            }
404
405            if index == target {
406                return Some(op);
407            }
408            index += 1;
409        }
410
411        None
412    }
413
414    fn next_sibling_op(&self, op: usize) -> usize {
415        Self::next_sibling_op_in(self.ops, op)
416    }
417
418    const fn next_sibling_op_in(ops: &[TemplateOp], op: usize) -> usize {
419        match ops[op].decode() {
420            DecodedTemplateOp::Enter { skip, .. } => op + skip as usize,
421            DecodedTemplateOp::Text => op + 2,
422            DecodedTemplateOp::Attr { namespace: true } => op + 4,
423            DecodedTemplateOp::Attr { .. } => op + 3,
424            _ => op + 1,
425        }
426    }
427
428    /// Compute a content-based hash of template structure.
429    /// This is const so it can be used both at compile time and runtime.
430    const fn compute_hash(
431        ops: &[TemplateOp],
432        strings: &'static [&'static str],
433        anchors: &[TemplateAnchor],
434    ) -> u64 {
435        use xxhash_rust::const_xxh64::xxh64;
436
437        let mut hash = 0u64;
438
439        // Raw operations
440        let mut i = 0;
441        while i < ops.len() {
442            hash = xxh64(&ops[i].bits().to_le_bytes(), hash);
443            i += 1;
444        }
445
446        // Static strings
447        hash = xxh64(&[0xA0], hash);
448        let mut i = 0;
449        while i < strings.len() {
450            let string = strings[i];
451            let bytes = string.as_bytes();
452            hash = xxh64(bytes, hash);
453            i += 1;
454        }
455
456        // Hash anchor metadata.
457        hash = xxh64(&[0xA1], hash);
458        let mut i = 0;
459        while i < anchors.len() {
460            let anchor = anchors[i];
461            hash = xxh64(&anchor.parent_op_index.to_le_bytes(), hash);
462            hash = xxh64(&anchor.slot_path().bits().to_le_bytes(), hash);
463            hash = xxh64(&anchor.node_start.to_le_bytes(), hash);
464            hash = xxh64(&anchor.node_end.to_le_bytes(), hash);
465            hash = xxh64(&anchor.attr_start.to_le_bytes(), hash);
466            hash = xxh64(&anchor.attr_end.to_le_bytes(), hash);
467            i += 1;
468        }
469
470        hash
471    }
472}
473
474impl std::hash::Hash for Template {
475    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
476        self.hash.hash(state);
477    }
478}
479
480impl PartialEq for Template {
481    fn eq(&self, other: &Self) -> bool {
482        self.hash == other.hash
483    }
484}
485
486impl PartialOrd for Template {
487    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
488        Some(self.cmp(other))
489    }
490}
491
492impl Ord for Template {
493    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
494        self.hash.cmp(&other.hash)
495    }
496}