solverforge_solver/heuristic/move/traits.rs
1// Move trait definition.
2
3use std::fmt::Debug;
4
5use solverforge_core::domain::PlanningSolution;
6use solverforge_scoring::Director;
7
8use super::MoveTabuSignature;
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq)]
11pub struct MoveAffectedEntity<'a> {
12 pub descriptor_index: usize,
13 pub entity_index: usize,
14 pub variable_name: &'a str,
15}
16
17/// A move that modifies one or more planning variables.
18///
19/// Moves are fully typed for maximum performance - no boxing, no virtual dispatch.
20/// Undo is handled by `RecordingDirector`, not by move return values.
21///
22/// # Type Parameters
23/// * `S` - The planning solution type
24///
25/// # Implementation Notes
26/// - Moves should be lightweight
27/// - Use `RecordingDirector` to wrap the score director for automatic undo
28/// - Moves are NEVER cloned - ownership transfers via arena indices
29/// - Methods are generic over D to allow use with both concrete directors and RecordingDirector
30pub trait Move<S: PlanningSolution>: Send + Sync + Debug {
31 /* Returns true if this move can be executed in the current state.
32
33 A move is not doable if:
34 - The source value equals the destination value (no change)
35 - Required entities are pinned
36 - The move would violate hard constraints that can be detected early
37 */
38 fn is_doable<D: Director<S>>(&self, score_director: &D) -> bool;
39
40 /* Executes this move, modifying the working solution.
41
42 This method modifies the planning variables through the score director.
43 Use `RecordingDirector` to enable automatic undo via `undo_changes()`.
44 */
45 fn do_move<D: Director<S>>(&self, score_director: &mut D);
46
47 fn descriptor_index(&self) -> usize;
48
49 fn entity_indices(&self) -> &[usize];
50
51 fn variable_name(&self) -> &str;
52
53 fn requires_hard_improvement(&self) -> bool {
54 false
55 }
56
57 fn tabu_signature<D: Director<S>>(&self, score_director: &D) -> MoveTabuSignature;
58
59 fn for_each_affected_entity(&self, visitor: &mut dyn FnMut(MoveAffectedEntity<'_>)) {
60 let descriptor_index = self.descriptor_index();
61 let variable_name = self.variable_name();
62 for &entity_index in self.entity_indices() {
63 visitor(MoveAffectedEntity {
64 descriptor_index,
65 entity_index,
66 variable_name,
67 });
68 }
69 }
70}