sable_core/
handle.rs

1//! Generational index handles for safe resource references.
2//!
3//! Handles provide a safe way to reference resources that may be deleted.
4//! Each handle contains an index and a generation counter that is incremented
5//! when the slot is reused, preventing dangling references.
6//!
7//! # Thread Safety
8//!
9//! This module provides two allocator variants:
10//! - [`HandleAllocator`] — Single-threaded, zero-overhead allocator
11//! - [`ConcurrentHandleAllocator`] — Thread-safe allocator using `parking_lot` (requires `parallel` feature)
12//!
13//! # Parallel Iteration
14//!
15//! With the `parallel` feature enabled, [`HandleAllocator`] supports parallel iteration
16//! via rayon's `par_iter()` method.
17
18use std::marker::PhantomData;
19
20/// A generational index handle.
21///
22/// The handle contains:
23/// - An index into a resource array
24/// - A generation counter to detect stale references
25/// - A phantom type marker for type safety
26#[derive(Debug)]
27pub struct Handle<T> {
28    index: u32,
29    generation: u32,
30    _marker: PhantomData<T>,
31}
32
33impl<T> Clone for Handle<T> {
34    fn clone(&self) -> Self {
35        *self
36    }
37}
38
39impl<T> Copy for Handle<T> {}
40
41impl<T> PartialEq for Handle<T> {
42    fn eq(&self, other: &Self) -> bool {
43        self.index == other.index && self.generation == other.generation
44    }
45}
46
47impl<T> Eq for Handle<T> {}
48
49impl<T> std::hash::Hash for Handle<T> {
50    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
51        self.index.hash(state);
52        self.generation.hash(state);
53    }
54}
55
56impl<T> Handle<T> {
57    /// Creates a new handle with the given index and generation.
58    #[inline]
59    #[must_use]
60    pub const fn new(index: u32, generation: u32) -> Self {
61        Self {
62            index,
63            generation,
64            _marker: PhantomData,
65        }
66    }
67
68    /// Returns the index portion of the handle.
69    #[inline]
70    #[must_use]
71    pub const fn index(self) -> u32 {
72        self.index
73    }
74
75    /// Returns the generation portion of the handle.
76    #[inline]
77    #[must_use]
78    pub const fn generation(self) -> u32 {
79        self.generation
80    }
81
82    /// Creates a dangling handle that doesn't point to any valid resource.
83    #[inline]
84    #[must_use]
85    pub const fn dangling() -> Self {
86        Self {
87            index: u32::MAX,
88            generation: 0,
89            _marker: PhantomData,
90        }
91    }
92
93    /// Checks if this handle is dangling.
94    #[inline]
95    #[must_use]
96    pub const fn is_dangling(self) -> bool {
97        self.index == u32::MAX
98    }
99}
100
101impl<T> Default for Handle<T> {
102    fn default() -> Self {
103        Self::dangling()
104    }
105}
106
107/// Entry in the handle allocator.
108#[derive(Debug, Clone)]
109struct AllocatorEntry<T> {
110    /// The stored value, if any.
111    value: Option<T>,
112    /// Current generation for this slot.
113    generation: u32,
114}
115
116impl<T> Default for AllocatorEntry<T> {
117    fn default() -> Self {
118        Self {
119            value: None,
120            generation: 0,
121        }
122    }
123}
124
125/// An allocator for generational handles.
126///
127/// Manages allocation and deallocation of handles, ensuring that
128/// stale handles are detected.
129#[derive(Debug)]
130pub struct HandleAllocator<T> {
131    entries: Vec<AllocatorEntry<T>>,
132    free_list: Vec<u32>,
133}
134
135impl<T> Default for HandleAllocator<T> {
136    fn default() -> Self {
137        Self::new()
138    }
139}
140
141impl<T> HandleAllocator<T> {
142    /// Creates a new empty allocator.
143    #[must_use]
144    pub fn new() -> Self {
145        Self {
146            entries: Vec::new(),
147            free_list: Vec::new(),
148        }
149    }
150
151    /// Creates an allocator with pre-allocated capacity.
152    #[must_use]
153    pub fn with_capacity(capacity: usize) -> Self {
154        Self {
155            entries: Vec::with_capacity(capacity),
156            free_list: Vec::new(),
157        }
158    }
159
160    /// Allocates a handle for the given value.
161    #[must_use]
162    pub fn allocate(&mut self, value: T) -> Handle<T> {
163        if let Some(index) = self.free_list.pop() {
164            let entry = &mut self.entries[index as usize];
165            entry.value = Some(value);
166            Handle::new(index, entry.generation)
167        } else {
168            let index = self.entries.len() as u32;
169            self.entries.push(AllocatorEntry {
170                value: Some(value),
171                generation: 0,
172            });
173            Handle::new(index, 0)
174        }
175    }
176
177    /// Deallocates a handle, returning the value if the handle was valid.
178    pub fn deallocate(&mut self, handle: Handle<T>) -> Option<T> {
179        let index = handle.index as usize;
180        if index >= self.entries.len() {
181            return None;
182        }
183
184        let entry = &mut self.entries[index];
185        if entry.generation != handle.generation {
186            return None;
187        }
188
189        let value = entry.value.take();
190        if value.is_some() {
191            entry.generation = entry.generation.wrapping_add(1);
192            self.free_list.push(handle.index);
193        }
194        value
195    }
196
197    /// Gets a reference to the value, if the handle is valid.
198    #[must_use]
199    pub fn get(&self, handle: Handle<T>) -> Option<&T> {
200        let index = handle.index as usize;
201        if index >= self.entries.len() {
202            return None;
203        }
204
205        let entry = &self.entries[index];
206        if entry.generation != handle.generation {
207            return None;
208        }
209
210        entry.value.as_ref()
211    }
212
213    /// Gets a mutable reference to the value, if the handle is valid.
214    #[must_use]
215    pub fn get_mut(&mut self, handle: Handle<T>) -> Option<&mut T> {
216        let index = handle.index as usize;
217        if index >= self.entries.len() {
218            return None;
219        }
220
221        let entry = &mut self.entries[index];
222        if entry.generation != handle.generation {
223            return None;
224        }
225
226        entry.value.as_mut()
227    }
228
229    /// Checks if a handle is valid.
230    #[must_use]
231    pub fn is_valid(&self, handle: Handle<T>) -> bool {
232        self.get(handle).is_some()
233    }
234
235    /// Returns the number of allocated handles.
236    #[must_use]
237    pub fn len(&self) -> usize {
238        self.entries.len() - self.free_list.len()
239    }
240
241    /// Returns true if no handles are allocated.
242    #[must_use]
243    pub fn is_empty(&self) -> bool {
244        self.len() == 0
245    }
246
247    /// Returns the total capacity.
248    #[must_use]
249    pub fn capacity(&self) -> usize {
250        self.entries.capacity()
251    }
252
253    /// Clears all entries, deallocating all handles.
254    pub fn clear(&mut self) {
255        self.entries.clear();
256        self.free_list.clear();
257    }
258
259    /// Iterates over all valid (handle, value) pairs.
260    pub fn iter(&self) -> impl Iterator<Item = (Handle<T>, &T)> {
261        self.entries
262            .iter()
263            .enumerate()
264            .filter_map(|(index, entry)| {
265                entry
266                    .value
267                    .as_ref()
268                    .map(|value| (Handle::new(index as u32, entry.generation), value))
269            })
270    }
271
272    /// Iterates over all valid (handle, value) pairs mutably.
273    pub fn iter_mut(&mut self) -> impl Iterator<Item = (Handle<T>, &mut T)> {
274        self.entries
275            .iter_mut()
276            .enumerate()
277            .filter_map(|(index, entry)| {
278                let generation = entry.generation;
279                entry
280                    .value
281                    .as_mut()
282                    .map(|value| (Handle::new(index as u32, generation), value))
283            })
284    }
285
286    /// Returns an iterator over all valid values (without handles).
287    pub fn values(&self) -> impl Iterator<Item = &T> {
288        self.entries.iter().filter_map(|entry| entry.value.as_ref())
289    }
290
291    /// Returns a mutable iterator over all valid values (without handles).
292    pub fn values_mut(&mut self) -> impl Iterator<Item = &mut T> {
293        self.entries
294            .iter_mut()
295            .filter_map(|entry| entry.value.as_mut())
296    }
297}
298
299// ============================================================================
300// Parallel Iteration Support (requires `parallel` feature)
301// ============================================================================
302
303#[cfg(feature = "parallel")]
304impl<T: Send + Sync> HandleAllocator<T> {
305    /// Returns a parallel iterator over all valid values.
306    ///
307    /// This uses rayon for work-stealing parallelism.
308    ///
309    /// # Example
310    ///
311    /// ```rust,ignore
312    /// use rayon::prelude::*;
313    ///
314    /// allocator.par_values().for_each(|value| {
315    ///     // Process value in parallel
316    /// });
317    /// ```
318    #[must_use]
319    pub fn par_values(&self) -> impl rayon::iter::ParallelIterator<Item = &T> {
320        use rayon::prelude::*;
321        self.entries
322            .par_iter()
323            .filter_map(|entry| entry.value.as_ref())
324    }
325
326    /// Returns a parallel mutable iterator over all valid values.
327    pub fn par_values_mut(&mut self) -> impl rayon::iter::ParallelIterator<Item = &mut T> {
328        use rayon::prelude::*;
329        self.entries
330            .par_iter_mut()
331            .filter_map(|entry| entry.value.as_mut())
332    }
333}
334
335// ============================================================================
336// Concurrent Handle Allocator (requires `parallel` feature)
337// ============================================================================
338
339#[cfg(feature = "parallel")]
340mod concurrent {
341    use super::{AllocatorEntry, Handle};
342    use parking_lot::{Mutex, RwLock};
343
344    /// A thread-safe handle allocator using `parking_lot` primitives.
345    ///
346    /// This allocator allows concurrent read access and synchronized write access.
347    /// Use this when handles need to be shared across threads.
348    ///
349    /// # Performance Characteristics
350    ///
351    /// - **Reads** (`get`): Uses `RwLock`, multiple concurrent readers allowed
352    /// - **Writes** (`allocate`, `deallocate`): Briefly locks a mutex for the free list
353    ///
354    /// # Example
355    ///
356    /// ```rust,ignore
357    /// use std::sync::Arc;
358    ///
359    /// let allocator = Arc::new(ConcurrentHandleAllocator::new());
360    ///
361    /// // Clone for another thread
362    /// let alloc_clone = Arc::clone(&allocator);
363    /// std::thread::spawn(move || {
364    ///     let handle = alloc_clone.allocate(42);
365    ///     // ...
366    /// });
367    /// ```
368    #[derive(Debug)]
369    pub struct ConcurrentHandleAllocator<T> {
370        entries: RwLock<Vec<AllocatorEntry<T>>>,
371        free_list: Mutex<Vec<u32>>,
372    }
373
374    impl<T> Default for ConcurrentHandleAllocator<T> {
375        fn default() -> Self {
376            Self::new()
377        }
378    }
379
380    impl<T> ConcurrentHandleAllocator<T> {
381        /// Creates a new empty concurrent allocator.
382        #[must_use]
383        pub fn new() -> Self {
384            Self {
385                entries: RwLock::new(Vec::new()),
386                free_list: Mutex::new(Vec::new()),
387            }
388        }
389
390        /// Creates a concurrent allocator with pre-allocated capacity.
391        #[must_use]
392        pub fn with_capacity(capacity: usize) -> Self {
393            Self {
394                entries: RwLock::new(Vec::with_capacity(capacity)),
395                free_list: Mutex::new(Vec::new()),
396            }
397        }
398
399        /// Allocates a handle for the given value.
400        ///
401        /// This operation briefly locks the free list and entries for writing.
402        pub fn allocate(&self, value: T) -> Handle<T> {
403            let mut free_list = self.free_list.lock();
404
405            if let Some(index) = free_list.pop() {
406                let mut entries = self.entries.write();
407                let entry = &mut entries[index as usize];
408                entry.value = Some(value);
409                Handle::new(index, entry.generation)
410            } else {
411                let mut entries = self.entries.write();
412                let index = entries.len() as u32;
413                entries.push(AllocatorEntry {
414                    value: Some(value),
415                    generation: 0,
416                });
417                Handle::new(index, 0)
418            }
419        }
420
421        /// Deallocates a handle, returning the value if the handle was valid.
422        pub fn deallocate(&self, handle: Handle<T>) -> Option<T> {
423            let index = handle.index as usize;
424
425            let mut entries = self.entries.write();
426            if index >= entries.len() {
427                return None;
428            }
429
430            let entry = &mut entries[index];
431            if entry.generation != handle.generation {
432                return None;
433            }
434
435            let value = entry.value.take();
436            if value.is_some() {
437                entry.generation = entry.generation.wrapping_add(1);
438                drop(entries); // Release write lock before acquiring mutex
439                self.free_list.lock().push(handle.index);
440            }
441            value
442        }
443
444        /// Gets a reference to the value, if the handle is valid.
445        ///
446        /// This operation takes a read lock and returns a guard.
447        #[must_use]
448        pub fn get(&self, handle: Handle<T>) -> Option<ValueRef<'_, T>> {
449            let index = handle.index as usize;
450            let entries = self.entries.read();
451
452            if index >= entries.len() {
453                return None;
454            }
455
456            if entries[index].generation != handle.generation {
457                return None;
458            }
459
460            entries[index].value.as_ref()?;
461
462            Some(ValueRef {
463                guard: entries,
464                index,
465            })
466        }
467
468        /// Checks if a handle is valid.
469        #[must_use]
470        pub fn is_valid(&self, handle: Handle<T>) -> bool {
471            let index = handle.index as usize;
472            let entries = self.entries.read();
473
474            if index >= entries.len() {
475                return false;
476            }
477
478            let entry = &entries[index];
479            entry.generation == handle.generation && entry.value.is_some()
480        }
481
482        /// Returns the number of allocated handles.
483        #[must_use]
484        pub fn len(&self) -> usize {
485            let entries = self.entries.read();
486            let free_list = self.free_list.lock();
487            entries.len() - free_list.len()
488        }
489
490        /// Returns true if no handles are allocated.
491        #[must_use]
492        pub fn is_empty(&self) -> bool {
493            self.len() == 0
494        }
495
496        /// Clears all entries, deallocating all handles.
497        pub fn clear(&self) {
498            let mut entries = self.entries.write();
499            let mut free_list = self.free_list.lock();
500            entries.clear();
501            free_list.clear();
502        }
503    }
504
505    /// A guard that holds a read lock and provides access to a value.
506    pub struct ValueRef<'a, T> {
507        guard: parking_lot::RwLockReadGuard<'a, Vec<AllocatorEntry<T>>>,
508        index: usize,
509    }
510
511    impl<T> std::ops::Deref for ValueRef<'_, T> {
512        type Target = T;
513
514        fn deref(&self) -> &Self::Target {
515            // SAFETY: We verified the value exists before creating this guard
516            self.guard[self.index].value.as_ref().unwrap()
517        }
518    }
519}
520
521#[cfg(feature = "parallel")]
522pub use concurrent::ConcurrentHandleAllocator;
523
524#[cfg(test)]
525mod tests {
526    use super::*;
527
528    #[test]
529    fn test_handle_creation() {
530        let handle: Handle<i32> = Handle::new(0, 0);
531        assert_eq!(handle.index(), 0);
532        assert_eq!(handle.generation(), 0);
533    }
534
535    #[test]
536    fn test_handle_dangling() {
537        let handle: Handle<i32> = Handle::dangling();
538        assert!(handle.is_dangling());
539    }
540
541    #[test]
542    fn test_handle_default() {
543        let handle: Handle<i32> = Handle::default();
544        assert!(handle.is_dangling());
545    }
546
547    #[test]
548    fn test_handle_equality() {
549        let h1: Handle<i32> = Handle::new(1, 2);
550        let h2: Handle<i32> = Handle::new(1, 2);
551        let h3: Handle<i32> = Handle::new(1, 3);
552
553        assert_eq!(h1, h2);
554        assert_ne!(h1, h3);
555    }
556
557    #[test]
558    fn test_handle_copy() {
559        let h1: Handle<i32> = Handle::new(1, 2);
560        let h2 = h1;
561        assert_eq!(h1, h2);
562    }
563
564    #[test]
565    fn test_allocator_allocate() {
566        let mut alloc = HandleAllocator::new();
567        let handle = alloc.allocate(42);
568
569        assert_eq!(handle.index(), 0);
570        assert_eq!(handle.generation(), 0);
571        assert_eq!(alloc.len(), 1);
572    }
573
574    #[test]
575    fn test_allocator_get() {
576        let mut alloc = HandleAllocator::new();
577        let handle = alloc.allocate(42);
578
579        assert_eq!(alloc.get(handle), Some(&42));
580    }
581
582    #[test]
583    fn test_allocator_get_mut() {
584        let mut alloc = HandleAllocator::new();
585        let handle = alloc.allocate(42);
586
587        if let Some(value) = alloc.get_mut(handle) {
588            *value = 100;
589        }
590
591        assert_eq!(alloc.get(handle), Some(&100));
592    }
593
594    #[test]
595    fn test_allocator_deallocate() {
596        let mut alloc = HandleAllocator::new();
597        let handle = alloc.allocate(42);
598
599        let value = alloc.deallocate(handle);
600        assert_eq!(value, Some(42));
601        assert_eq!(alloc.len(), 0);
602    }
603
604    #[test]
605    fn test_allocator_stale_handle() {
606        let mut alloc = HandleAllocator::new();
607        let handle1 = alloc.allocate(42);
608        alloc.deallocate(handle1);
609
610        // Stale handle should not be valid
611        assert!(!alloc.is_valid(handle1));
612        assert_eq!(alloc.get(handle1), None);
613    }
614
615    #[test]
616    fn test_allocator_reuse_slot() {
617        let mut alloc = HandleAllocator::new();
618        let handle1 = alloc.allocate(42);
619        alloc.deallocate(handle1);
620        let handle2 = alloc.allocate(100);
621
622        // Same index, different generation
623        assert_eq!(handle2.index(), handle1.index());
624        assert_eq!(handle2.generation(), handle1.generation() + 1);
625
626        // Old handle invalid, new handle valid
627        assert!(!alloc.is_valid(handle1));
628        assert!(alloc.is_valid(handle2));
629        assert_eq!(alloc.get(handle2), Some(&100));
630    }
631
632    #[test]
633    fn test_allocator_multiple_allocations() {
634        let mut alloc = HandleAllocator::new();
635        let h1 = alloc.allocate(1);
636        let h2 = alloc.allocate(2);
637        let h3 = alloc.allocate(3);
638
639        assert_eq!(alloc.len(), 3);
640        assert_eq!(alloc.get(h1), Some(&1));
641        assert_eq!(alloc.get(h2), Some(&2));
642        assert_eq!(alloc.get(h3), Some(&3));
643    }
644
645    #[test]
646    fn test_allocator_deallocate_middle() {
647        let mut alloc = HandleAllocator::new();
648        let h1 = alloc.allocate(1);
649        let h2 = alloc.allocate(2);
650        let h3 = alloc.allocate(3);
651
652        alloc.deallocate(h2);
653
654        assert!(alloc.is_valid(h1));
655        assert!(!alloc.is_valid(h2));
656        assert!(alloc.is_valid(h3));
657        assert_eq!(alloc.len(), 2);
658    }
659
660    #[test]
661    fn test_allocator_double_deallocate() {
662        let mut alloc = HandleAllocator::new();
663        let handle = alloc.allocate(42);
664
665        assert_eq!(alloc.deallocate(handle), Some(42));
666        assert_eq!(alloc.deallocate(handle), None);
667    }
668
669    #[test]
670    fn test_allocator_invalid_handle() {
671        let alloc: HandleAllocator<i32> = HandleAllocator::new();
672        let invalid = Handle::new(999, 0);
673
674        assert!(!alloc.is_valid(invalid));
675        assert_eq!(alloc.get(invalid), None);
676    }
677
678    #[test]
679    fn test_allocator_clear() {
680        let mut alloc = HandleAllocator::new();
681        let _ = alloc.allocate(1);
682        let _ = alloc.allocate(2);
683        let _ = alloc.allocate(3);
684
685        alloc.clear();
686
687        assert!(alloc.is_empty());
688        assert_eq!(alloc.len(), 0);
689    }
690
691    #[test]
692    fn test_allocator_iter() {
693        let mut alloc = HandleAllocator::new();
694        let h1 = alloc.allocate(1);
695        let _h2 = alloc.allocate(2);
696        let h3 = alloc.allocate(3);
697
698        alloc.deallocate(h1);
699
700        let values: Vec<_> = alloc.iter().map(|(_, v)| *v).collect();
701        assert_eq!(values.len(), 2);
702        assert!(values.contains(&2));
703        assert!(values.contains(&3));
704        assert!(!values.contains(&1));
705
706        // Check that we can get the handles back
707        for (handle, _) in alloc.iter() {
708            assert!(alloc.is_valid(handle));
709            // h3 should still be valid
710            if handle == h3 {
711                assert_eq!(alloc.get(handle), Some(&3));
712            }
713        }
714    }
715
716    #[test]
717    fn test_allocator_iter_mut() {
718        let mut alloc = HandleAllocator::new();
719        let _ = alloc.allocate(1);
720        let _ = alloc.allocate(2);
721        let _ = alloc.allocate(3);
722
723        for (_, value) in alloc.iter_mut() {
724            *value *= 10;
725        }
726
727        let values: Vec<_> = alloc.iter().map(|(_, v)| *v).collect();
728        assert!(values.contains(&10));
729        assert!(values.contains(&20));
730        assert!(values.contains(&30));
731    }
732
733    #[test]
734    fn test_allocator_with_capacity() {
735        let alloc: HandleAllocator<i32> = HandleAllocator::with_capacity(100);
736        assert!(alloc.capacity() >= 100);
737        assert!(alloc.is_empty());
738    }
739
740    #[test]
741    fn test_handle_hash() {
742        use std::collections::HashSet;
743
744        let mut set = HashSet::new();
745        let h1: Handle<i32> = Handle::new(1, 0);
746        let h2: Handle<i32> = Handle::new(2, 0);
747        let h3: Handle<i32> = Handle::new(1, 0);
748
749        set.insert(h1);
750        set.insert(h2);
751        set.insert(h3); // Same as h1
752
753        assert_eq!(set.len(), 2);
754    }
755
756    #[test]
757    fn test_values_iterator() {
758        let mut alloc = HandleAllocator::new();
759        let _ = alloc.allocate(1);
760        let h2 = alloc.allocate(2);
761        let _ = alloc.allocate(3);
762
763        alloc.deallocate(h2);
764
765        let values: Vec<_> = alloc.values().copied().collect();
766        assert_eq!(values.len(), 2);
767        assert!(values.contains(&1));
768        assert!(values.contains(&3));
769        assert!(!values.contains(&2));
770    }
771}
772
773#[cfg(all(test, feature = "parallel"))]
774mod parallel_tests {
775    use super::*;
776    use rayon::prelude::*;
777
778    #[test]
779    fn test_par_values() {
780        let mut alloc = HandleAllocator::new();
781        for i in 0..1000 {
782            let _ = alloc.allocate(i);
783        }
784
785        let sum: i32 = alloc.par_values().sum();
786        assert_eq!(sum, (0..1000).sum());
787    }
788
789    #[test]
790    fn test_par_values_mut() {
791        let mut alloc = HandleAllocator::new();
792        for i in 0..100 {
793            let _ = alloc.allocate(i);
794        }
795
796        alloc.par_values_mut().for_each(|v| *v *= 2);
797
798        let sum: i32 = alloc.values().copied().sum();
799        assert_eq!(sum, (0..100).map(|i| i * 2).sum());
800    }
801
802    #[test]
803    fn test_concurrent_allocator_basic() {
804        let alloc = ConcurrentHandleAllocator::new();
805        let h1 = alloc.allocate(42);
806        let h2 = alloc.allocate(100);
807
808        assert!(alloc.is_valid(h1));
809        assert!(alloc.is_valid(h2));
810        assert_eq!(alloc.len(), 2);
811
812        assert_eq!(*alloc.get(h1).unwrap(), 42);
813        assert_eq!(*alloc.get(h2).unwrap(), 100);
814    }
815
816    #[test]
817    fn test_concurrent_allocator_deallocate() {
818        let alloc = ConcurrentHandleAllocator::new();
819        let h1 = alloc.allocate(42);
820
821        assert_eq!(alloc.deallocate(h1), Some(42));
822        assert!(!alloc.is_valid(h1));
823        assert!(alloc.is_empty());
824    }
825
826    #[test]
827    fn test_concurrent_allocator_stale_handle() {
828        let alloc = ConcurrentHandleAllocator::new();
829        let h1 = alloc.allocate(42);
830        alloc.deallocate(h1);
831        let h2 = alloc.allocate(100);
832
833        assert!(!alloc.is_valid(h1));
834        assert!(alloc.is_valid(h2));
835        assert_eq!(h2.index(), h1.index());
836        assert_ne!(h2.generation(), h1.generation());
837    }
838
839    #[test]
840    fn test_concurrent_allocator_threaded() {
841        use std::sync::Arc;
842        use std::thread;
843
844        let alloc = Arc::new(ConcurrentHandleAllocator::new());
845        let mut handles = Vec::new();
846
847        // Spawn threads that allocate
848        let threads: Vec<_> = (0..4)
849            .map(|t| {
850                let alloc = Arc::clone(&alloc);
851                thread::spawn(move || {
852                    let mut local_handles = Vec::new();
853                    for i in 0..100 {
854                        local_handles.push(alloc.allocate(t * 100 + i));
855                    }
856                    local_handles
857                })
858            })
859            .collect();
860
861        for t in threads {
862            handles.extend(t.join().unwrap());
863        }
864
865        assert_eq!(alloc.len(), 400);
866
867        // All handles should be valid
868        for handle in &handles {
869            assert!(alloc.is_valid(*handle));
870        }
871    }
872}