solverforge_solver/heuristic/selector/
mimic.rs

1//! Mimic selectors for synchronized selection across multiple selectors.
2//!
3//! Mimic selectors enable multiple selectors to select the same element in lockstep.
4//! This is essential for:
5//! - Nearby selection: Get the "origin" entity that was already selected
6//! - Coordinated moves: Ensure multiple parts of a move reference the same entity
7//!
8//! # Architecture
9//!
10//! - [`MimicRecordingEntitySelector`]: Wraps a child selector and records each selected entity
11//! - [`MimicReplayingEntitySelector`]: Replays the entity recorded by a recording selector
12
13use std::fmt::Debug;
14use std::sync::{Arc, RwLock};
15
16use solverforge_core::domain::PlanningSolution;
17use solverforge_scoring::ScoreDirector;
18
19use super::entity::{EntityReference, EntitySelector};
20
21/// Shared state between recording and replaying selectors.
22#[derive(Debug, Default)]
23struct MimicState {
24    /// Whether hasNext has been called on the recorder.
25    has_next_recorded: bool,
26    /// The result of the last hasNext call.
27    has_next: bool,
28    /// Whether next has been called on the recorder.
29    next_recorded: bool,
30    /// The last recorded entity reference.
31    recorded_entity: Option<EntityReference>,
32}
33
34/// Handle for sharing mimic state between recording and replaying selectors.
35#[derive(Debug, Clone)]
36pub struct MimicRecorder {
37    state: Arc<RwLock<MimicState>>,
38    /// Identifier for debugging.
39    id: String,
40}
41
42impl MimicRecorder {
43    /// Creates a new mimic recorder with the given identifier.
44    pub fn new(id: impl Into<String>) -> Self {
45        Self {
46            state: Arc::new(RwLock::new(MimicState::default())),
47            id: id.into(),
48        }
49    }
50
51    /// Records a has_next result.
52    fn record_has_next(&self, has_next: bool) {
53        let mut state = self.state.write().unwrap();
54        state.has_next_recorded = true;
55        state.has_next = has_next;
56        state.next_recorded = false;
57        state.recorded_entity = None;
58    }
59
60    /// Records a next result.
61    fn record_next(&self, entity: EntityReference) {
62        let mut state = self.state.write().unwrap();
63        state.has_next_recorded = true;
64        state.has_next = true;
65        state.next_recorded = true;
66        state.recorded_entity = Some(entity);
67    }
68
69    /// Gets the recorded has_next state.
70    pub fn get_has_next(&self) -> Option<bool> {
71        let state = self.state.read().unwrap();
72        if state.has_next_recorded {
73            Some(state.has_next)
74        } else {
75            None
76        }
77    }
78
79    /// Gets the recorded entity.
80    pub fn get_recorded_entity(&self) -> Option<EntityReference> {
81        let state = self.state.read().unwrap();
82        if state.next_recorded {
83            state.recorded_entity
84        } else {
85            None
86        }
87    }
88
89    /// Returns the ID of this recorder.
90    pub fn id(&self) -> &str {
91        &self.id
92    }
93
94    /// Resets the state for a new iteration.
95    pub fn reset(&self) {
96        *self.state.write().unwrap() = MimicState::default();
97    }
98}
99
100/// An entity selector that records each selected entity for replay by other selectors.
101///
102/// This is used to synchronize selection across multiple selectors. The recording
103/// selector wraps a child selector and broadcasts each selected entity to all
104/// replaying selectors that share the same recorder.
105///
106/// # Zero-Erasure Design
107///
108/// The child entity selector `ES` is stored as a concrete generic type parameter,
109/// eliminating virtual dispatch overhead when iterating over entities.
110pub struct MimicRecordingEntitySelector<S, ES> {
111    /// The child selector that actually selects entities (zero-erasure).
112    child: ES,
113    /// The recorder that broadcasts selections.
114    recorder: MimicRecorder,
115    /// Marker for solution type.
116    _phantom: std::marker::PhantomData<fn() -> S>,
117}
118
119impl<S, ES> MimicRecordingEntitySelector<S, ES> {
120    /// Creates a new recording selector wrapping the given child selector.
121    pub fn new(child: ES, recorder: MimicRecorder) -> Self {
122        Self {
123            child,
124            recorder,
125            _phantom: std::marker::PhantomData,
126        }
127    }
128
129    /// Returns the recorder for creating replaying selectors.
130    pub fn recorder(&self) -> MimicRecorder {
131        self.recorder.clone()
132    }
133}
134
135impl<S: PlanningSolution, ES: Debug> Debug for MimicRecordingEntitySelector<S, ES> {
136    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
137        f.debug_struct("MimicRecordingEntitySelector")
138            .field("child", &self.child)
139            .field("recorder_id", &self.recorder.id)
140            .finish()
141    }
142}
143
144impl<S, ES> EntitySelector<S> for MimicRecordingEntitySelector<S, ES>
145where
146    S: PlanningSolution,
147    ES: EntitySelector<S>,
148{
149    fn iter<'a, D: ScoreDirector<S>>(
150        &'a self,
151        score_director: &'a D,
152    ) -> Box<dyn Iterator<Item = EntityReference> + 'a> {
153        // Reset for new iteration
154        self.recorder.reset();
155
156        let child_iter = self.child.iter(score_director);
157        Box::new(RecordingIterator {
158            inner: child_iter,
159            recorder: &self.recorder,
160        })
161    }
162
163    fn size<D: ScoreDirector<S>>(&self, score_director: &D) -> usize {
164        self.child.size(score_director)
165    }
166
167    fn is_never_ending(&self) -> bool {
168        self.child.is_never_ending()
169    }
170}
171
172/// Iterator that records each entity as it's yielded.
173struct RecordingIterator<'a> {
174    inner: Box<dyn Iterator<Item = EntityReference> + 'a>,
175    recorder: &'a MimicRecorder,
176}
177
178impl<'a> Iterator for RecordingIterator<'a> {
179    type Item = EntityReference;
180
181    fn next(&mut self) -> Option<Self::Item> {
182        let next = self.inner.next();
183        match next {
184            Some(entity) => {
185                self.recorder.record_next(entity);
186                Some(entity)
187            }
188            None => {
189                self.recorder.record_has_next(false);
190                None
191            }
192        }
193    }
194}
195
196/// An entity selector that replays the last entity recorded by a recording selector.
197///
198/// This selector always yields exactly one entity (the last one recorded) or no entities
199/// if the recording selector hasn't recorded anything yet.
200pub struct MimicReplayingEntitySelector {
201    /// The recorder to replay from.
202    recorder: MimicRecorder,
203}
204
205impl MimicReplayingEntitySelector {
206    /// Creates a new replaying selector that replays from the given recorder.
207    pub fn new(recorder: MimicRecorder) -> Self {
208        Self { recorder }
209    }
210}
211
212impl Debug for MimicReplayingEntitySelector {
213    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
214        f.debug_struct("MimicReplayingEntitySelector")
215            .field("recorder_id", &self.recorder.id)
216            .finish()
217    }
218}
219
220impl<S: PlanningSolution> EntitySelector<S> for MimicReplayingEntitySelector {
221    fn iter<'a, D: ScoreDirector<S>>(
222        &'a self,
223        _score_director: &'a D,
224    ) -> Box<dyn Iterator<Item = EntityReference> + 'a> {
225        Box::new(ReplayingIterator {
226            recorder: &self.recorder,
227            returned: false,
228        })
229    }
230
231    fn size<D: ScoreDirector<S>>(&self, _score_director: &D) -> usize {
232        // At most one entity is returned
233        if self.recorder.get_recorded_entity().is_some() {
234            1
235        } else {
236            0
237        }
238    }
239
240    fn is_never_ending(&self) -> bool {
241        false
242    }
243}
244
245/// Iterator that replays a single recorded entity.
246struct ReplayingIterator<'a> {
247    recorder: &'a MimicRecorder,
248    returned: bool,
249}
250
251impl<'a> Iterator for ReplayingIterator<'a> {
252    type Item = EntityReference;
253
254    fn next(&mut self) -> Option<Self::Item> {
255        if self.returned {
256            return None;
257        }
258
259        // Check if something was recorded
260        match self.recorder.get_recorded_entity() {
261            Some(entity) => {
262                self.returned = true;
263                Some(entity)
264            }
265            None => {
266                // Check has_next to provide better error handling
267                match self.recorder.get_has_next() {
268                    Some(false) => None, // Recording selector exhausted
269                    Some(true) => panic!(
270                        "MimicReplayingEntitySelector: Recording selector's hasNext() was true \
271                         but next() was never called. Ensure the recording selector's iterator \
272                         is advanced before using the replaying selector."
273                    ),
274                    None => panic!(
275                        "MimicReplayingEntitySelector: No recording found. \
276                         The recording selector must be iterated before the replaying selector."
277                    ),
278                }
279            }
280        }
281    }
282}
283
284#[cfg(test)]
285mod tests {
286    use super::*;
287    use crate::heuristic::selector::entity::FromSolutionEntitySelector;
288    use solverforge_core::domain::{EntityDescriptor, SolutionDescriptor, TypedEntityExtractor};
289    use solverforge_core::score::SimpleScore;
290    use solverforge_scoring::SimpleScoreDirector;
291    use std::any::TypeId;
292
293    #[derive(Clone, Debug)]
294    struct Queen {
295        id: i64,
296    }
297
298    #[derive(Clone, Debug)]
299    struct NQueensSolution {
300        queens: Vec<Queen>,
301        score: Option<SimpleScore>,
302    }
303
304    impl PlanningSolution for NQueensSolution {
305        type Score = SimpleScore;
306
307        fn score(&self) -> Option<Self::Score> {
308            self.score
309        }
310
311        fn set_score(&mut self, score: Option<Self::Score>) {
312            self.score = score;
313        }
314    }
315
316    fn get_queens(s: &NQueensSolution) -> &Vec<Queen> {
317        &s.queens
318    }
319
320    fn get_queens_mut(s: &mut NQueensSolution) -> &mut Vec<Queen> {
321        &mut s.queens
322    }
323
324    fn create_test_director(
325        n: usize,
326    ) -> SimpleScoreDirector<NQueensSolution, impl Fn(&NQueensSolution) -> SimpleScore> {
327        let queens: Vec<_> = (0..n).map(|i| Queen { id: i as i64 }).collect();
328
329        let solution = NQueensSolution {
330            queens,
331            score: None,
332        };
333
334        let extractor = Box::new(TypedEntityExtractor::new(
335            "Queen",
336            "queens",
337            get_queens,
338            get_queens_mut,
339        ));
340        let entity_desc = EntityDescriptor::new("Queen", TypeId::of::<Queen>(), "queens")
341            .with_extractor(extractor);
342
343        let descriptor =
344            SolutionDescriptor::new("NQueensSolution", TypeId::of::<NQueensSolution>())
345                .with_entity(entity_desc);
346
347        SimpleScoreDirector::with_calculator(solution, descriptor, |_| SimpleScore::of(0))
348    }
349
350    #[test]
351    fn test_mimic_recording_selector() {
352        let director = create_test_director(3);
353
354        // Verify entity IDs
355        let solution = director.working_solution();
356        for (i, queen) in solution.queens.iter().enumerate() {
357            assert_eq!(queen.id, i as i64);
358        }
359
360        let recorder = MimicRecorder::new("test");
361        let child = FromSolutionEntitySelector::new(0);
362        let recording = MimicRecordingEntitySelector::new(child, recorder);
363
364        let entities: Vec<_> = recording.iter(&director).collect();
365        assert_eq!(entities.len(), 3);
366        assert_eq!(entities[0], EntityReference::new(0, 0));
367        assert_eq!(entities[1], EntityReference::new(0, 1));
368        assert_eq!(entities[2], EntityReference::new(0, 2));
369    }
370
371    #[test]
372    fn test_mimic_replaying_selector() {
373        let director = create_test_director(3);
374
375        let recorder = MimicRecorder::new("test");
376        let child = FromSolutionEntitySelector::new(0);
377        let recording = MimicRecordingEntitySelector::new(child, recorder.clone());
378        let replaying = MimicReplayingEntitySelector::new(recorder);
379
380        // Iterate through recording selector
381        let mut recording_iter = recording.iter(&director);
382
383        // First entity recorded
384        let first = recording_iter.next().unwrap();
385        assert_eq!(first, EntityReference::new(0, 0));
386
387        // Replaying should yield the same entity
388        let replayed: Vec<_> = replaying.iter(&director).collect();
389        assert_eq!(replayed.len(), 1);
390        assert_eq!(replayed[0], EntityReference::new(0, 0));
391
392        // Move to second entity
393        let second = recording_iter.next().unwrap();
394        assert_eq!(second, EntityReference::new(0, 1));
395
396        // Replaying should now yield the second entity
397        let replayed: Vec<_> = replaying.iter(&director).collect();
398        assert_eq!(replayed.len(), 1);
399        assert_eq!(replayed[0], EntityReference::new(0, 1));
400    }
401
402    #[test]
403    fn test_mimic_synchronized_iteration() {
404        let director = create_test_director(3);
405
406        let recorder = MimicRecorder::new("test");
407        let child = FromSolutionEntitySelector::new(0);
408        let recording = MimicRecordingEntitySelector::new(child, recorder.clone());
409        let replaying = MimicReplayingEntitySelector::new(recorder);
410
411        // Simulate how this would be used in a move selector:
412        // For each recorded entity, get the replayed entity
413        for recorded in recording.iter(&director) {
414            let replayed: Vec<_> = replaying.iter(&director).collect();
415            assert_eq!(replayed.len(), 1);
416            assert_eq!(replayed[0], recorded);
417        }
418    }
419
420    #[test]
421    fn test_mimic_empty_selector() {
422        let director = create_test_director(0);
423
424        let recorder = MimicRecorder::new("test");
425        let child = FromSolutionEntitySelector::new(0);
426        let recording = MimicRecordingEntitySelector::new(child, recorder.clone());
427        let replaying = MimicReplayingEntitySelector::new(recorder);
428
429        // Recording selector is empty
430        let entities: Vec<_> = recording.iter(&director).collect();
431        assert_eq!(entities.len(), 0);
432
433        // Replaying should also be empty
434        let replayed: Vec<_> = replaying.iter(&director).collect();
435        assert_eq!(replayed.len(), 0);
436    }
437}