solverforge_solver/heuristic/move/
arena.rs

1//! Arena allocator for moves.
2//!
3//! Provides O(1) step cleanup by reusing a pre-allocated buffer.
4//! Instead of allocating a new Vec each step, the arena is reset
5//! and reused.
6
7use std::fmt::Debug;
8use std::mem::MaybeUninit;
9use std::ptr;
10
11/// Arena allocator for moves with O(1) reset.
12///
13/// Instead of allocating a new Vec<M> each step and letting it drop,
14/// the arena maintains a reusable buffer. Calling `reset()` simply
15/// sets the length to 0 without running destructors (moves are Copy-like
16/// in practice since they contain only primitives and small inline data).
17///
18/// # Performance
19///
20/// | Operation | Vec per step | MoveArena |
21/// |-----------|--------------|-----------|
22/// | Alloc     | O(n) heap    | O(1) bump |
23/// | Cleanup   | O(n) drop    | O(1) reset|
24/// | Memory    | n * size_of  | Reused    |
25///
26/// # Example
27///
28/// ```
29/// use solverforge_solver::heuristic::r#move::MoveArena;
30///
31/// let mut arena: MoveArena<i32> = MoveArena::new();
32///
33/// // Step 1: generate and evaluate moves
34/// arena.push(1);
35/// arena.push(2);
36/// arena.push(3);
37/// assert_eq!(arena.len(), 3);
38///
39/// // Step 2: reset and reuse (O(1)!)
40/// arena.reset();
41/// assert!(arena.is_empty());
42///
43/// arena.push(10);
44/// arena.push(20);
45/// for mov in arena.iter() {
46///     assert!(*mov >= 10);
47/// }
48/// ```
49pub struct MoveArena<M> {
50    /// Storage for moves. We use MaybeUninit to avoid requiring Default.
51    storage: Vec<MaybeUninit<M>>,
52    /// Number of valid moves currently in the arena.
53    len: usize,
54}
55
56impl<M> MoveArena<M> {
57    /// Creates a new empty arena.
58    #[inline]
59    pub fn new() -> Self {
60        Self {
61            storage: Vec::new(),
62            len: 0,
63        }
64    }
65
66    /// Creates a new arena with pre-allocated capacity.
67    #[inline]
68    pub fn with_capacity(capacity: usize) -> Self {
69        Self {
70            storage: Vec::with_capacity(capacity),
71            len: 0,
72        }
73    }
74
75    /// Resets the arena, making it empty.
76    ///
77    /// This is O(1) - it just sets len to 0.
78    /// Existing data is left in place and will be overwritten.
79    #[inline]
80    pub fn reset(&mut self) {
81        // Drop existing moves if M has drop semantics
82        // For most moves (ChangeMove, SwapMove), this is a no-op
83        // since they contain only primitives
84        for i in 0..self.len {
85            unsafe {
86                ptr::drop_in_place(self.storage[i].as_mut_ptr());
87            }
88        }
89        self.len = 0;
90    }
91
92    /// Adds a move to the arena.
93    #[inline]
94    pub fn push(&mut self, m: M) {
95        if self.len < self.storage.len() {
96            // Reuse existing slot
97            self.storage[self.len] = MaybeUninit::new(m);
98        } else {
99            // Need to grow
100            self.storage.push(MaybeUninit::new(m));
101        }
102        self.len += 1;
103    }
104
105    /// Returns the number of moves in the arena.
106    #[inline]
107    pub fn len(&self) -> usize {
108        self.len
109    }
110
111    /// Returns true if the arena is empty.
112    #[inline]
113    pub fn is_empty(&self) -> bool {
114        self.len == 0
115    }
116
117    /// Returns an iterator over the moves.
118    #[inline]
119    pub fn iter(&self) -> impl Iterator<Item = &M> {
120        self.storage[..self.len]
121            .iter()
122            .map(|slot| unsafe { slot.assume_init_ref() })
123    }
124
125    /// Returns a mutable iterator over the moves.
126    #[inline]
127    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut M> {
128        self.storage[..self.len]
129            .iter_mut()
130            .map(|slot| unsafe { slot.assume_init_mut() })
131    }
132
133    /// Gets a move by index.
134    #[inline]
135    pub fn get(&self, index: usize) -> Option<&M> {
136        if index < self.len {
137            Some(unsafe { self.storage[index].assume_init_ref() })
138        } else {
139            None
140        }
141    }
142
143    /// Extends the arena from an iterator.
144    #[inline]
145    pub fn extend<I: IntoIterator<Item = M>>(&mut self, iter: I) {
146        for m in iter {
147            self.push(m);
148        }
149    }
150
151    /// Returns the capacity of the arena.
152    #[inline]
153    pub fn capacity(&self) -> usize {
154        self.storage.capacity()
155    }
156}
157
158impl<M> Default for MoveArena<M> {
159    fn default() -> Self {
160        Self::new()
161    }
162}
163
164impl<M: Debug> Debug for MoveArena<M> {
165    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
166        f.debug_struct("MoveArena")
167            .field("len", &self.len)
168            .field("capacity", &self.storage.capacity())
169            .finish()
170    }
171}
172
173impl<M> Drop for MoveArena<M> {
174    fn drop(&mut self) {
175        // Drop all initialized moves
176        for i in 0..self.len {
177            unsafe {
178                ptr::drop_in_place(self.storage[i].as_mut_ptr());
179            }
180        }
181    }
182}
183
184#[cfg(test)]
185mod tests {
186    use super::*;
187
188    #[test]
189    fn test_arena_basic() {
190        let mut arena: MoveArena<i32> = MoveArena::new();
191        assert!(arena.is_empty());
192
193        arena.push(1);
194        arena.push(2);
195        arena.push(3);
196
197        assert_eq!(arena.len(), 3);
198        assert_eq!(arena.get(0), Some(&1));
199        assert_eq!(arena.get(1), Some(&2));
200        assert_eq!(arena.get(2), Some(&3));
201        assert_eq!(arena.get(3), None);
202    }
203
204    #[test]
205    fn test_arena_reset() {
206        let mut arena: MoveArena<i32> = MoveArena::new();
207        arena.push(1);
208        arena.push(2);
209        arena.push(3);
210
211        let capacity_before = arena.capacity();
212
213        arena.reset();
214
215        assert!(arena.is_empty());
216        assert_eq!(arena.len(), 0);
217        // Capacity is preserved
218        assert_eq!(arena.capacity(), capacity_before);
219    }
220
221    #[test]
222    fn test_arena_reuse_after_reset() {
223        let mut arena: MoveArena<i32> = MoveArena::new();
224
225        // First step
226        arena.push(1);
227        arena.push(2);
228        assert_eq!(arena.len(), 2);
229
230        arena.reset();
231
232        // Second step - reuses storage
233        arena.push(10);
234        arena.push(20);
235        arena.push(30);
236        assert_eq!(arena.len(), 3);
237        assert_eq!(arena.get(0), Some(&10));
238        assert_eq!(arena.get(1), Some(&20));
239        assert_eq!(arena.get(2), Some(&30));
240    }
241
242    #[test]
243    fn test_arena_iter() {
244        let mut arena: MoveArena<i32> = MoveArena::new();
245        arena.push(1);
246        arena.push(2);
247        arena.push(3);
248
249        let collected: Vec<_> = arena.iter().copied().collect();
250        assert_eq!(collected, vec![1, 2, 3]);
251    }
252
253    #[test]
254    fn test_arena_extend() {
255        let mut arena: MoveArena<i32> = MoveArena::new();
256        arena.extend(vec![1, 2, 3]);
257        assert_eq!(arena.len(), 3);
258
259        let collected: Vec<_> = arena.iter().copied().collect();
260        assert_eq!(collected, vec![1, 2, 3]);
261    }
262
263    #[test]
264    fn test_arena_with_capacity() {
265        let arena: MoveArena<i32> = MoveArena::with_capacity(100);
266        assert!(arena.is_empty());
267        assert!(arena.capacity() >= 100);
268    }
269}