solverforge-solver 0.21.0

Solver engine for SolverForge
Documentation
/* Mimic selectors for synchronized selection across multiple selectors.

Mimic selectors enable multiple selectors to select the same element in lockstep.
This is essential for:
- Nearby selection: Get the "origin" entity that was already selected
- Coordinated moves: Ensure multiple parts of a move reference the same entity

# Architecture

- [`MimicRecordingEntitySelector`]: Wraps a child selector and records each selected entity
- [`MimicReplayingEntitySelector`]: Replays the entity recorded by a recording selector
*/

use std::fmt::Debug;
use std::sync::{Arc, Mutex};

use solverforge_core::domain::PlanningSolution;
use solverforge_scoring::Director;

use super::entity::{EntityReference, EntitySelector};

/* Shared state between recording and replaying selectors.

A plain owned value behind one `Arc<Mutex<..>>`: the recording selector
writes, the replaying selector reads, and the state is freed when the last
handle drops. No leaked allocations, no manual refcounts, no unsafe impls.
The lock is uncontended in the sequential record-then-replay flow.
*/
#[derive(Debug, Default, Clone, Copy)]
struct MimicState {
    // Whether hasNext has been called on the recorder.
    has_next_recorded: bool,
    // The result of the last hasNext call.
    has_next: bool,
    // Whether next has been called on the recorder.
    next_recorded: bool,
    // The last recorded entity reference.
    recorded_entity: Option<EntityReference>,
}

/// Handle for sharing mimic state between recording and replaying selectors.
#[derive(Clone)]
pub struct MimicRecorder {
    shared: Arc<Mutex<MimicState>>,
    // Identifier for debugging.
    id: String,
}

impl MimicRecorder {
    pub fn new(id: impl Into<String>) -> Self {
        Self {
            shared: Arc::new(Mutex::new(MimicState::default())),
            id: id.into(),
        }
    }

    // Records a has_next result.
    fn record_has_next(&self, has_next: bool) {
        *self.shared.lock().unwrap() = MimicState {
            has_next_recorded: true,
            has_next,
            next_recorded: false,
            recorded_entity: None,
        };
    }

    // Records a next result.
    fn record_next(&self, entity: EntityReference) {
        *self.shared.lock().unwrap() = MimicState {
            has_next_recorded: true,
            has_next: true,
            next_recorded: true,
            recorded_entity: Some(entity),
        };
    }

    pub fn get_has_next(&self) -> Option<bool> {
        let state = self.shared.lock().unwrap();
        if state.has_next_recorded {
            Some(state.has_next)
        } else {
            None
        }
    }

    pub fn get_recorded_entity(&self) -> Option<EntityReference> {
        let state = self.shared.lock().unwrap();
        if state.next_recorded {
            state.recorded_entity
        } else {
            None
        }
    }

    pub fn id(&self) -> &str {
        &self.id
    }

    /// Resets the state for a new iteration.
    pub fn reset(&self) {
        *self.shared.lock().unwrap() = MimicState::default();
    }
}

impl Debug for MimicRecorder {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("MimicRecorder")
            .field("id", &self.id)
            .field("state", &*self.shared.lock().unwrap())
            .finish()
    }
}

/// An entity selector that records each selected entity for replay by other selectors.
///
/// This is used to synchronize selection across multiple selectors. The recording
/// selector wraps a child selector and broadcasts each selected entity to all
/// replaying selectors that share the same recorder.
///
/// # Zero-Erasure Design
///
/// The child entity selector `ES` is stored as a concrete generic type parameter,
/// eliminating virtual dispatch overhead when iterating over entities.
pub struct MimicRecordingEntitySelector<S, ES> {
    // The child selector that actually selects entities (zero-erasure).
    child: ES,
    // The recorder that broadcasts selections.
    recorder: MimicRecorder,
    // Marker for solution type.
    _phantom: std::marker::PhantomData<fn() -> S>,
}

impl<S, ES> MimicRecordingEntitySelector<S, ES> {
    pub fn new(child: ES, recorder: MimicRecorder) -> Self {
        Self {
            child,
            recorder,
            _phantom: std::marker::PhantomData,
        }
    }

    pub fn recorder(&self) -> MimicRecorder {
        self.recorder.clone()
    }
}

impl<S: PlanningSolution, ES: Debug> Debug for MimicRecordingEntitySelector<S, ES> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("MimicRecordingEntitySelector")
            .field("child", &self.child)
            .field("recorder_id", &self.recorder.id)
            .finish()
    }
}

impl<S, ES> EntitySelector<S> for MimicRecordingEntitySelector<S, ES>
where
    S: PlanningSolution,
    ES: EntitySelector<S>,
{
    fn iter<'a, D: Director<S>>(
        &'a self,
        score_director: &D,
    ) -> impl Iterator<Item = EntityReference> + 'a {
        // Reset for new iteration
        self.recorder.reset();

        let child_iter = self.child.iter(score_director);
        RecordingIterator {
            inner: child_iter,
            recorder: &self.recorder,
        }
    }

    fn size<D: Director<S>>(&self, score_director: &D) -> usize {
        self.child.size(score_director)
    }

    fn is_never_ending(&self) -> bool {
        self.child.is_never_ending()
    }
}

// Iterator that records each entity as it's yielded.
struct RecordingIterator<'a, I> {
    inner: I,
    recorder: &'a MimicRecorder,
}

impl<'a, I: Iterator<Item = EntityReference>> Iterator for RecordingIterator<'a, I> {
    type Item = EntityReference;

    fn next(&mut self) -> Option<Self::Item> {
        let next = self.inner.next();
        match next {
            Some(entity) => {
                self.recorder.record_next(entity);
                Some(entity)
            }
            None => {
                self.recorder.record_has_next(false);
                None
            }
        }
    }
}

/// An entity selector that replays the last entity recorded by a recording selector.
///
/// This selector always yields exactly one entity (the last one recorded) or no entities
/// if the recording selector hasn't recorded anything yet.
pub struct MimicReplayingEntitySelector {
    // The recorder to replay from.
    recorder: MimicRecorder,
}

impl MimicReplayingEntitySelector {
    pub fn new(recorder: MimicRecorder) -> Self {
        Self { recorder }
    }
}

impl Debug for MimicReplayingEntitySelector {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("MimicReplayingEntitySelector")
            .field("recorder_id", &self.recorder.id)
            .finish()
    }
}

impl<S: PlanningSolution> EntitySelector<S> for MimicReplayingEntitySelector {
    fn iter<'a, D: Director<S>>(
        &'a self,
        _score_director: &D,
    ) -> impl Iterator<Item = EntityReference> + 'a {
        ReplayingIterator {
            recorder: &self.recorder,
            returned: false,
        }
    }

    fn size<D: Director<S>>(&self, _score_director: &D) -> usize {
        // At most one entity is returned
        if self.recorder.get_recorded_entity().is_some() {
            1
        } else {
            0
        }
    }

    fn is_never_ending(&self) -> bool {
        false
    }
}

// Iterator that replays a single recorded entity.
struct ReplayingIterator<'a> {
    recorder: &'a MimicRecorder,
    returned: bool,
}

impl<'a> Iterator for ReplayingIterator<'a> {
    type Item = EntityReference;

    fn next(&mut self) -> Option<Self::Item> {
        if self.returned {
            return None;
        }

        // Check if something was recorded
        match self.recorder.get_recorded_entity() {
            Some(entity) => {
                self.returned = true;
                Some(entity)
            }
            None => {
                // Check has_next to provide better error handling
                match self.recorder.get_has_next() {
                    Some(false) => None, // Recording selector exhausted
                    Some(true) => panic!(
                        "MimicReplayingEntitySelector: Recording selector's hasNext() was true \
                         but next() was never called. Ensure the recording selector's iterator \
                         is advanced before using the replaying selector."
                    ),
                    None => panic!(
                        "MimicReplayingEntitySelector: No recording found. \
                         The recording selector must be iterated before the replaying selector."
                    ),
                }
            }
        }
    }
}