Skip to main content

solverforge_solver/heuristic/move/
change.rs

1//! ChangeMove - assigns a value to a planning variable.
2//!
3//! This is the most fundamental move type. It takes a value and assigns
4//! it to a planning variable on an entity.
5//!
6//! # Zero-Erasure Design
7//!
8//! This move stores typed function pointers that operate directly on
9//! the solution. No `Arc<dyn>`, no `Box<dyn Any>`, no `downcast_ref`.
10
11use std::fmt::Debug;
12
13use solverforge_core::domain::PlanningSolution;
14use solverforge_scoring::ScoreDirector;
15
16use super::Move;
17
18/// A move that assigns a value to an entity's variable.
19///
20/// Stores typed function pointers for zero-erasure execution.
21/// No trait objects, no boxing - all operations are fully typed at compile time.
22///
23/// # Type Parameters
24/// * `S` - The planning solution type
25/// * `V` - The variable value type
26pub struct ChangeMove<S, V> {
27    entity_index: usize,
28    to_value: Option<V>,
29    getter: fn(&S, usize) -> Option<V>,
30    setter: fn(&mut S, usize, Option<V>),
31    variable_name: &'static str,
32    descriptor_index: usize,
33}
34
35impl<S, V: Clone> Clone for ChangeMove<S, V> {
36    fn clone(&self) -> Self {
37        Self {
38            entity_index: self.entity_index,
39            to_value: self.to_value.clone(),
40            getter: self.getter,
41            setter: self.setter,
42            variable_name: self.variable_name,
43            descriptor_index: self.descriptor_index,
44        }
45    }
46}
47
48impl<S, V: Copy> Copy for ChangeMove<S, V> {}
49
50impl<S, V: Debug> Debug for ChangeMove<S, V> {
51    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52        f.debug_struct("ChangeMove")
53            .field("entity_index", &self.entity_index)
54            .field("descriptor_index", &self.descriptor_index)
55            .field("variable_name", &self.variable_name)
56            .field("to_value", &self.to_value)
57            .finish()
58    }
59}
60
61impl<S, V> ChangeMove<S, V> {
62    /// Creates a new change move with typed function pointers.
63    ///
64    /// # Arguments
65    /// * `entity_index` - Index of the entity in its collection
66    /// * `to_value` - The value to assign (None to unassign)
67    /// * `getter` - Function pointer to get current value from solution
68    /// * `setter` - Function pointer to set value on solution
69    /// * `variable_name` - Name of the variable (for debugging)
70    /// * `descriptor_index` - Index of the entity descriptor
71    pub fn new(
72        entity_index: usize,
73        to_value: Option<V>,
74        getter: fn(&S, usize) -> Option<V>,
75        setter: fn(&mut S, usize, Option<V>),
76        variable_name: &'static str,
77        descriptor_index: usize,
78    ) -> Self {
79        Self {
80            entity_index,
81            to_value,
82            getter,
83            setter,
84            variable_name,
85            descriptor_index,
86        }
87    }
88
89    /// Returns the entity index.
90    pub fn entity_index(&self) -> usize {
91        self.entity_index
92    }
93
94    /// Returns the target value.
95    pub fn to_value(&self) -> Option<&V> {
96        self.to_value.as_ref()
97    }
98
99    /// Returns the getter function pointer.
100    pub fn getter(&self) -> fn(&S, usize) -> Option<V> {
101        self.getter
102    }
103
104    /// Returns the setter function pointer.
105    pub fn setter(&self) -> fn(&mut S, usize, Option<V>) {
106        self.setter
107    }
108}
109
110impl<S, V> Move<S> for ChangeMove<S, V>
111where
112    S: PlanningSolution,
113    V: Clone + PartialEq + Send + Sync + Debug + 'static,
114{
115    fn is_doable<D: ScoreDirector<S>>(&self, score_director: &D) -> bool {
116        // Get current value using typed getter - no boxing, no downcast
117        let current = (self.getter)(score_director.working_solution(), self.entity_index);
118
119        // Compare directly - fully typed comparison
120        match (&current, &self.to_value) {
121            (None, None) => false,                      // Both unassigned
122            (Some(cur), Some(target)) => cur != target, // Different values
123            _ => true,                                  // One assigned, one not
124        }
125    }
126
127    fn do_move<D: ScoreDirector<S>>(&self, score_director: &mut D) {
128        // Capture old value using typed getter - zero erasure
129        let old_value = (self.getter)(score_director.working_solution(), self.entity_index);
130
131        // Notify before change
132        score_director.before_variable_changed(
133            self.descriptor_index,
134            self.entity_index,
135            self.variable_name,
136        );
137
138        // Set value using typed setter - no boxing
139        (self.setter)(
140            score_director.working_solution_mut(),
141            self.entity_index,
142            self.to_value.clone(),
143        );
144
145        // Notify after change
146        score_director.after_variable_changed(
147            self.descriptor_index,
148            self.entity_index,
149            self.variable_name,
150        );
151
152        // Register typed undo closure - zero erasure
153        let setter = self.setter;
154        let idx = self.entity_index;
155        score_director.register_undo(Box::new(move |s: &mut S| {
156            setter(s, idx, old_value);
157        }));
158    }
159
160    fn descriptor_index(&self) -> usize {
161        self.descriptor_index
162    }
163
164    fn entity_indices(&self) -> &[usize] {
165        std::slice::from_ref(&self.entity_index)
166    }
167
168    fn variable_name(&self) -> &str {
169        self.variable_name
170    }
171}