Skip to main content

oxihuman_core/
id_pool.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! ID pool: recycles released IDs before allocating new ones.
6
7/// An opaque ID.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
9#[allow(dead_code)]
10pub struct Id(pub u64);
11
12/// ID pool.
13#[derive(Debug)]
14#[allow(dead_code)]
15pub struct IdPool {
16    next: u64,
17    recycled: Vec<u64>,
18    active: std::collections::HashSet<u64>,
19}
20
21/// Create a new IdPool starting at `start`.
22#[allow(dead_code)]
23pub fn new_id_pool(start: u64) -> IdPool {
24    IdPool {
25        next: start,
26        recycled: Vec::new(),
27        active: std::collections::HashSet::new(),
28    }
29}
30
31/// Allocate an ID.
32#[allow(dead_code)]
33pub fn id_alloc(pool: &mut IdPool) -> Id {
34    if let Some(id) = pool.recycled.pop() {
35        pool.active.insert(id);
36        Id(id)
37    } else {
38        let id = pool.next;
39        pool.next += 1;
40        pool.active.insert(id);
41        Id(id)
42    }
43}
44
45/// Release an ID back to the pool.
46#[allow(dead_code)]
47pub fn id_release(pool: &mut IdPool, id: Id) -> bool {
48    if pool.active.remove(&id.0) {
49        pool.recycled.push(id.0);
50        true
51    } else {
52        false
53    }
54}
55
56/// Whether an ID is currently active.
57#[allow(dead_code)]
58pub fn id_is_active(pool: &IdPool, id: Id) -> bool {
59    pool.active.contains(&id.0)
60}
61
62/// Count of active IDs.
63#[allow(dead_code)]
64pub fn id_active_count(pool: &IdPool) -> usize {
65    pool.active.len()
66}
67
68/// Count of recycled (waiting) IDs.
69#[allow(dead_code)]
70pub fn id_recycled_count(pool: &IdPool) -> usize {
71    pool.recycled.len()
72}
73
74/// Peek at the next ID that would be allocated without recycling.
75#[allow(dead_code)]
76pub fn id_peek_next(pool: &IdPool) -> u64 {
77    pool.next
78}
79
80/// Release all active IDs at once.
81#[allow(dead_code)]
82pub fn id_release_all(pool: &mut IdPool) {
83    let ids: Vec<u64> = pool.active.drain().collect();
84    pool.recycled.extend(ids);
85}
86
87/// Total IDs ever allocated (next - start not tracked, use active + recycled).
88#[allow(dead_code)]
89pub fn id_total(pool: &IdPool) -> usize {
90    pool.active.len() + pool.recycled.len()
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96
97    #[test]
98    fn test_alloc_sequential() {
99        let mut pool = new_id_pool(1);
100        let a = id_alloc(&mut pool);
101        let b = id_alloc(&mut pool);
102        assert_eq!(a.0, 1);
103        assert_eq!(b.0, 2);
104    }
105
106    #[test]
107    fn test_release_and_reuse() {
108        let mut pool = new_id_pool(0);
109        let a = id_alloc(&mut pool);
110        id_release(&mut pool, a);
111        let b = id_alloc(&mut pool);
112        assert_eq!(b.0, a.0);
113    }
114
115    #[test]
116    fn test_is_active() {
117        let mut pool = new_id_pool(0);
118        let id = id_alloc(&mut pool);
119        assert!(id_is_active(&pool, id));
120        id_release(&mut pool, id);
121        assert!(!id_is_active(&pool, id));
122    }
123
124    #[test]
125    fn test_double_release() {
126        let mut pool = new_id_pool(0);
127        let id = id_alloc(&mut pool);
128        assert!(id_release(&mut pool, id));
129        assert!(!id_release(&mut pool, id));
130    }
131
132    #[test]
133    fn test_active_count() {
134        let mut pool = new_id_pool(0);
135        id_alloc(&mut pool);
136        id_alloc(&mut pool);
137        assert_eq!(id_active_count(&pool), 2);
138    }
139
140    #[test]
141    fn test_recycled_count() {
142        let mut pool = new_id_pool(0);
143        let id = id_alloc(&mut pool);
144        id_release(&mut pool, id);
145        assert_eq!(id_recycled_count(&pool), 1);
146    }
147
148    #[test]
149    fn test_release_all() {
150        let mut pool = new_id_pool(0);
151        id_alloc(&mut pool);
152        id_alloc(&mut pool);
153        id_release_all(&mut pool);
154        assert_eq!(id_active_count(&pool), 0);
155        assert_eq!(id_recycled_count(&pool), 2);
156    }
157
158    #[test]
159    fn test_peek_next() {
160        let pool = new_id_pool(10);
161        assert_eq!(id_peek_next(&pool), 10);
162    }
163
164    #[test]
165    fn test_total() {
166        let mut pool = new_id_pool(0);
167        id_alloc(&mut pool);
168        let b = id_alloc(&mut pool);
169        id_release(&mut pool, b);
170        assert_eq!(id_total(&pool), 2);
171    }
172}