zng_unique_id/
named.rs

1use std::{
2    fmt,
3    hash::{BuildHasher, Hash},
4};
5
6use std::collections::hash_map;
7
8use crate::{IdEntry, IdMap};
9
10struct ConstDefaultHashBuilder;
11impl BuildHasher for ConstDefaultHashBuilder {
12    type Hasher = std::collections::hash_map::DefaultHasher;
13
14    fn build_hasher(&self) -> Self::Hasher {
15        std::collections::hash_map::DefaultHasher::default()
16    }
17}
18
19type DefaultHashMap<K, V> = std::collections::HashMap<K, V, ConstDefaultHashBuilder>;
20
21const fn default_hash_map_new<K, V>() -> DefaultHashMap<K, V> {
22    DefaultHashMap::with_hasher(ConstDefaultHashBuilder)
23}
24
25#[doc(hidden)]
26pub use zng_txt::Txt;
27
28/// Bidirectional map between a `Txt` and a [`unique_id!`] generated id type.
29struct NameIdMap<I> {
30    name_to_id: DefaultHashMap<Txt, I>,
31    id_to_name: IdMap<I, Txt>,
32}
33impl<I> NameIdMap<I>
34where
35    I: Copy + PartialEq + Eq + Hash + fmt::Debug,
36{
37    pub const fn new() -> Self {
38        NameIdMap {
39            name_to_id: default_hash_map_new(),
40            id_to_name: IdMap::new(),
41        }
42    }
43
44    pub fn set(&mut self, name: Txt, id: I) -> Result<(), IdNameError<I>> {
45        if name.is_empty() {
46            return Ok(());
47        }
48
49        match self.id_to_name.entry(id) {
50            IdEntry::Occupied(e) => {
51                if *e.get() == name {
52                    Ok(())
53                } else {
54                    Err(IdNameError::AlreadyNamed(e.get().clone()))
55                }
56            }
57            IdEntry::Vacant(e) => match self.name_to_id.entry(name.clone()) {
58                hash_map::Entry::Occupied(ne) => Err(IdNameError::NameUsed(*ne.get())),
59                hash_map::Entry::Vacant(ne) => {
60                    e.insert(name);
61                    ne.insert(id);
62                    Ok(())
63                }
64            },
65        }
66    }
67
68    pub fn get_id_or_insert(&mut self, name: Txt, new_unique: impl FnOnce() -> I) -> I {
69        if name.is_empty() {
70            return new_unique();
71        }
72        match self.name_to_id.entry(name.clone()) {
73            hash_map::Entry::Occupied(e) => *e.get(),
74            hash_map::Entry::Vacant(e) => {
75                let id = new_unique();
76                e.insert(id);
77                self.id_to_name.insert(id, name);
78                id
79            }
80        }
81    }
82
83    pub fn new_named(&mut self, name: Txt, new_unique: impl FnOnce() -> I) -> Result<I, IdNameError<I>> {
84        if name.is_empty() {
85            Ok(new_unique())
86        } else {
87            match self.name_to_id.entry(name.clone()) {
88                hash_map::Entry::Occupied(e) => Err(IdNameError::NameUsed(*e.get())),
89                hash_map::Entry::Vacant(e) => {
90                    let id = new_unique();
91                    e.insert(id);
92                    self.id_to_name.insert(id, name);
93                    Ok(id)
94                }
95            }
96        }
97    }
98
99    pub fn get_name(&self, id: I) -> Txt {
100        self.id_to_name.get(&id).cloned().unwrap_or_default()
101    }
102}
103
104/// Error when trying to associate give a name with an existing id.
105#[derive(Clone, Debug)]
106pub enum IdNameError<I: Clone + Copy + fmt::Debug> {
107    /// The id is already named, id names are permanent.
108    ///
109    /// The associated value if the id name.
110    AlreadyNamed(Txt),
111    /// The name is already used for another id, names must be unique.
112    ///
113    /// The associated value if the named id.
114    NameUsed(I),
115}
116impl<I: Clone + Copy + fmt::Debug> fmt::Display for IdNameError<I> {
117    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118        match self {
119            IdNameError::AlreadyNamed(name) => write!(f, "cannot name the id, it is already called `{name:?}`"),
120            IdNameError::NameUsed(id) => write!(f, "cannot name the id, it is already the name of {id:#?}"),
121        }
122    }
123}
124impl<I: Clone + Copy + fmt::Debug> std::error::Error for IdNameError<I> {}
125
126#[doc(hidden)]
127pub struct UniqueIdNameStore<I>(parking_lot::RwLock<NameIdMap<I>>);
128impl<I> UniqueIdNameStore<I>
129where
130    I: Copy + PartialEq + Eq + Hash + fmt::Debug,
131{
132    pub const fn new() -> Self {
133        Self(parking_lot::const_rwlock(NameIdMap::new()))
134    }
135
136    pub fn named(&self, name: impl Into<Txt>, new_unique: impl FnOnce() -> I) -> I {
137        self.0.write().get_id_or_insert(name.into(), new_unique)
138    }
139
140    pub fn named_new(&self, name: impl Into<Txt>, new_unique: impl FnOnce() -> I) -> Result<I, IdNameError<I>> {
141        self.0.write().new_named(name.into(), new_unique)
142    }
143
144    pub fn name(&self, id: I) -> Txt {
145        self.0.read().get_name(id)
146    }
147
148    pub fn set_name(&self, name: impl Into<Txt>, id: I) -> Result<(), IdNameError<I>> {
149        self.0.write().set(name.into(), id)
150    }
151}
152impl<I> Default for UniqueIdNameStore<I>
153where
154    I: Copy + PartialEq + Eq + Hash + fmt::Debug,
155{
156    fn default() -> Self {
157        Self::new()
158    }
159}
160
161/// Extend an unique ID type to have an optional attached name string, also implements.
162#[macro_export]
163macro_rules! impl_unique_id_name {
164    ($UniqueId:ident) => {
165        $crate::paste! {
166            $crate::hot_static! {
167                static [<$UniqueId:upper _ID_NAMES>]: $crate::UniqueIdNameStore<$UniqueId> = $crate::UniqueIdNameStore::new();
168            }
169        }
170
171        impl $UniqueId {
172            fn names_store() -> &'static $crate::UniqueIdNameStore<Self> {
173                $crate::paste! {
174                    $crate::hot_static_ref! {
175                        [<$UniqueId:upper _ID_NAMES>]
176                    }
177                }
178            }
179
180            /// Get or generate an ID with associated name.
181            ///
182            /// If the `name` is already associated with an ID, returns it.
183            /// If the `name` is new, generates a new ID and associated it with the name.
184            /// If `name` is an empty string just returns a new ID.
185            pub fn named(name: impl Into<$crate::Txt>) -> Self {
186                Self::names_store().named(name, Self::new_unique)
187            }
188
189            /// Calls [`named`] in a debug build and [`new_unique`] in a release build.
190            ///
191            /// [`named`]: Self::named
192            /// [`new_unique`]: Self::new_unique
193            pub fn debug_named(name: impl Into<$crate::Txt>) -> Self {
194                #[cfg(debug_assertions)]
195                return Self::named(name);
196
197                #[cfg(not(debug_assertions))]
198                {
199                    let _ = name;
200                    Self::new_unique()
201                }
202            }
203
204            /// Generate a new ID with associated name.
205            ///
206            /// If the `name` is already associated with an ID, returns the `NameUsed` error.
207            /// If the `name` is an empty string just returns a new ID.
208            pub fn named_new(name: impl Into<$crate::Txt>) -> Result<Self, $crate::IdNameError<Self>> {
209                Self::names_store().named_new(name.into(), Self::new_unique)
210            }
211
212            /// Returns the name associated with the ID or `""`.
213            pub fn name(self) -> $crate::Txt {
214                Self::names_store().name(self)
215            }
216
217            /// Associate a `name` with the ID, if it is not named.
218            ///
219            /// If the `name` is already associated with a different ID, returns the `NameUsed` error.
220            /// If the ID is already named, with a name different from `name`, returns the `AlreadyNamed` error.
221            /// If the `name` is an empty string or already is the name of the ID, does nothing.
222            pub fn set_name(self, name: impl Into<$crate::Txt>) -> Result<(), $crate::IdNameError<Self>> {
223                Self::names_store().set_name(name.into(), self)
224            }
225        }
226    };
227}
228
229/// Implement debug and display for an unique ID type that also implements name.
230#[macro_export]
231macro_rules! impl_unique_id_fmt {
232    ($UniqueId:ident) => {
233        impl std::fmt::Debug for $UniqueId {
234            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
235                let name = self.name();
236                if f.alternate() {
237                    f.debug_struct(stringify!($UniqueId))
238                        .field("id", &self.get())
239                        .field("sequential", &self.sequential())
240                        .field("name", &name)
241                        .finish()
242                } else if !name.is_empty() {
243                    write!(f, r#"{}("{name}")"#, stringify!($UniqueId))
244                } else {
245                    write!(f, "{}({})", stringify!($UniqueId), self.sequential())
246                }
247            }
248        }
249        impl std::fmt::Display for $UniqueId {
250            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
251                let name = self.name();
252                if !name.is_empty() {
253                    write!(f, "{name}")
254                } else if f.alternate() {
255                    write!(f, "#{}", self.sequential())
256                } else {
257                    write!(f, "{}({})", stringify!($UniqueId), self.sequential())
258                }
259            }
260        }
261    };
262}