1use std::fmt;
9use std::sync::Arc;
10
11use solverforge_core::domain::{DynamicScalarVariableSlot, EntityClassId, VariableId};
12
13use super::{ScalarVariableSlot, ValueSource};
14
15#[derive(Clone, Debug, PartialEq, Eq, Hash)]
18pub struct RuntimeScalarSlotId {
19 pub descriptor_index: usize,
20 pub variable_index: usize,
21 pub entity_class: Arc<str>,
22 pub variable_name: Arc<str>,
23 pub dynamic_identity: Option<(EntityClassId, VariableId)>,
24}
25
26impl RuntimeScalarSlotId {
27 pub fn from_static_slot<S>(slot: &ScalarVariableSlot<S>) -> Self {
28 Self {
29 descriptor_index: slot.descriptor_index,
30 variable_index: slot.variable_index,
31 entity_class: Arc::from(slot.entity_type_name),
32 variable_name: Arc::from(slot.variable_name),
33 dynamic_identity: None,
34 }
35 }
36
37 pub fn from_dynamic_slot<S>(slot: &DynamicScalarVariableSlot<S>) -> Self {
38 Self {
39 descriptor_index: slot.descriptor_index(),
40 variable_index: slot.descriptor_variable_index(),
41 entity_class: Arc::from(slot.entity_type_name),
42 variable_name: Arc::from(slot.variable_name),
43 dynamic_identity: Some((slot.entity, slot.variable)),
44 }
45 }
46}
47
48impl fmt::Display for RuntimeScalarSlotId {
49 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50 write!(
51 f,
52 "{}.{} (descriptor {}, variable {})",
53 self.entity_class, self.variable_name, self.descriptor_index, self.variable_index
54 )
55 }
56}
57
58#[derive(Clone, Copy, Debug, PartialEq, Eq)]
61pub enum ScalarAccessCapability {
62 Candidates,
63 NearbyValue,
64 NearbyEntity,
65 ConstructionEntityOrder,
66 ConstructionValueOrder,
67 SearchEntityOrder,
68 SearchValueOrder,
69}
70
71pub enum RuntimeScalarSlot<S> {
74 Static(ScalarVariableSlot<S>),
75 Dynamic(DynamicScalarVariableSlot<S>),
76}
77
78impl<S> Clone for RuntimeScalarSlot<S> {
79 fn clone(&self) -> Self {
80 match self {
81 Self::Static(slot) => Self::Static(*slot),
82 Self::Dynamic(slot) => Self::Dynamic(slot.clone()),
83 }
84 }
85}
86
87impl<S> fmt::Debug for RuntimeScalarSlot<S> {
88 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89 f.debug_struct("RuntimeScalarSlot")
90 .field("id", &self.id())
91 .field("dynamic", &self.is_dynamic())
92 .finish()
93 }
94}
95
96impl<S> RuntimeScalarSlot<S> {
97 pub fn id(&self) -> RuntimeScalarSlotId {
98 match self {
99 Self::Static(slot) => RuntimeScalarSlotId::from_static_slot(slot),
100 Self::Dynamic(slot) => RuntimeScalarSlotId::from_dynamic_slot(slot),
101 }
102 }
103
104 pub fn matches_target(&self, entity_class: Option<&str>, variable_name: Option<&str>) -> bool {
105 match self {
106 Self::Static(slot) => slot.matches_target(entity_class, variable_name),
107 Self::Dynamic(slot) => slot.matches_target(entity_class, variable_name),
108 }
109 }
110
111 pub fn has_capability(&self, capability: ScalarAccessCapability) -> bool {
112 match (self, capability) {
113 (_, ScalarAccessCapability::Candidates) => true,
116 (Self::Static(slot), ScalarAccessCapability::NearbyValue) => {
117 slot.nearby_value_candidates.is_some()
118 }
119 (Self::Static(slot), ScalarAccessCapability::NearbyEntity) => {
120 slot.nearby_entity_candidates.is_some()
121 }
122 (Self::Static(slot), ScalarAccessCapability::ConstructionEntityOrder) => {
123 slot.construction_entity_order_key.is_some()
124 }
125 (Self::Static(slot), ScalarAccessCapability::ConstructionValueOrder) => {
126 slot.construction_value_order_key.is_some()
127 }
128 (Self::Static(slot), ScalarAccessCapability::SearchEntityOrder) => {
129 slot.search_entity_order_key.is_some()
130 }
131 (Self::Static(slot), ScalarAccessCapability::SearchValueOrder) => {
132 slot.search_value_order_key.is_some()
133 }
134 (Self::Dynamic(slot), ScalarAccessCapability::NearbyValue) => {
135 slot.has_nearby_value_candidates()
136 }
137 (Self::Dynamic(slot), ScalarAccessCapability::NearbyEntity) => {
138 slot.has_nearby_entity_candidates()
139 }
140 (Self::Dynamic(_), ScalarAccessCapability::ConstructionEntityOrder)
144 | (Self::Dynamic(_), ScalarAccessCapability::ConstructionValueOrder)
145 | (Self::Dynamic(_), ScalarAccessCapability::SearchEntityOrder)
146 | (Self::Dynamic(_), ScalarAccessCapability::SearchValueOrder) => false,
147 }
148 }
149
150 pub(crate) fn descriptor_index(&self) -> usize {
153 match self {
154 Self::Static(slot) => slot.descriptor_index,
155 Self::Dynamic(slot) => slot.descriptor_index(),
156 }
157 }
158
159 pub(crate) fn variable_index(&self) -> usize {
162 match self {
163 Self::Static(slot) => slot.variable_index,
164 Self::Dynamic(slot) => slot.descriptor_variable_index(),
165 }
166 }
167
168 pub(crate) fn variable_name(&self) -> &'static str {
172 match self {
173 Self::Static(slot) => slot.variable_name,
174 Self::Dynamic(slot) => slot.variable_name,
175 }
176 }
177
178 pub(crate) fn entity_count(&self, solution: &S) -> usize {
179 match self {
180 Self::Static(slot) => (slot.entity_count)(solution),
181 Self::Dynamic(slot) => slot.entity_count(solution),
182 }
183 }
184
185 pub(crate) fn allows_unassigned(&self) -> bool {
189 match self {
190 Self::Static(slot) => slot.allows_unassigned,
191 Self::Dynamic(slot) => slot.allows_unassigned,
192 }
193 }
194
195 pub(crate) fn current_value(&self, solution: &S, entity_index: usize) -> Option<usize> {
196 match self {
197 Self::Static(slot) => slot.current_value(solution, entity_index),
198 Self::Dynamic(slot) => slot.current_value(solution, entity_index),
199 }
200 }
201
202 pub(crate) fn set_value(&self, solution: &mut S, entity_index: usize, value: Option<usize>) {
203 match self {
204 Self::Static(slot) => slot.set_value(solution, entity_index, value),
205 Self::Dynamic(slot) => slot.set_value(solution, entity_index, value),
206 }
207 }
208
209 pub(crate) fn value_is_legal(
210 &self,
211 solution: &S,
212 entity_index: usize,
213 value: Option<usize>,
214 ) -> bool {
215 match self {
216 Self::Static(slot) => slot.value_is_legal(solution, entity_index, value),
217 Self::Dynamic(slot) => slot.value_is_legal(solution, entity_index, value),
218 }
219 }
220
221 pub(crate) fn swap_destination_is_legal(
225 &self,
226 solution: &S,
227 entity_index: usize,
228 value: Option<usize>,
229 ) -> bool {
230 match self {
231 Self::Static(slot) if matches!(slot.value_source, ValueSource::Empty) => {
232 value.is_some()
233 }
234 Self::Static(slot) => slot.value_is_legal(solution, entity_index, value),
235 Self::Dynamic(slot) => slot.value_is_legal(solution, entity_index, value),
236 }
237 }
238
239 pub(crate) fn visit_candidate_values(
243 &self,
244 solution: &S,
245 entity_index: usize,
246 limit: Option<usize>,
247 visit: &mut dyn FnMut(usize),
248 ) {
249 let limit = limit.unwrap_or(usize::MAX);
250 match self {
251 Self::Static(slot) => {
252 for value in slot.candidate_values_for_entity(solution, entity_index, Some(limit)) {
253 visit(value);
254 }
255 }
256 Self::Dynamic(slot) => {
257 for &value in slot
258 .candidate_values(solution, entity_index)
259 .iter()
260 .take(limit)
261 {
262 visit(value);
263 }
264 }
265 }
266 }
267
268 pub(crate) fn visit_nearby_value_candidates(
272 &self,
273 solution: &S,
274 entity_index: usize,
275 limit: usize,
276 visit: &mut dyn FnMut(usize),
277 ) -> bool {
278 match self {
279 Self::Static(slot) => {
280 let Some(source) = slot.nearby_value_candidates else {
281 return false;
282 };
283 for &value in source(solution, entity_index, slot.variable_index)
284 .iter()
285 .take(limit)
286 {
287 visit(value);
288 }
289 true
290 }
291 Self::Dynamic(slot) => {
292 slot.visit_nearby_value_candidates(solution, entity_index, limit, visit)
293 }
294 }
295 }
296
297 pub(crate) fn nearby_value_distance(
298 &self,
299 solution: &S,
300 entity_index: usize,
301 candidate: usize,
302 ) -> Option<f64> {
303 match self {
304 Self::Static(slot) => slot.nearby_value_distance(solution, entity_index, candidate),
305 Self::Dynamic(slot) => slot.nearby_value_distance(solution, entity_index, candidate),
306 }
307 }
308
309 pub(crate) fn visit_nearby_entity_candidates(
310 &self,
311 solution: &S,
312 entity_index: usize,
313 limit: usize,
314 visit: &mut dyn FnMut(usize),
315 ) -> bool {
316 match self {
317 Self::Static(slot) => {
318 let Some(source) = slot.nearby_entity_candidates else {
319 return false;
320 };
321 for &candidate in source(solution, entity_index, slot.variable_index)
322 .iter()
323 .take(limit)
324 {
325 visit(candidate);
326 }
327 true
328 }
329 Self::Dynamic(slot) => {
330 slot.visit_nearby_entity_candidates(solution, entity_index, limit, visit)
331 }
332 }
333 }
334
335 pub(crate) fn nearby_entity_distance(
336 &self,
337 solution: &S,
338 left_entity_index: usize,
339 right_entity_index: usize,
340 ) -> Option<f64> {
341 match self {
342 Self::Static(slot) => {
343 slot.nearby_entity_distance(solution, left_entity_index, right_entity_index)
344 }
345 Self::Dynamic(slot) => {
346 slot.nearby_entity_distance(solution, left_entity_index, right_entity_index)
347 }
348 }
349 }
350
351 pub(crate) fn construction_entity_order_key(
352 &self,
353 solution: &S,
354 entity_index: usize,
355 ) -> Option<i64> {
356 match self {
357 Self::Static(slot) => slot.construction_entity_order_key(solution, entity_index),
358 Self::Dynamic(_) => None,
359 }
360 }
361
362 pub(crate) fn construction_value_order_key(
363 &self,
364 solution: &S,
365 entity_index: usize,
366 value: usize,
367 ) -> Option<i64> {
368 match self {
369 Self::Static(slot) => slot.construction_value_order_key(solution, entity_index, value),
370 Self::Dynamic(_) => None,
371 }
372 }
373
374 pub(crate) fn search_entity_order_key(&self, solution: &S, entity_index: usize) -> Option<i64> {
375 match self {
376 Self::Static(slot) => slot.search_entity_order_key(solution, entity_index),
377 Self::Dynamic(_) => None,
378 }
379 }
380
381 pub(crate) fn search_value_order_key(
382 &self,
383 solution: &S,
384 entity_index: usize,
385 value: usize,
386 ) -> Option<i64> {
387 match self {
388 Self::Static(slot) => slot.search_value_order_key(solution, entity_index, value),
389 Self::Dynamic(_) => None,
390 }
391 }
392
393 pub(crate) fn edit(
394 &self,
395 entity_index: usize,
396 to_value: Option<usize>,
397 ) -> RuntimeScalarEdit<S> {
398 RuntimeScalarEdit {
399 slot: self.clone(),
400 entity_index,
401 to_value,
402 }
403 }
404
405 pub fn is_dynamic(&self) -> bool {
406 matches!(self, Self::Dynamic(_))
407 }
408}
409
410pub struct RuntimeScalarEdit<S> {
415 pub slot: RuntimeScalarSlot<S>,
416 pub entity_index: usize,
417 pub to_value: Option<usize>,
418}
419
420impl<S> Clone for RuntimeScalarEdit<S> {
421 fn clone(&self) -> Self {
422 Self {
423 slot: self.slot.clone(),
424 entity_index: self.entity_index,
425 to_value: self.to_value,
426 }
427 }
428}
429
430impl<S> fmt::Debug for RuntimeScalarEdit<S> {
431 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
432 f.debug_struct("RuntimeScalarEdit")
433 .field("slot", &self.slot)
434 .field("entity_index", &self.entity_index)
435 .field("to_value", &self.to_value)
436 .finish()
437 }
438}
439
440impl<S> RuntimeScalarEdit<S> {
441 pub fn id(&self) -> RuntimeScalarSlotId {
442 self.slot.id()
443 }
444
445 pub(crate) fn descriptor_index(&self) -> usize {
446 self.slot.descriptor_index()
447 }
448
449 pub(crate) fn variable_index(&self) -> usize {
450 self.slot.variable_index()
451 }
452
453 pub(crate) fn variable_name(&self) -> &'static str {
454 self.slot.variable_name()
455 }
456}