solverforge_solver/builder/context/scalar/
group.rs1use std::fmt;
2use std::sync::Arc;
3
4use solverforge_core::domain::{DynamicScalarAssignmentMetadata, DynamicScalarVariableSlot};
5
6use crate::planning::{ScalarCandidateProvider, ScalarGroup, ScalarGroupKind, ScalarGroupLimits};
7
8use super::variable::ScalarVariableSlot;
9
10mod assignment;
11mod member;
12
13pub use assignment::ScalarAssignmentBinding;
14pub use member::ScalarGroupMemberBinding;
15
16pub struct ScalarGroupBinding<S> {
17 pub group_name: &'static str,
18 pub members: Vec<ScalarGroupMemberBinding<S>>,
19 pub kind: ScalarGroupBindingKind<S>,
20 pub limits: ScalarGroupLimits,
21}
22
23#[allow(clippy::large_enum_variant)]
26pub enum ScalarGroupBindingKind<S> {
27 Candidates {
28 candidate_provider: ScalarCandidateProvider<S>,
29 },
30 Assignment(ScalarAssignmentBinding<S>),
31}
32
33impl<S> Clone for ScalarGroupBindingKind<S> {
34 fn clone(&self) -> Self {
35 match self {
36 Self::Candidates { candidate_provider } => Self::Candidates {
37 candidate_provider: *candidate_provider,
38 },
39 Self::Assignment(assignment) => Self::Assignment(assignment.clone()),
40 }
41 }
42}
43
44impl<S> ScalarGroupBinding<S> {
45 pub fn bind(group: ScalarGroup<S>, scalar_slots: &[ScalarVariableSlot<S>]) -> Self {
46 let members = group
47 .targets()
48 .iter()
49 .map(|target| {
50 let descriptor_index = target.descriptor_index();
51 let variable_name = target.variable_name();
52 let slot = scalar_slots
53 .iter()
54 .copied()
55 .find(|slot| {
56 slot.descriptor_index == descriptor_index
57 && slot.variable_name == variable_name
58 })
59 .unwrap_or_else(|| {
60 panic!(
61 "scalar group `{}` targets unknown scalar variable `{}` on descriptor {}",
62 group.group_name(),
63 variable_name,
64 descriptor_index
65 )
66 });
67 ScalarGroupMemberBinding::from_scalar_slot(slot)
68 })
69 .collect::<Vec<_>>();
70
71 let kind = match group.kind() {
72 ScalarGroupKind::Assignment(declaration) => ScalarGroupBindingKind::Assignment(
73 ScalarAssignmentBinding::bind(group.group_name(), &members, declaration),
74 ),
75 ScalarGroupKind::Candidates { candidate_provider } => {
76 ScalarGroupBindingKind::Candidates { candidate_provider }
77 }
78 };
79
80 Self {
81 group_name: group.group_name(),
82 members,
83 kind,
84 limits: group.limits(),
85 }
86 }
87
88 pub fn dynamic_assignment(
89 group_name: &'static str,
90 target: DynamicScalarVariableSlot<S>,
91 metadata: Arc<dyn DynamicScalarAssignmentMetadata<S>>,
92 limits: ScalarGroupLimits,
93 ) -> Self {
94 let member = ScalarGroupMemberBinding::from_dynamic_slot(target);
95 let assignment = ScalarAssignmentBinding::dynamic(group_name, member.clone(), metadata);
96 Self {
97 group_name,
98 members: vec![member],
99 kind: ScalarGroupBindingKind::Assignment(assignment),
100 limits,
101 }
102 }
103
104 pub fn member_for_edit(
105 &self,
106 edit: &crate::planning::ScalarEdit<S>,
107 ) -> Option<ScalarGroupMemberBinding<S>> {
108 self.members
109 .iter()
110 .find(|member| {
111 member.descriptor_index == edit.descriptor_index()
112 && member.variable_name == edit.variable_name()
113 })
114 .cloned()
115 }
116
117 pub fn assignment(&self) -> Option<&ScalarAssignmentBinding<S>> {
118 match &self.kind {
119 ScalarGroupBindingKind::Assignment(assignment) => Some(assignment),
120 ScalarGroupBindingKind::Candidates { .. } => None,
121 }
122 }
123
124 pub(crate) fn assignment_mut(&mut self) -> Option<&mut ScalarAssignmentBinding<S>> {
125 match &mut self.kind {
126 ScalarGroupBindingKind::Assignment(assignment) => Some(assignment),
127 ScalarGroupBindingKind::Candidates { .. } => None,
128 }
129 }
130
131 pub(crate) fn canonicalize_dynamic_members(
132 &mut self,
133 scalar_slots: &[DynamicScalarVariableSlot<S>],
134 ) -> Result<(), String> {
135 for member in &mut self.members {
136 let Some((entity, variable)) = member.dynamic_identity() else {
137 continue;
138 };
139 let slot = scalar_slots
140 .iter()
141 .find(|slot| slot.entity == entity && slot.variable == variable)
142 .cloned()
143 .ok_or_else(|| {
144 format!(
145 "dynamic scalar group `{}` targets unregistered scalar variable {}.{}",
146 self.group_name, member.entity_type_name, member.variable_name
147 )
148 })?;
149 member.canonicalize_dynamic_slot(slot)?;
150 }
151
152 let group_name = self.group_name;
153 let Some(assignment) = self.assignment_mut() else {
154 return Ok(());
155 };
156 let Some((entity, variable)) = assignment.target().dynamic_identity() else {
157 return Ok(());
158 };
159 let slot = scalar_slots
160 .iter()
161 .find(|slot| slot.entity == entity && slot.variable == variable)
162 .cloned()
163 .ok_or_else(|| {
164 format!(
165 "dynamic scalar group `{}` targets an unregistered assignment variable",
166 group_name
167 )
168 })?;
169 assignment.target_mut().canonicalize_dynamic_slot(slot)
170 }
171
172 pub fn is_assignment(&self) -> bool {
173 matches!(self.kind, ScalarGroupBindingKind::Assignment(_))
174 }
175
176 pub fn is_candidate_group(&self) -> bool {
177 matches!(self.kind, ScalarGroupBindingKind::Candidates { .. })
178 }
179
180 pub fn has_sequence_metadata(&self) -> bool {
181 self.assignment()
182 .is_some_and(ScalarAssignmentBinding::has_sequence_metadata)
183 }
184
185 pub fn has_position_metadata(&self) -> bool {
186 self.assignment()
187 .is_some_and(ScalarAssignmentBinding::has_position_metadata)
188 }
189
190 pub fn default_max_moves_per_step(&self) -> Option<usize> {
191 self.limits.max_moves_per_step
192 }
193}
194
195impl<S> Clone for ScalarGroupBinding<S> {
196 fn clone(&self) -> Self {
197 Self {
198 group_name: self.group_name,
199 members: self.members.clone(),
200 kind: self.kind.clone(),
201 limits: self.limits,
202 }
203 }
204}
205
206impl<S> fmt::Debug for ScalarGroupBinding<S> {
207 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
208 f.debug_struct("ScalarGroupBinding")
209 .field("group_name", &self.group_name)
210 .field("members", &self.members)
211 .finish_non_exhaustive()
212 }
213}
214
215pub fn bind_scalar_groups<S>(
216 groups: Vec<ScalarGroup<S>>,
217 scalar_slots: &[ScalarVariableSlot<S>],
218) -> Vec<ScalarGroupBinding<S>> {
219 groups
220 .into_iter()
221 .map(|group| ScalarGroupBinding::bind(group, scalar_slots))
222 .collect()
223}