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}