Skip to main content

solverforge_solver/heuristic/move/
arena.rs

1/* Arena allocator for moves.
2
3Provides O(1) step cleanup by reusing a pre-allocated buffer.
4Instead of allocating a new Vec each step, the arena is reset
5and reused.
6*/
7
8use std::fmt::Debug;
9use std::mem::MaybeUninit;
10use std::ptr;
11
12use rand::seq::SliceRandom;
13use rand::Rng;
14
15/// Arena allocator for moves with O(1) reset.
16///
17/// Instead of allocating a new Vec<M> each step and letting it drop,
18/// the arena maintains a reusable buffer. Calling `reset()` simply
19/// sets the length to 0 without running destructors (moves are Copy-like
20/// in practice since they contain only primitives and small inline data).
21///
22/// # Performance
23///
24/// | Operation | Vec per step | MoveArena |
25/// |-----------|--------------|-----------|
26/// | Alloc     | O(n) heap    | O(1) bump |
27/// | Cleanup   | O(n) drop    | O(1) reset|
28/// | Memory    | n * size_of  | Reused    |
29///
30/// # Example
31///
32/// ```
33/// use solverforge_solver::heuristic::r#move::MoveArena;
34///
35/// let mut arena: MoveArena<i32> = MoveArena::new();
36///
37/// // Step 1: generate and evaluate moves
38/// arena.push(1);
39/// arena.push(2);
40/// arena.push(3);
41/// assert_eq!(arena.len(), 3);
42///
43/// // Step 2: reset and reuse (O(1)!)
44/// arena.reset();
45/// assert!(arena.is_empty());
46///
47/// arena.push(10);
48/// arena.push(20);
49/// for mov in arena.iter() {
50///     assert!(*mov >= 10);
51/// }
52/// ```
53pub struct MoveArena<M> {
54    // Storage for moves. We use MaybeUninit to avoid requiring Default.
55    storage: Vec<MaybeUninit<M>>,
56    // Number of valid moves currently in the arena.
57    len: usize,
58    // Index of the taken move (if any). Only one move can be taken per step.
59    taken: Option<usize>,
60}
61
62impl<M> MoveArena<M> {
63    #[inline]
64    pub fn new() -> Self {
65        Self {
66            storage: Vec::new(),
67            len: 0,
68            taken: None,
69        }
70    }
71
72    #[inline]
73    pub fn with_capacity(capacity: usize) -> Self {
74        Self {
75            storage: Vec::with_capacity(capacity),
76            len: 0,
77            taken: None,
78        }
79    }
80
81    /* Resets the arena, making it empty.
82
83    Drops all moves except the one that was taken (if any).
84    */
85    #[inline]
86    pub fn reset(&mut self) {
87        // Drop existing moves except the taken one (already moved out)
88        for i in 0..self.len {
89            if self.taken != Some(i) {
90                unsafe {
91                    ptr::drop_in_place(self.storage[i].as_mut_ptr());
92                }
93            }
94        }
95        self.len = 0;
96        self.taken = None;
97    }
98
99    #[inline]
100    pub fn push(&mut self, m: M) {
101        if self.len < self.storage.len() {
102            // Reuse existing slot
103            self.storage[self.len] = MaybeUninit::new(m);
104        } else {
105            // Need to grow
106            self.storage.push(MaybeUninit::new(m));
107        }
108        self.len += 1;
109    }
110
111    #[inline]
112    pub fn len(&self) -> usize {
113        self.len
114    }
115
116    // Returns true if the arena is empty.
117    #[inline]
118    pub fn is_empty(&self) -> bool {
119        self.len == 0
120    }
121
122    // Returns an iterator over the moves.
123    #[inline]
124    pub fn iter(&self) -> impl Iterator<Item = &M> {
125        self.storage[..self.len]
126            .iter()
127            .map(|slot| unsafe { slot.assume_init_ref() })
128    }
129
130    // Returns a mutable iterator over the moves.
131    #[inline]
132    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut M> {
133        self.storage[..self.len]
134            .iter_mut()
135            .map(|slot| unsafe { slot.assume_init_mut() })
136    }
137
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    */
172    #[inline]
173    pub fn take(&mut self, index: usize) -> M {
174        assert!(index < self.len, "index out of bounds");
175        assert!(self.taken.is_none(), "move already taken this step");
176        self.taken = Some(index);
177        unsafe { self.storage[index].assume_init_read() }
178    }
179
180    // Extends the arena from an iterator.
181    #[inline]
182    pub fn extend<I: IntoIterator<Item = M>>(&mut self, iter: I) {
183        for m in iter {
184            self.push(m);
185        }
186    }
187
188    /* Shuffles the initialized moves in-place using Fisher-Yates.
189
190    This avoids re-generating and re-collecting all moves each step.
191    The arena keeps its existing storage and just randomises evaluation order.
192    */
193    #[inline]
194    pub fn shuffle<R: Rng>(&mut self, rng: &mut R) {
195        if self.len < 2 {
196            return;
197        }
198        // Fisher-Yates on the initialized portion of storage
199        let slice = &mut self.storage[..self.len];
200        slice.shuffle(rng);
201    }
202
203    #[inline]
204    pub fn capacity(&self) -> usize {
205        self.storage.capacity()
206    }
207}
208
209impl<M> Default for MoveArena<M> {
210    fn default() -> Self {
211        Self::new()
212    }
213}
214
215impl<M: Debug> Debug for MoveArena<M> {
216    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
217        f.debug_struct("MoveArena")
218            .field("len", &self.len)
219            .field("capacity", &self.storage.capacity())
220            .finish()
221    }
222}
223
224impl<M> Drop for MoveArena<M> {
225    fn drop(&mut self) {
226        // Drop all initialized moves except taken one
227        for i in 0..self.len {
228            if self.taken != Some(i) {
229                unsafe {
230                    ptr::drop_in_place(self.storage[i].as_mut_ptr());
231                }
232            }
233        }
234    }
235}