Skip to main content

stylex_structures/
uid_generator.rs

1use 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
12// Global counters map, protected by Mutex for thread safety
13static GLOBAL_COUNTERS: Lazy<Mutex<FxHashMap<String, AtomicUsize>>> =
14  Lazy::new(|| Mutex::new(FxHashMap::default()));
15
16// Thread-local counters for test isolation
17thread_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  /// Creates a new UidGenerator with the given prefix and counter mode.
30  pub fn new(prefix: &str, mode: CounterMode) -> Self {
31    match mode {
32      CounterMode::_Global => {
33        // Ensure the counter for this prefix exists in global counters
34        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        // These modes don't need global counter initialization
45      },
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        // Thread unique mode doesn't maintain persistent counters
74      },
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  /// Generates a unique identifier.
146  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"); // Same because local counters are independent
174    assert_eq!(gen1.generate(), "_test2");
175    assert_eq!(gen2.generate(), "_test2"); // Each maintains its own counter
176  }
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"); // Shared within same thread
185    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    // Both should contain thread ID and be unique
195    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        // Wait for both threads to be ready
214        barrier.wait();
215
216        // Each thread should get the same sequence independently
217        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    // Both threads should generate the same sequence because they're isolated
226    assert_eq!(results[0].1, vec!["_test", "_test2", "_test3"]);
227    assert_eq!(results[1].1, vec!["_test", "_test2", "_test3"]);
228  }
229}