Skip to main content

solverforge_solver/phase/localsearch/
cursor_source.rs

1//! One phase-facing source for move cursors.
2//!
3//! Ordinary selectors use their existing cursor kernel through the one
4//! `SelectorCursorSource` adapter below. Configured selector compositions
5//! instead own their persistent state tree and implement this trait directly.
6//! Local-search and VND therefore have one cursor-opening operation and no
7//! selector-specific execution branch.
8
9use solverforge_core::domain::PlanningSolution;
10use solverforge_scoring::Director;
11
12use crate::heuristic::r#move::Move;
13use crate::heuristic::selector::move_selector::{
14    MoveSelector, MoveStreamContext, ResourceMoveCursor, UnitResourceCursor,
15};
16
17/// Opens the next phase cursor from mutable solve-owned execution state.
18///
19/// Implementations must return a cursor that owns all candidate work. A
20/// source may lend state only while opening that cursor and must regain it
21/// when the cursor drops, which lets pause/resume and subsequent steps retain
22/// the exact seeded stream progression.
23pub trait MoveCursorSource<S, M>
24where
25    S: PlanningSolution,
26    M: Move<S>,
27{
28    /// Mutable solve-owned data lent at cursor-open and reachable-pull
29    /// boundaries. Ordinary stock selectors use `()`. Compiled runtime
30    /// sources receive the runner's one provider registry/reason arena here,
31    /// so VND can retain many neighborhood states without cloning or storing
32    /// a mutable resource in any one of them.
33    type Resources: Send;
34
35    /// The cursor has no mutable resource field. The shared phase loop lends
36    /// `Resources` only for each candidate pull through
37    /// [`ResourceMoveCursor::next_candidate_with_resources`].
38    type Cursor<'a>: ResourceMoveCursor<S, M, Self::Resources>
39    where
40        Self: 'a;
41
42    fn open_cursor<'a, D: Director<S>>(
43        &'a mut self,
44        resources: &mut Self::Resources,
45        score_director: &D,
46        context: MoveStreamContext,
47    ) -> Self::Cursor<'a>;
48}
49
50/// Type-level adapter for ordinary selectors and their one existing cursor
51/// kernel.
52///
53/// This is not a fallback or a second execution path: it delegates directly
54/// to `MoveSelector::open_cursor_with_context`. Stateful compiled
55/// compositions use their explicit execution owners instead, which avoids
56/// coherence overlap while preserving one phase loop.
57#[doc(hidden)]
58pub struct SelectorCursorSource<MS> {
59    selector: MS,
60}
61
62impl<MS> SelectorCursorSource<MS> {
63    pub(crate) fn new(selector: MS) -> Self {
64        Self { selector }
65    }
66}
67
68impl<MS: std::fmt::Debug> std::fmt::Debug for SelectorCursorSource<MS> {
69    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70        f.debug_tuple("SelectorCursorSource")
71            .field(&self.selector)
72            .finish()
73    }
74}
75
76impl<S, M, MS> MoveCursorSource<S, M> for SelectorCursorSource<MS>
77where
78    S: PlanningSolution,
79    M: Move<S>,
80    MS: MoveSelector<S, M>,
81{
82    type Resources = ();
83
84    type Cursor<'a>
85        = UnitResourceCursor<MS::Cursor<'a>>
86    where
87        Self: 'a;
88
89    fn open_cursor<'a, D: Director<S>>(
90        &'a mut self,
91        _resources: &mut Self::Resources,
92        score_director: &D,
93        context: MoveStreamContext,
94    ) -> Self::Cursor<'a> {
95        UnitResourceCursor::new(
96            self.selector
97                .open_cursor_with_context(score_director, context),
98        )
99    }
100}