stylex_structures/
uid_generator.rs1use std::sync::Mutex;
2use std::sync::atomic::{AtomicUsize, Ordering};
3use std::thread;
4
5use rustc_hash::FxHashMap;
6use stylex_enums::counter_mode::CounterMode;
7use stylex_macros::stylex_panic;
8use swc_core::ecma::ast::Ident;
9
10use once_cell::sync::Lazy;
11
12static GLOBAL_COUNTERS: Lazy<Mutex<FxHashMap<String, AtomicUsize>>> =
14 Lazy::new(|| Mutex::new(FxHashMap::default()));
15
16thread_local! {
18 static THREAD_LOCAL_COUNTERS: std::cell::RefCell<FxHashMap<String, usize>> =
19 std::cell::RefCell::new(FxHashMap::default());
20}
21
22pub struct UidGenerator {
23 prefix: String,
24 mode: CounterMode,
25 local_counter: AtomicUsize,
26}
27
28impl UidGenerator {
29 pub fn new(prefix: &str, mode: CounterMode) -> Self {
31 match mode {
32 CounterMode::_Global => {
33 let mut counters = match GLOBAL_COUNTERS.lock() {
35 Ok(c) => c,
36 Err(e) => stylex_panic!("GLOBAL_COUNTERS mutex poisoned: {}", e),
37 };
38 counters
39 .entry(prefix.to_string())
40 .or_insert_with(|| AtomicUsize::new(1));
41 drop(counters);
42 },
43 CounterMode::Local | CounterMode::ThreadLocal | CounterMode::_ThreadUnique => {
44 },
46 }
47
48 Self {
49 prefix: prefix.to_string(),
50 mode,
51 local_counter: AtomicUsize::new(1),
52 }
53 }
54
55 pub fn clear(&mut self) {
56 match self.mode {
57 CounterMode::_Global => {
58 let mut counters = match GLOBAL_COUNTERS.lock() {
59 Ok(c) => c,
60 Err(e) => stylex_panic!("GLOBAL_COUNTERS mutex poisoned: {}", e),
61 };
62 counters.remove(&self.prefix);
63 },
64 CounterMode::Local => {
65 self.local_counter.store(1, Ordering::SeqCst);
66 },
67 CounterMode::ThreadLocal => {
68 THREAD_LOCAL_COUNTERS.with(|counters| {
69 counters.borrow_mut().remove(&self.prefix);
70 });
71 },
72 CounterMode::_ThreadUnique => {
73 },
75 }
76 }
77
78 pub fn generate(&self) -> String {
79 match self.mode {
80 CounterMode::_Global => {
81 let counters = match GLOBAL_COUNTERS.lock() {
82 Ok(c) => c,
83 Err(e) => stylex_panic!("GLOBAL_COUNTERS mutex poisoned: {}", e),
84 };
85 let counter = match counters.get(&self.prefix) {
86 Some(c) => c,
87 None => stylex_panic!(
88 "Counter for prefix '{}' not found in GLOBAL_COUNTERS",
89 self.prefix
90 ),
91 };
92 let count = counter.fetch_add(1, Ordering::SeqCst);
93
94 let count_string = if count < 2 {
95 String::default()
96 } else {
97 count.to_string()
98 };
99
100 format!("_{}{}", self.prefix, count_string)
101 },
102 CounterMode::Local => {
103 let count = self.local_counter.fetch_add(1, Ordering::SeqCst);
104
105 let count_string = if count < 2 {
106 String::default()
107 } else {
108 count.to_string()
109 };
110
111 format!("_{}{}", self.prefix, count_string)
112 },
113 CounterMode::ThreadLocal => {
114 let count = THREAD_LOCAL_COUNTERS.with(|counters| {
115 let mut counters = counters.borrow_mut();
116 let counter = counters.entry(self.prefix.clone()).or_insert(1);
117 let current_count = *counter;
118 *counter += 1;
119 current_count
120 });
121
122 let count_string = if count < 2 {
123 String::default()
124 } else {
125 count.to_string()
126 };
127
128 format!("_{}{}", self.prefix, count_string)
129 },
130 CounterMode::_ThreadUnique => {
131 let thread_id = thread::current().id();
132 let count = self.local_counter.fetch_add(1, Ordering::SeqCst);
133
134 let count_string = if count < 2 {
135 String::default()
136 } else {
137 count.to_string()
138 };
139
140 format!("_{}_{:?}{}", self.prefix, thread_id, count_string)
141 },
142 }
143 }
144
145 pub fn generate_ident(&self) -> Ident {
147 let unique_name = self.generate();
148
149 Ident::from(unique_name.as_str())
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156
157 #[test]
158 fn test_global_counter_consistency() {
159 let gen1 = UidGenerator::new("test", CounterMode::_Global);
160 let gen2 = UidGenerator::new("test", CounterMode::_Global);
161
162 assert_eq!(gen1.generate(), "_test");
163 assert_eq!(gen2.generate(), "_test2");
164 assert_eq!(gen1.generate(), "_test3");
165 }
166
167 #[test]
168 fn test_local_counter_isolation() {
169 let gen1 = UidGenerator::new("test", CounterMode::Local);
170 let gen2 = UidGenerator::new("test", CounterMode::Local);
171
172 assert_eq!(gen1.generate(), "_test");
173 assert_eq!(gen2.generate(), "_test"); assert_eq!(gen1.generate(), "_test2");
175 assert_eq!(gen2.generate(), "_test2"); }
177
178 #[test]
179 fn test_thread_local_counter() {
180 let gen1 = UidGenerator::new("test", CounterMode::ThreadLocal);
181 let gen2 = UidGenerator::new("test", CounterMode::ThreadLocal);
182
183 assert_eq!(gen1.generate(), "_test");
184 assert_eq!(gen2.generate(), "_test2"); assert_eq!(gen1.generate(), "_test3");
186 }
187
188 #[test]
189 fn test_thread_unique_identifiers() {
190 let generator = UidGenerator::new("test", CounterMode::_ThreadUnique);
191 let id1 = generator.generate();
192 let id2 = generator.generate();
193
194 assert!(id1.starts_with("_test_"));
196 assert!(id2.starts_with("_test_"));
197 assert_ne!(id1, id2);
198 }
199
200 #[test]
201 fn test_parallel_thread_local_isolation() {
202 use std::sync::{Arc, Barrier};
203 use std::thread;
204
205 let barrier = Arc::new(Barrier::new(2));
206 let mut handles = vec![];
207
208 for thread_num in 0..2 {
209 let barrier = Arc::clone(&barrier);
210 let handle = thread::spawn(move || {
211 let generator = UidGenerator::new("test", CounterMode::ThreadLocal);
212
213 barrier.wait();
215
216 let results = (0..3).map(|_| generator.generate()).collect::<Vec<_>>();
218 (thread_num, results)
219 });
220 handles.push(handle);
221 }
222
223 let results: Vec<_> = handles.into_iter().map(|h| h.join().unwrap()).collect();
224
225 assert_eq!(results[0].1, vec!["_test", "_test2", "_test3"]);
227 assert_eq!(results[1].1, vec!["_test", "_test2", "_test3"]);
228 }
229}