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}