Skip to main content

blitz_traits/
node_id.rs

1//! Versioned node identifier shared between the Blitz crates.
2
3/// A versioned identifier for a node in a Blitz DOM tree.
4///
5/// A `NodeId` packs a 32-bit slot index (low bits) and a 32-bit version
6/// (high bits). Node storage bumps a slot's version when the slot is
7/// reused, so ids referring to a dropped node no longer resolve (lookups
8/// return `None` rather than aliasing the new node occupying the slot).
9///
10/// The `Default` value is a null id which never resolves to a node.
11#[derive(Copy, Clone, Default, Eq, PartialEq, Hash)]
12pub struct NodeId(u64);
13
14impl NodeId {
15    /// Convert this id to its raw `u64` representation (index + version).
16    ///
17    /// The value round-trips through [`NodeId::from_u64`]. This is useful for
18    /// interop with APIs which use integer ids (e.g. Taffy or AccessKit).
19    #[inline(always)]
20    pub fn as_u64(self) -> u64 {
21        self.0
22    }
23
24    /// Reconstruct a `NodeId` from the raw `u64` representation produced by
25    /// [`NodeId::as_u64`].
26    ///
27    /// Passing a value that did not come from `as_u64` will produce an id
28    /// which fails to resolve (it will not alias an unrelated live node).
29    #[inline(always)]
30    pub fn from_u64(raw: u64) -> Self {
31        Self(raw)
32    }
33
34    /// The slot index part of this id.
35    #[inline(always)]
36    fn index(self) -> u32 {
37        self.0 as u32
38    }
39
40    /// The version part of this id.
41    #[inline(always)]
42    fn version(self) -> u32 {
43        (self.0 >> 32) as u32
44    }
45}
46
47/// Ordered by slot index first, then by version, so that ordering roughly
48/// matches node creation order (as with the previous `Slab`-backed storage)
49/// rather than being dominated by the version bits.
50impl Ord for NodeId {
51    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
52        (self.index(), self.version()).cmp(&(other.index(), other.version()))
53    }
54}
55
56impl PartialOrd for NodeId {
57    #[inline]
58    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
59        Some(self.cmp(other))
60    }
61}
62
63/// Serialized as the raw `u64`, so it round-trips through
64/// [`NodeId::from_u64`] and stays an integer on the wire.
65///
66/// It is deliberately not the slot index: a reference handed to an out-of-
67/// process client and echoed back later has to fail rather than resolve to
68/// whatever node took over the slot, and the version bits are what make that
69/// possible.
70impl serde::Serialize for NodeId {
71    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
72        serializer.serialize_u64(self.0)
73    }
74}
75
76impl<'de> serde::Deserialize<'de> for NodeId {
77    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
78        <u64 as serde::Deserialize>::deserialize(deserializer).map(Self)
79    }
80}
81
82impl std::fmt::Debug for NodeId {
83    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84        write!(f, "NodeId({}v{})", self.index(), self.version())
85    }
86}
87
88impl std::fmt::Display for NodeId {
89    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90        write!(f, "{:?}", self)
91    }
92}