Skip to main content

oxihuman_core/
pool_allocator.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Fixed-size object pool allocator.
6
7/// A slot in the pool.
8#[derive(Debug, Clone)]
9pub struct PoolSlot<T> {
10    pub value: T,
11    pub generation: u32,
12}
13
14/// Handle into the pool.
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16pub struct PoolHandle {
17    pub index: usize,
18    pub generation: u32,
19}
20
21/// Fixed-capacity pool allocator.
22pub struct PoolAllocator<T> {
23    slots: Vec<Option<PoolSlot<T>>>,
24    free: Vec<usize>,
25    generations: Vec<u32>,
26    capacity: usize,
27    live_count: usize,
28}
29
30#[allow(dead_code)]
31impl<T: Clone> PoolAllocator<T> {
32    pub fn new(capacity: usize) -> Self {
33        let slots = (0..capacity).map(|_| None).collect();
34        let free = (0..capacity).rev().collect();
35        let generations = vec![0u32; capacity];
36        PoolAllocator {
37            slots,
38            free,
39            generations,
40            capacity,
41            live_count: 0,
42        }
43    }
44
45    pub fn alloc(&mut self, value: T) -> Option<PoolHandle> {
46        let index = self.free.pop()?;
47        self.generations[index] += 1;
48        let gen = self.generations[index];
49        self.slots[index] = Some(PoolSlot {
50            value,
51            generation: gen,
52        });
53        self.live_count += 1;
54        Some(PoolHandle {
55            index,
56            generation: gen,
57        })
58    }
59
60    pub fn free(&mut self, handle: PoolHandle) -> bool {
61        if handle.index >= self.capacity {
62            return false;
63        }
64        match &self.slots[handle.index] {
65            Some(s) if s.generation == handle.generation => {
66                self.slots[handle.index] = None;
67                self.free.push(handle.index);
68                self.live_count -= 1;
69                true
70            }
71            _ => false,
72        }
73    }
74
75    pub fn get(&self, handle: PoolHandle) -> Option<&T> {
76        if handle.index >= self.capacity {
77            return None;
78        }
79        self.slots[handle.index]
80            .as_ref()
81            .filter(|s| s.generation == handle.generation)
82            .map(|s| &s.value)
83    }
84
85    pub fn get_mut(&mut self, handle: PoolHandle) -> Option<&mut T> {
86        if handle.index >= self.capacity {
87            return None;
88        }
89        self.slots[handle.index]
90            .as_mut()
91            .filter(|s| s.generation == handle.generation)
92            .map(|s| &mut s.value)
93    }
94
95    pub fn is_valid(&self, handle: PoolHandle) -> bool {
96        handle.index < self.capacity
97            && self.slots[handle.index]
98                .as_ref()
99                .is_some_and(|s| s.generation == handle.generation)
100    }
101
102    pub fn live_count(&self) -> usize {
103        self.live_count
104    }
105
106    pub fn capacity(&self) -> usize {
107        self.capacity
108    }
109
110    pub fn free_count(&self) -> usize {
111        self.free.len()
112    }
113
114    pub fn reset(&mut self) {
115        for slot in &mut self.slots {
116            *slot = None;
117        }
118        self.free.clear();
119        for i in (0..self.capacity).rev() {
120            self.free.push(i);
121        }
122        self.live_count = 0;
123    }
124
125    pub fn iter(&self) -> impl Iterator<Item = (PoolHandle, &T)> {
126        self.slots.iter().enumerate().filter_map(|(i, slot)| {
127            slot.as_ref().map(|s| {
128                (
129                    PoolHandle {
130                        index: i,
131                        generation: s.generation,
132                    },
133                    &s.value,
134                )
135            })
136        })
137    }
138}
139
140pub fn new_pool<T: Clone>(capacity: usize) -> PoolAllocator<T> {
141    PoolAllocator::new(capacity)
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147
148    #[test]
149    fn alloc_and_get() {
150        let mut pool: PoolAllocator<i32> = new_pool(4);
151        let h = pool.alloc(42).expect("should succeed");
152        assert_eq!(*pool.get(h).expect("should succeed"), 42);
153    }
154
155    #[test]
156    fn free_invalidates_handle() {
157        let mut pool: PoolAllocator<i32> = new_pool(4);
158        let h = pool.alloc(10).expect("should succeed");
159        assert!(pool.free(h));
160        assert!(pool.get(h).is_none());
161    }
162
163    #[test]
164    fn generation_prevents_use_after_free() {
165        let mut pool: PoolAllocator<i32> = new_pool(4);
166        let h = pool.alloc(1).expect("should succeed");
167        pool.free(h);
168        let h2 = pool.alloc(2).expect("should succeed");
169        assert_eq!(h2.index, h.index);
170        assert!(pool.get(h).is_none());
171        assert_eq!(*pool.get(h2).expect("should succeed"), 2);
172    }
173
174    #[test]
175    fn live_count_tracking() {
176        let mut pool: PoolAllocator<u8> = new_pool(8);
177        let h1 = pool.alloc(1).expect("should succeed");
178        let h2 = pool.alloc(2).expect("should succeed");
179        assert_eq!(pool.live_count(), 2);
180        pool.free(h1);
181        assert_eq!(pool.live_count(), 1);
182        pool.free(h2);
183        assert_eq!(pool.live_count(), 0);
184    }
185
186    #[test]
187    fn capacity_exhausted() {
188        let mut pool: PoolAllocator<u8> = new_pool(2);
189        pool.alloc(1).expect("should succeed");
190        pool.alloc(2).expect("should succeed");
191        assert!(pool.alloc(3).is_none());
192    }
193
194    #[test]
195    fn reset_restores_capacity() {
196        let mut pool: PoolAllocator<u8> = new_pool(2);
197        pool.alloc(1).expect("should succeed");
198        pool.alloc(2).expect("should succeed");
199        pool.reset();
200        assert_eq!(pool.free_count(), 2);
201        assert!(pool.alloc(3).is_some());
202    }
203
204    #[test]
205    fn get_mut_modifies_value() {
206        let mut pool: PoolAllocator<i32> = new_pool(4);
207        let h = pool.alloc(10).expect("should succeed");
208        *pool.get_mut(h).expect("should succeed") = 99;
209        assert_eq!(*pool.get(h).expect("should succeed"), 99);
210    }
211
212    #[test]
213    fn iter_live_items() {
214        let mut pool: PoolAllocator<i32> = new_pool(4);
215        let h1 = pool.alloc(1).expect("should succeed");
216        let _h2 = pool.alloc(2).expect("should succeed");
217        pool.free(h1);
218        let vals: Vec<i32> = pool.iter().map(|(_, v)| *v).collect();
219        assert_eq!(vals, vec![2]);
220    }
221
222    #[test]
223    fn is_valid_check() {
224        let mut pool: PoolAllocator<i32> = new_pool(4);
225        let h = pool.alloc(5).expect("should succeed");
226        assert!(pool.is_valid(h));
227        pool.free(h);
228        assert!(!pool.is_valid(h));
229    }
230}