Skip to main content

solverforge_solver/heuristic/move/
change.rs

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