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    /// Index of the taken move (if any). Only one move can be taken per step.
55    taken: Option<usize>,
56}
57
58impl<M> MoveArena<M> {
59    /// Creates a new empty arena.
60    #[inline]
61    pub fn new() -> Self {
62        Self {
63            storage: Vec::new(),
64            len: 0,
65            taken: None,
66        }
67    }
68
69    /// Creates a new arena with pre-allocated capacity.
70    #[inline]
71    pub fn with_capacity(capacity: usize) -> Self {
72        Self {
73            storage: Vec::with_capacity(capacity),
74            len: 0,
75            taken: None,
76        }
77    }
78
79    /// Resets the arena, making it empty.
80    ///
81    /// Drops all moves except the one that was taken (if any).
82    #[inline]
83    pub fn reset(&mut self) {
84        // Drop existing moves except the taken one (already moved out)
85        for i in 0..self.len {
86            if self.taken != Some(i) {
87                unsafe {
88                    ptr::drop_in_place(self.storage[i].as_mut_ptr());
89                }
90            }
91        }
92        self.len = 0;
93        self.taken = None;
94    }
95
96    /// Adds a move to the arena.
97    #[inline]
98    pub fn push(&mut self, m: M) {
99        if self.len < self.storage.len() {
100            // Reuse existing slot
101            self.storage[self.len] = MaybeUninit::new(m);
102        } else {
103            // Need to grow
104            self.storage.push(MaybeUninit::new(m));
105        }
106        self.len += 1;
107    }
108
109    /// Returns the number of moves in the arena.
110    #[inline]
111    pub fn len(&self) -> usize {
112        self.len
113    }
114
115    /// Returns true if the arena is empty.
116    #[inline]
117    pub fn is_empty(&self) -> bool {
118        self.len == 0
119    }
120
121    /// Returns an iterator over the moves.
122    #[inline]
123    pub fn iter(&self) -> impl Iterator<Item = &M> {
124        self.storage[..self.len]
125            .iter()
126            .map(|slot| unsafe { slot.assume_init_ref() })
127    }
128
129    /// Returns a mutable iterator over the moves.
130    #[inline]
131    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut M> {
132        self.storage[..self.len]
133            .iter_mut()
134            .map(|slot| unsafe { slot.assume_init_mut() })
135    }
136
137    /// Gets a move by index.
138    #[inline]
139    pub fn get(&self, index: usize) -> Option<&M> {
140        if index < self.len {
141            Some(unsafe { self.storage[index].assume_init_ref() })
142        } else {
143            None
144        }
145    }
146
147    /// Takes ownership of a move by index.
148    ///
149    /// Only one move can be taken per step. Call `reset()` before taking another.
150    ///
151    /// # Panics
152    ///
153    /// Panics if `index >= len` or if a move was already taken.
154    ///
155    /// # Example
156    ///
157    /// ```
158    /// use solverforge_solver::heuristic::r#move::MoveArena;
159    ///
160    /// let mut arena: MoveArena<String> = MoveArena::new();
161    /// arena.push("first".to_string());
162    /// arena.push("second".to_string());
163    ///
164    /// // Take ownership of the move at index 1
165    /// let taken = arena.take(1);
166    /// assert_eq!(taken, "second");
167    ///
168    /// // Reset before next step
169    /// arena.reset();
170    /// ```
171    #[inline]
172    pub fn take(&mut self, index: usize) -> M {
173        assert!(index < self.len, "index out of bounds");
174        assert!(self.taken.is_none(), "move already taken this step");
175        self.taken = Some(index);
176        unsafe { self.storage[index].assume_init_read() }
177    }
178
179    /// Extends the arena from an iterator.
180    #[inline]
181    pub fn extend<I: IntoIterator<Item = M>>(&mut self, iter: I) {
182        for m in iter {
183            self.push(m);
184        }
185    }
186
187    /// Returns the capacity of the arena.
188    #[inline]
189    pub fn capacity(&self) -> usize {
190        self.storage.capacity()
191    }
192}
193
194impl<M> Default for MoveArena<M> {
195    fn default() -> Self {
196        Self::new()
197    }
198}
199
200impl<M: Debug> Debug for MoveArena<M> {
201    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
202        f.debug_struct("MoveArena")
203            .field("len", &self.len)
204            .field("capacity", &self.storage.capacity())
205            .finish()
206    }
207}
208
209impl<M> Drop for MoveArena<M> {
210    fn drop(&mut self) {
211        // Drop all initialized moves except taken one
212        for i in 0..self.len {
213            if self.taken != Some(i) {
214                unsafe {
215                    ptr::drop_in_place(self.storage[i].as_mut_ptr());
216                }
217            }
218        }
219    }
220}
221
222#[cfg(test)]
223mod tests {
224    use super::*;
225
226    #[test]
227    fn test_arena_basic() {
228        let mut arena: MoveArena<i32> = MoveArena::new();
229        assert!(arena.is_empty());
230
231        arena.push(1);
232        arena.push(2);
233        arena.push(3);
234
235        assert_eq!(arena.len(), 3);
236        assert_eq!(arena.get(0), Some(&1));
237        assert_eq!(arena.get(1), Some(&2));
238        assert_eq!(arena.get(2), Some(&3));
239        assert_eq!(arena.get(3), None);
240    }
241
242    #[test]
243    fn test_arena_reset() {
244        let mut arena: MoveArena<i32> = MoveArena::new();
245        arena.push(1);
246        arena.push(2);
247        arena.push(3);
248
249        let capacity_before = arena.capacity();
250
251        arena.reset();
252
253        assert!(arena.is_empty());
254        assert_eq!(arena.len(), 0);
255        // Capacity is preserved
256        assert_eq!(arena.capacity(), capacity_before);
257    }
258
259    #[test]
260    fn test_arena_reuse_after_reset() {
261        let mut arena: MoveArena<i32> = MoveArena::new();
262
263        // First step
264        arena.push(1);
265        arena.push(2);
266        assert_eq!(arena.len(), 2);
267
268        arena.reset();
269
270        // Second step - reuses storage
271        arena.push(10);
272        arena.push(20);
273        arena.push(30);
274        assert_eq!(arena.len(), 3);
275        assert_eq!(arena.get(0), Some(&10));
276        assert_eq!(arena.get(1), Some(&20));
277        assert_eq!(arena.get(2), Some(&30));
278    }
279
280    #[test]
281    fn test_arena_iter() {
282        let mut arena: MoveArena<i32> = MoveArena::new();
283        arena.push(1);
284        arena.push(2);
285        arena.push(3);
286
287        let collected: Vec<_> = arena.iter().copied().collect();
288        assert_eq!(collected, vec![1, 2, 3]);
289    }
290
291    #[test]
292    fn test_arena_extend() {
293        let mut arena: MoveArena<i32> = MoveArena::new();
294        arena.extend(vec![1, 2, 3]);
295        assert_eq!(arena.len(), 3);
296
297        let collected: Vec<_> = arena.iter().copied().collect();
298        assert_eq!(collected, vec![1, 2, 3]);
299    }
300
301    #[test]
302    fn test_arena_with_capacity() {
303        let arena: MoveArena<i32> = MoveArena::with_capacity(100);
304        assert!(arena.is_empty());
305        assert!(arena.capacity() >= 100);
306    }
307
308    #[test]
309    fn test_arena_take() {
310        let mut arena: MoveArena<String> = MoveArena::new();
311        arena.push("a".to_string());
312        arena.push("b".to_string());
313        arena.push("c".to_string());
314
315        let taken = arena.take(1);
316        assert_eq!(taken, "b");
317
318        // Reset clears everything including taken tracking
319        arena.reset();
320        assert!(arena.is_empty());
321
322        // Can take again after reset
323        arena.push("x".to_string());
324        let taken = arena.take(0);
325        assert_eq!(taken, "x");
326    }
327
328    #[test]
329    #[should_panic(expected = "move already taken")]
330    fn test_arena_double_take_panics() {
331        let mut arena: MoveArena<i32> = MoveArena::new();
332        arena.push(1);
333        arena.push(2);
334        arena.take(0);
335        arena.take(1); // Should panic
336    }
337}