vortex_session/
registry.rs1use std::cmp::Ordering;
5use std::fmt;
6use std::fmt::Debug;
7use std::fmt::Display;
8use std::fmt::Formatter;
9use std::hash::Hash;
10use std::ops::Deref;
11use std::sync::Arc;
12use std::sync::LazyLock;
13use std::sync::OnceLock;
14
15use lasso::Spur;
16use lasso::ThreadedRodeo;
17use parking_lot::RwLock;
18use vortex_error::VortexExpect;
19use vortex_utils::aliases::DefaultHashBuilder;
20use vortex_utils::aliases::hash_set::HashSet;
21
22static INTERNER: LazyLock<ThreadedRodeo<Spur, DefaultHashBuilder>> =
24 LazyLock::new(|| ThreadedRodeo::with_hasher(DefaultHashBuilder::default()));
25
26#[derive(Clone, Copy, PartialEq, Eq, Hash)]
32pub struct Id(Spur);
33
34impl Id {
35 pub fn new(s: &str) -> Self {
37 Self(INTERNER.get_or_intern(s))
38 }
39
40 pub fn new_static(s: &'static str) -> Self {
42 Self(INTERNER.get_or_intern_static(s))
43 }
44
45 pub fn as_str(&self) -> &str {
47 let s = INTERNER.resolve(&self.0);
48 unsafe { &*(s as *const str) }
51 }
52}
53
54impl From<&str> for Id {
55 #[expect(clippy::disallowed_methods, reason = "interning a dynamic id")]
56 fn from(s: &str) -> Self {
57 Self::new(s)
58 }
59}
60
61impl Display for Id {
62 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
63 f.write_str(self.as_str())
64 }
65}
66
67impl Debug for Id {
68 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
69 write!(f, "Id(\"{}\")", self.as_str())
70 }
71}
72
73impl PartialOrd for Id {
74 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
75 Some(self.cmp(other))
76 }
77}
78
79impl Ord for Id {
80 fn cmp(&self, other: &Self) -> Ordering {
81 self.as_str().cmp(other.as_str())
82 }
83}
84
85impl AsRef<str> for Id {
86 fn as_ref(&self) -> &str {
87 self.as_str()
88 }
89}
90
91impl PartialEq<&Id> for Id {
92 fn eq(&self, other: &&Id) -> bool {
93 self == *other
94 }
95}
96
97impl PartialEq<Id> for &Id {
98 fn eq(&self, other: &Id) -> bool {
99 *self == other
100 }
101}
102
103pub struct CachedId {
120 s: &'static str,
121 cached: OnceLock<Id>,
122}
123
124impl CachedId {
125 pub const fn new(s: &'static str) -> Self {
127 Self {
128 s,
129 cached: OnceLock::new(),
130 }
131 }
132}
133
134impl Deref for CachedId {
135 type Target = Id;
136
137 #[expect(
138 clippy::disallowed_methods,
139 reason = "CachedId interns its static id once here"
140 )]
141 fn deref(&self) -> &Id {
142 self.cached.get_or_init(|| Id::new_static(self.s))
143 }
144}
145
146#[derive(Clone, Debug)]
149pub struct ReadContext {
150 ids: Arc<[Id]>,
151}
152
153impl ReadContext {
154 pub fn new(ids: impl Into<Arc<[Id]>>) -> Self {
156 Self { ids: ids.into() }
157 }
158
159 pub fn resolve(&self, idx: u16) -> Option<Id> {
161 self.ids.get(idx as usize).cloned()
162 }
163
164 pub fn ids(&self) -> &[Id] {
165 &self.ids
166 }
167}
168
169#[derive(Clone, Debug, Default)]
178pub struct Interner {
179 ids: Arc<RwLock<Vec<Id>>>,
183 allowed: Option<Arc<HashSet<Id>>>,
185}
186
187impl Interner {
188 pub fn new(ids: Vec<Id>) -> Self {
190 Self {
191 ids: Arc::new(RwLock::new(ids)),
192 allowed: None,
193 }
194 }
195
196 pub fn empty() -> Self {
198 Self::default()
199 }
200
201 pub fn with_allowed_ids(mut self, allowed: HashSet<Id>) -> Self {
206 self.allowed = Some(Arc::new(allowed));
207 self
208 }
209
210 pub fn intern(&self, id: &Id) -> Option<u16> {
212 if let Some(allowed) = &self.allowed
213 && !allowed.contains(id)
214 {
215 return None;
217 }
218
219 let mut ids = self.ids.write();
220 if let Some(idx) = ids.iter().position(|e| e == id) {
221 return Some(u16::try_from(idx).vortex_expect("Cannot have more than u16::MAX items"));
222 }
223
224 let idx = ids.len();
225 assert!(
226 idx < u16::MAX as usize,
227 "Cannot have more than u16::MAX items"
228 );
229 ids.push(*id);
230 Some(u16::try_from(idx).vortex_expect("checked already"))
231 }
232
233 pub fn to_ids(&self) -> Vec<Id> {
235 self.ids.read().clone()
236 }
237}
238
239#[cfg(test)]
240mod tests {
241 use vortex_utils::aliases::hash_set::HashSet;
242
243 use super::CachedId;
244 use super::Interner;
245
246 static VALID: CachedId = CachedId::new("vortex.test.valid");
247 static INVALID: CachedId = CachedId::new("vortex.test.invalid");
248
249 #[test]
250 fn context_filters_interned_ids() {
251 let valid = *VALID;
252 let invalid = *INVALID;
253 let context = Interner::empty().with_allowed_ids(HashSet::from([valid]));
254
255 assert_eq!(context.intern(&valid), Some(0));
256 assert_eq!(context.intern(&valid), Some(0));
257 assert_eq!(context.intern(&invalid), None);
258 assert_eq!(context.to_ids(), [valid]);
259 }
260}