Skip to main content

frp_plexus/
id.rs

1use std::sync::atomic::{AtomicU64, Ordering};
2
3use serde::{Deserialize, Serialize};
4
5// ---------------------------------------------------------------------------
6// ID newtypes
7// ---------------------------------------------------------------------------
8
9macro_rules! define_id {
10    ($name:ident, $doc:literal) => {
11        #[doc = $doc]
12        #[derive(
13            Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize,
14        )]
15        pub struct $name(u64);
16
17        impl $name {
18            /// Wrap a raw `u64` as this ID type.
19            #[inline]
20            pub fn new(id: u64) -> Self {
21                Self(id)
22            }
23
24            /// Return the underlying `u64` value.
25            #[inline]
26            pub fn value(self) -> u64 {
27                self.0
28            }
29        }
30
31        impl std::fmt::Display for $name {
32            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33                write!(f, "{}({})", stringify!($name), self.0)
34            }
35        }
36    };
37}
38
39define_id!(AtomId, "Unique identifier for an `Atom`.");
40define_id!(BlockId, "Unique identifier for a `Block`.");
41define_id!(EdgeId, "Unique identifier for a `HyperEdge`.");
42define_id!(GraphId, "Unique identifier for a `Graph`.");
43define_id!(PortId, "Unique identifier for a `Port`.");
44
45// ---------------------------------------------------------------------------
46// ID generator — thread-safe, monotonically increasing
47// ---------------------------------------------------------------------------
48
49/// Thread-safe generator for all ID types. Each counter is process-local and
50/// starts at 1 (0 is reserved as a sentinel "null" value).
51pub struct IdGen {
52    atom: AtomicU64,
53    block: AtomicU64,
54    edge: AtomicU64,
55    graph: AtomicU64,
56    port: AtomicU64,
57}
58
59impl IdGen {
60    /// Create a new generator with all counters at 1.
61    pub const fn new() -> Self {
62        Self {
63            atom: AtomicU64::new(1),
64            block: AtomicU64::new(1),
65            edge: AtomicU64::new(1),
66            graph: AtomicU64::new(1),
67            port: AtomicU64::new(1),
68        }
69    }
70
71    /// Generate the next unique [`AtomId`].
72    pub fn next_atom_id(&self) -> AtomId {
73        AtomId::new(self.atom.fetch_add(1, Ordering::Relaxed))
74    }
75
76    /// Generate the next unique [`BlockId`].
77    pub fn next_block_id(&self) -> BlockId {
78        BlockId::new(self.block.fetch_add(1, Ordering::Relaxed))
79    }
80
81    /// Generate the next unique [`EdgeId`].
82    pub fn next_edge_id(&self) -> EdgeId {
83        EdgeId::new(self.edge.fetch_add(1, Ordering::Relaxed))
84    }
85
86    /// Generate the next unique [`GraphId`].
87    pub fn next_graph_id(&self) -> GraphId {
88        GraphId::new(self.graph.fetch_add(1, Ordering::Relaxed))
89    }
90
91    /// Generate the next unique [`PortId`].
92    pub fn next_port_id(&self) -> PortId {
93        PortId::new(self.port.fetch_add(1, Ordering::Relaxed))
94    }
95}
96
97impl Default for IdGen {
98    fn default() -> Self {
99        Self::new()
100    }
101}
102
103// ---------------------------------------------------------------------------
104// Tests
105// ---------------------------------------------------------------------------
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    #[test]
112    fn id_new_and_value_round_trip() {
113        assert_eq!(AtomId::new(42).value(), 42);
114        assert_eq!(BlockId::new(0).value(), 0);
115    }
116
117    #[test]
118    fn id_gen_monotonic() {
119        let id_gen = IdGen::new();
120        let a1 = id_gen.next_atom_id();
121        let a2 = id_gen.next_atom_id();
122        assert!(a2.value() > a1.value());
123    }
124
125    #[test]
126    fn id_gen_types_independent() {
127        let id_gen = IdGen::new();
128        let atom = id_gen.next_atom_id();
129        let block = id_gen.next_block_id();
130        // Both start at 1 independently
131        assert_eq!(atom.value(), 1);
132        assert_eq!(block.value(), 1);
133    }
134}