Skip to main content

rosin_core/
nodeid.rs

1//! A stable and unique node identifier.
2
3use std::{
4    num::NonZeroU64,
5    sync::atomic::{AtomicU64, Ordering},
6};
7
8static NEXT_NODE_ID: AtomicU64 = AtomicU64::new(1);
9
10/// A unique identifier for a node.
11///
12/// In view callbacks, create NodeIds with the [`crate::id`] macro.
13#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
14pub struct NodeId(pub(crate) NonZeroU64);
15
16impl From<NodeId> for u64 {
17    fn from(id: NodeId) -> u64 {
18        id.0.get()
19    }
20}
21
22impl From<NodeId> for accesskit::NodeId {
23    fn from(value: NodeId) -> Self {
24        Self(value.0.get())
25    }
26}
27
28/// The preferred method for creating a [`NodeId`].
29///
30/// Generates an id based on the call location, so repeated calls to the same view function will create nodes with stable ids.
31/// You can optionally pass a comma separated list of anything that implements [`Into<u64>`] to make it unique,
32/// such as the id of a parent node, or the position in a list, which is useful when writing reusable widgets.
33#[macro_export]
34macro_rules! id {
35    () => {
36        const { NodeId::__internal_new(file!(), line!(), column!()) }
37    };
38    ($($x:expr),+ $(,)?) => {{
39        let mut id = const { NodeId::__internal_new(file!(), line!(), column!()) };
40        $(
41            id = id.__internal_mix(($x).into());
42        )+
43        id
44    }};
45}
46
47impl Default for NodeId {
48    fn default() -> Self {
49        Self::next()
50    }
51}
52
53impl NodeId {
54    pub(crate) const FNV_OFFSET_BASIS: u64 = 0xcbf29ce484222325;
55    pub(crate) const FNV_PRIME: u64 = 0x100000001b3;
56
57    #[doc(hidden)]
58    pub const fn __internal_new(file: &'static str, line: u32, column: u32) -> Self {
59        const fn fnv1a_64(seed: Option<u64>, bytes: &[u8]) -> u64 {
60            let mut hash = if let Some(seed) = seed { seed } else { NodeId::FNV_OFFSET_BASIS };
61
62            let mut i = 0;
63            while i < bytes.len() {
64                hash ^= bytes[i] as u64;
65                hash = hash.wrapping_mul(NodeId::FNV_PRIME);
66                i += 1;
67            }
68
69            hash
70        }
71
72        let h1 = fnv1a_64(None, file.as_bytes());
73        let h2 = fnv1a_64(Some(h1), &line.to_le_bytes());
74        let h3 = fnv1a_64(Some(h2), &column.to_le_bytes());
75
76        let non_zero = match NonZeroU64::new(h3) {
77            Some(val) => val,
78            None => NonZeroU64::MAX,
79        };
80
81        NodeId(non_zero)
82    }
83
84    #[doc(hidden)]
85    pub const fn __internal_mix(self, rhs: u64) -> Self {
86        let mixed = (self.0.get() ^ rhs).wrapping_mul(NodeId::FNV_PRIME);
87        let non_zero = match NonZeroU64::new(mixed) {
88            Some(val) => val,
89            None => NonZeroU64::MAX,
90        };
91        NodeId(non_zero)
92    }
93
94    /// Create a new [`NodeId`]. For times when the `id!()` macro isn't appropriate, such as when creating ids in a loop.
95    ///
96    /// Not suitable for use in a view callback since it returns a unique id every time.
97    pub fn next() -> Self {
98        // Relaxed is sufficient since we only care about uniqueness.
99        Self(NonZeroU64::new(NEXT_NODE_ID.fetch_add(1, Ordering::Relaxed)).unwrap_or(NonZeroU64::MAX))
100    }
101
102    /// Resume [`NodeId`] generation from a specific value.
103    /// This is useful in the exceptionally rare cases when resuming id creation
104    /// from a particular value with the [`NodeId::next()`] function is required.
105    ///
106    /// **Panics**: if `value` is zero, as [`NodeId`] values must be non-zero.
107    pub fn set_counter(value: u64) {
108        assert!(value != 0, "NodeId counter cannot start at zero");
109        NEXT_NODE_ID.store(value, Ordering::SeqCst);
110    }
111
112    /// Returns the current value of the [`NodeId`] generator.
113    /// Useful for persisting the id counter to resume later with [`NodeId::set_counter()`].
114    pub fn get_counter() -> u64 {
115        NEXT_NODE_ID.load(Ordering::SeqCst)
116    }
117}