1use std::fmt;
4use std::sync::Arc;
5
6use crate::domain::{EntityClassId, SolutionDescriptor, VariableId};
7use crate::score::Score;
8
9mod backend;
10mod resolution;
11
12use backend::{BackendListAccess, BackendScalarAccess};
13use resolution::{resolve_dynamic_descriptor_indexes, DynamicVariableKind};
14
15mod assignment;
16
17pub use assignment::{
18 DynamicScalarAssignmentMetadata, DynamicScalarAssignmentMetadataCapabilities,
19};
20
21pub trait DynamicModelBackend: Clone + Send + Sync + 'static {
27 type Score: Score;
28
29 fn entity_count(&self, entity: EntityClassId) -> usize;
30
31 fn get_scalar(&self, entity: EntityClassId, row: usize, variable: VariableId) -> Option<usize>;
32
33 fn set_scalar(
34 &mut self,
35 entity: EntityClassId,
36 row: usize,
37 variable: VariableId,
38 value: Option<usize>,
39 );
40
41 fn list_len(&self, entity: EntityClassId, row: usize, variable: VariableId) -> usize;
42
43 fn list_get(
44 &self,
45 entity: EntityClassId,
46 row: usize,
47 variable: VariableId,
48 pos: usize,
49 ) -> Option<usize>;
50
51 fn list_insert(
52 &mut self,
53 entity: EntityClassId,
54 row: usize,
55 variable: VariableId,
56 pos: usize,
57 value: usize,
58 );
59
60 fn list_remove(
61 &mut self,
62 entity: EntityClassId,
63 row: usize,
64 variable: VariableId,
65 pos: usize,
66 ) -> Option<usize>;
67
68 fn candidate_values(&self, entity: EntityClassId, row: usize, variable: VariableId)
69 -> &[usize];
70
71 fn scalar_value_is_legal(
72 &self,
73 entity: EntityClassId,
74 row: usize,
75 variable: VariableId,
76 value: usize,
77 ) -> bool;
78
79 fn list_element_count(&self, _entity: EntityClassId, _variable: VariableId) -> usize {
80 0
81 }
82
83 fn list_element(
84 &self,
85 _entity: EntityClassId,
86 _variable: VariableId,
87 element_index: usize,
88 ) -> Option<usize> {
89 Some(element_index)
90 }
91
92 fn list_assigned_elements(&self, _entity: EntityClassId, _variable: VariableId) -> Vec<usize> {
93 Vec::new()
94 }
95}
96
97pub trait DynamicScalarAccess<S>: Send + Sync {
99 fn entity_class(&self) -> EntityClassId;
100 fn variable(&self) -> VariableId;
101 fn entity_count(&self, solution: &S) -> usize;
102 fn get(&self, solution: &S, row: usize) -> Option<usize>;
103 fn set(&self, solution: &mut S, row: usize, value: Option<usize>);
104 fn candidate_values<'a>(&self, solution: &'a S, row: usize) -> &'a [usize];
105 fn value_is_legal(&self, solution: &S, row: usize, value: usize) -> bool;
106
107 fn has_nearby_value_candidates(&self) -> bool {
113 false
114 }
115
116 fn visit_nearby_value_candidates(
124 &self,
125 _solution: &S,
126 _row: usize,
127 _limit: usize,
128 _visit: &mut dyn FnMut(usize),
129 ) -> bool {
130 false
131 }
132
133 fn nearby_value_distance(&self, _solution: &S, _row: usize, _candidate: usize) -> Option<f64> {
136 None
137 }
138
139 fn has_nearby_entity_candidates(&self) -> bool {
141 false
142 }
143
144 fn visit_nearby_entity_candidates(
151 &self,
152 _solution: &S,
153 _left_row: usize,
154 _limit: usize,
155 _visit: &mut dyn FnMut(usize),
156 ) -> bool {
157 false
158 }
159
160 fn nearby_entity_distance(
163 &self,
164 _solution: &S,
165 _left_row: usize,
166 _right_row: usize,
167 ) -> Option<f64> {
168 None
169 }
170}
171
172#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
180pub struct DynamicListAccessCapabilities {
181 pub set: bool,
182 pub replace: bool,
183 pub reverse: bool,
184 pub sublist: bool,
185}
186
187pub trait DynamicListAccess<S>: Send + Sync {
188 fn entity_class(&self) -> EntityClassId;
189 fn variable(&self) -> VariableId;
190 fn entity_count(&self, solution: &S) -> usize;
191 fn element_count(&self, solution: &S) -> usize;
192 fn element(&self, solution: &S, element_index: usize) -> Option<usize>;
193 fn assigned_elements(&self, solution: &S) -> Vec<usize>;
194 fn len(&self, solution: &S, row: usize) -> usize;
195 fn get(&self, solution: &S, row: usize, pos: usize) -> Option<usize>;
196 fn insert(&self, solution: &mut S, row: usize, pos: usize, value: usize);
197 fn remove(&self, solution: &mut S, row: usize, pos: usize) -> Option<usize>;
198
199 fn capabilities(&self) -> DynamicListAccessCapabilities {
200 DynamicListAccessCapabilities::default()
201 }
202
203 fn set(&self, _solution: &mut S, _row: usize, _pos: usize, _value: usize) -> bool {
204 false
205 }
206
207 fn replace(&self, _solution: &mut S, _row: usize, _values: Vec<usize>) -> bool {
209 false
210 }
211
212 fn reverse(&self, _solution: &mut S, _row: usize, _start: usize, _end: usize) -> bool {
213 false
214 }
215
216 fn sublist_remove(
217 &self,
218 _solution: &mut S,
219 _row: usize,
220 _start: usize,
221 _end: usize,
222 ) -> Option<Vec<usize>> {
223 None
224 }
225
226 fn sublist_insert(
227 &self,
228 _solution: &mut S,
229 _row: usize,
230 _pos: usize,
231 _values: Vec<usize>,
232 ) -> bool {
233 false
234 }
235}
236
237#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
246pub struct DynamicListMetadataCapabilities {
247 pub element_owner: bool,
248 pub construction_order_key: bool,
249 pub precedence_duration: bool,
253 pub precedence_successors: bool,
257 pub cross_position_distance: bool,
258 pub intra_position_distance: bool,
259 pub route: bool,
260 pub savings: bool,
261}
262
263pub trait DynamicListMetadata<S>: Send + Sync {
273 fn entity_class(&self) -> EntityClassId;
274 fn variable(&self) -> VariableId;
275 fn capabilities(&self) -> DynamicListMetadataCapabilities;
276
277 fn element_owner(&self, solution: &S, element: usize) -> Option<usize>;
282
283 fn construction_order_key(&self, solution: &S, element: usize) -> Option<i64>;
285
286 fn precedence_duration(&self, solution: &S, element: usize) -> Option<usize>;
290
291 fn extend_precedence_successors(
298 &self,
299 solution: &S,
300 element: usize,
301 successors: &mut Vec<usize>,
302 ) -> bool;
303
304 fn cross_position_distance(
306 &self,
307 solution: &S,
308 from_entity: usize,
309 from_position: usize,
310 to_entity: usize,
311 to_position: usize,
312 ) -> Option<f64>;
313
314 fn intra_position_distance(
316 &self,
317 solution: &S,
318 entity: usize,
319 from_position: usize,
320 to_position: usize,
321 ) -> Option<f64>;
322
323 fn route_depot(&self, solution: &S, entity: usize) -> Option<usize>;
332 fn route_distance(&self, solution: &S, entity: usize, from: usize, to: usize) -> Option<i64>;
333 fn route_feasible(&self, solution: &S, entity: usize, route: &[usize]) -> Option<bool>;
334
335 fn savings_depot(&self, solution: &S, entity: usize) -> Option<usize>;
337 fn savings_metric_class(&self, solution: &S, entity: usize) -> Option<usize>;
338 fn savings_distance(&self, solution: &S, entity: usize, from: usize, to: usize) -> Option<i64>;
339 fn savings_feasible(&self, solution: &S, entity: usize, route: &[usize]) -> Option<bool>;
340}
341
342pub struct DynamicScalarVariableSlot<S> {
344 pub entity: EntityClassId,
345 pub variable: VariableId,
346 pub entity_type_name: &'static str,
347 pub variable_name: &'static str,
348 pub allows_unassigned: bool,
349 descriptor_index: Option<usize>,
350 descriptor_variable_index: Option<usize>,
351 access: Arc<dyn DynamicScalarAccess<S>>,
352}
353
354impl<S> Clone for DynamicScalarVariableSlot<S> {
355 fn clone(&self) -> Self {
356 Self {
357 entity: self.entity,
358 variable: self.variable,
359 entity_type_name: self.entity_type_name,
360 variable_name: self.variable_name,
361 allows_unassigned: self.allows_unassigned,
362 descriptor_index: self.descriptor_index,
363 descriptor_variable_index: self.descriptor_variable_index,
364 access: Arc::clone(&self.access),
365 }
366 }
367}
368
369impl<S> fmt::Debug for DynamicScalarVariableSlot<S> {
370 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
371 f.debug_struct("DynamicScalarVariableSlot")
372 .field("entity", &self.entity)
373 .field("variable", &self.variable)
374 .field("entity_type_name", &self.entity_type_name)
375 .field("variable_name", &self.variable_name)
376 .field("allows_unassigned", &self.allows_unassigned)
377 .field("descriptor_index", &self.descriptor_index)
378 .field("descriptor_variable_index", &self.descriptor_variable_index)
379 .finish()
380 }
381}
382
383impl<S> PartialEq for DynamicScalarVariableSlot<S> {
384 fn eq(&self, other: &Self) -> bool {
385 self.entity == other.entity
386 && self.variable == other.variable
387 && self.entity_type_name == other.entity_type_name
388 && self.variable_name == other.variable_name
389 && self.allows_unassigned == other.allows_unassigned
390 && self.descriptor_index == other.descriptor_index
391 && self.descriptor_variable_index == other.descriptor_variable_index
392 }
393}
394
395impl<S> Eq for DynamicScalarVariableSlot<S> {}
396
397impl<S> DynamicScalarVariableSlot<S> {
398 pub fn with_access(
399 entity: EntityClassId,
400 variable: VariableId,
401 entity_type_name: &'static str,
402 variable_name: &'static str,
403 allows_unassigned: bool,
404 access: Arc<dyn DynamicScalarAccess<S>>,
405 ) -> Self {
406 Self {
407 entity,
408 variable,
409 entity_type_name,
410 variable_name,
411 allows_unassigned,
412 descriptor_index: None,
413 descriptor_variable_index: None,
414 access,
415 }
416 }
417
418 pub fn resolve_descriptor_index(
419 &mut self,
420 descriptor: &SolutionDescriptor,
421 ) -> Result<(), String> {
422 let indexes = resolve_dynamic_descriptor_indexes(
423 descriptor,
424 self.entity,
425 self.variable,
426 self.entity_type_name,
427 self.variable_name,
428 DynamicVariableKind::Scalar,
429 )?;
430 if let Some(existing) = self.descriptor_index {
431 if existing != indexes.descriptor_index {
432 return Err(format!(
433 "dynamic scalar variable {}.{} was pre-bound to descriptor index {existing}, but logical entity ID {} resolves to descriptor index {}",
434 self.entity_type_name,
435 self.variable_name,
436 self.entity.0,
437 indexes.descriptor_index
438 ));
439 }
440 }
441 self.descriptor_index = Some(indexes.descriptor_index);
442 self.descriptor_variable_index = Some(indexes.variable_index);
443 Ok(())
444 }
445
446 pub fn resolved_against(mut self, descriptor: &SolutionDescriptor) -> Result<Self, String> {
447 self.resolve_descriptor_index(descriptor)?;
448 Ok(self)
449 }
450
451 pub fn is_descriptor_resolved(&self) -> bool {
452 self.descriptor_index.is_some() && self.descriptor_variable_index.is_some()
453 }
454
455 pub fn descriptor_index(&self) -> usize {
456 self.descriptor_index.unwrap_or_else(|| {
457 panic!(
458 "dynamic scalar variable {}.{} has not been resolved against a SolutionDescriptor",
459 self.entity_type_name, self.variable_name
460 )
461 })
462 }
463
464 pub fn descriptor_variable_index(&self) -> usize {
465 self.descriptor_variable_index.unwrap_or_else(|| {
466 panic!(
467 "dynamic scalar variable {}.{} has not been resolved against a SolutionDescriptor",
468 self.entity_type_name, self.variable_name
469 )
470 })
471 }
472
473 pub fn matches_target(&self, entity_class: Option<&str>, variable_name: Option<&str>) -> bool {
474 entity_class.is_none_or(|entity| entity == self.entity_type_name)
475 && variable_name.is_none_or(|variable| variable == self.variable_name)
476 }
477
478 pub fn entity_count(&self, solution: &S) -> usize {
479 self.access.entity_count(solution)
480 }
481
482 pub fn current_value(&self, solution: &S, row: usize) -> Option<usize> {
483 self.access.get(solution, row)
484 }
485
486 pub fn set_value(&self, solution: &mut S, row: usize, value: Option<usize>) {
487 self.access.set(solution, row, value);
488 }
489
490 pub fn candidate_values<'a>(&self, solution: &'a S, row: usize) -> &'a [usize] {
491 self.access.candidate_values(solution, row)
492 }
493
494 pub fn has_nearby_value_candidates(&self) -> bool {
495 self.access.has_nearby_value_candidates()
496 }
497
498 pub fn visit_nearby_value_candidates(
499 &self,
500 solution: &S,
501 row: usize,
502 limit: usize,
503 visit: &mut dyn FnMut(usize),
504 ) -> bool {
505 self.access
506 .visit_nearby_value_candidates(solution, row, limit, visit)
507 }
508
509 pub fn nearby_value_distance(&self, solution: &S, row: usize, candidate: usize) -> Option<f64> {
510 self.access.nearby_value_distance(solution, row, candidate)
511 }
512
513 pub fn has_nearby_entity_candidates(&self) -> bool {
514 self.access.has_nearby_entity_candidates()
515 }
516
517 pub fn visit_nearby_entity_candidates(
518 &self,
519 solution: &S,
520 left_row: usize,
521 limit: usize,
522 visit: &mut dyn FnMut(usize),
523 ) -> bool {
524 self.access
525 .visit_nearby_entity_candidates(solution, left_row, limit, visit)
526 }
527
528 pub fn nearby_entity_distance(
529 &self,
530 solution: &S,
531 left_row: usize,
532 right_row: usize,
533 ) -> Option<f64> {
534 self.access
535 .nearby_entity_distance(solution, left_row, right_row)
536 }
537
538 pub fn value_is_legal(&self, solution: &S, row: usize, value: Option<usize>) -> bool {
539 let Some(value) = value else {
540 return self.allows_unassigned;
541 };
542 self.access.value_is_legal(solution, row, value)
543 }
544}
545
546impl<S> DynamicScalarVariableSlot<S>
547where
548 S: DynamicModelBackend,
549{
550 pub fn new(
551 entity: EntityClassId,
552 variable: VariableId,
553 entity_type_name: &'static str,
554 variable_name: &'static str,
555 allows_unassigned: bool,
556 ) -> Self {
557 Self::with_access(
558 entity,
559 variable,
560 entity_type_name,
561 variable_name,
562 allows_unassigned,
563 Arc::new(BackendScalarAccess { entity, variable }),
564 )
565 }
566}
567
568pub struct DynamicListVariableSlot<S> {
570 pub entity: EntityClassId,
571 pub variable: VariableId,
572 pub entity_type_name: &'static str,
573 pub variable_name: &'static str,
574 descriptor_index: Option<usize>,
575 descriptor_variable_index: Option<usize>,
576 access: Arc<dyn DynamicListAccess<S>>,
577 metadata: Option<Arc<dyn DynamicListMetadata<S>>>,
578}
579
580impl<S> Clone for DynamicListVariableSlot<S> {
581 fn clone(&self) -> Self {
582 Self {
583 entity: self.entity,
584 variable: self.variable,
585 entity_type_name: self.entity_type_name,
586 variable_name: self.variable_name,
587 descriptor_index: self.descriptor_index,
588 descriptor_variable_index: self.descriptor_variable_index,
589 access: Arc::clone(&self.access),
590 metadata: self.metadata.as_ref().map(Arc::clone),
591 }
592 }
593}
594
595impl<S> fmt::Debug for DynamicListVariableSlot<S> {
596 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
597 f.debug_struct("DynamicListVariableSlot")
598 .field("entity", &self.entity)
599 .field("variable", &self.variable)
600 .field("entity_type_name", &self.entity_type_name)
601 .field("variable_name", &self.variable_name)
602 .field("descriptor_index", &self.descriptor_index)
603 .field("descriptor_variable_index", &self.descriptor_variable_index)
604 .field(
605 "metadata_capabilities",
606 &self
607 .metadata
608 .as_ref()
609 .map(|metadata| metadata.capabilities()),
610 )
611 .finish()
612 }
613}
614
615impl<S> PartialEq for DynamicListVariableSlot<S> {
616 fn eq(&self, other: &Self) -> bool {
617 self.entity == other.entity
618 && self.variable == other.variable
619 && self.entity_type_name == other.entity_type_name
620 && self.variable_name == other.variable_name
621 && self.descriptor_index == other.descriptor_index
622 && self.descriptor_variable_index == other.descriptor_variable_index
623 }
624}
625
626impl<S> Eq for DynamicListVariableSlot<S> {}
627
628impl<S> DynamicListVariableSlot<S> {
629 pub fn try_with_access(
636 entity: EntityClassId,
637 variable: VariableId,
638 entity_type_name: &'static str,
639 variable_name: &'static str,
640 access: Arc<dyn DynamicListAccess<S>>,
641 ) -> Result<Self, String> {
642 if access.entity_class() != entity || access.variable() != variable {
643 return Err(format!(
644 "dynamic list access is bound to entity ID {} variable ID {}, but slot {}.{} is entity ID {} variable ID {}",
645 access.entity_class().0,
646 access.variable().0,
647 entity_type_name,
648 variable_name,
649 entity.0,
650 variable.0,
651 ));
652 }
653 Ok(Self {
654 entity,
655 variable,
656 entity_type_name,
657 variable_name,
658 descriptor_index: None,
659 descriptor_variable_index: None,
660 access,
661 metadata: None,
662 })
663 }
664
665 pub fn with_access_and_metadata(
672 entity: EntityClassId,
673 variable: VariableId,
674 entity_type_name: &'static str,
675 variable_name: &'static str,
676 access: Arc<dyn DynamicListAccess<S>>,
677 metadata: Arc<dyn DynamicListMetadata<S>>,
678 ) -> Result<Self, String> {
679 Self::try_with_access(entity, variable, entity_type_name, variable_name, access)?
680 .with_metadata(metadata)
681 }
682
683 pub fn with_metadata(
687 mut self,
688 metadata: Arc<dyn DynamicListMetadata<S>>,
689 ) -> Result<Self, String> {
690 if metadata.entity_class() != self.entity || metadata.variable() != self.variable {
691 return Err(format!(
692 "dynamic list metadata is bound to entity ID {} variable ID {}, but slot {}.{} is entity ID {} variable ID {}",
693 metadata.entity_class().0,
694 metadata.variable().0,
695 self.entity_type_name,
696 self.variable_name,
697 self.entity.0,
698 self.variable.0,
699 ));
700 }
701 self.metadata = Some(metadata);
702 Ok(self)
703 }
704
705 pub fn resolve_descriptor_index(
706 &mut self,
707 descriptor: &SolutionDescriptor,
708 ) -> Result<(), String> {
709 let indexes = resolve_dynamic_descriptor_indexes(
710 descriptor,
711 self.entity,
712 self.variable,
713 self.entity_type_name,
714 self.variable_name,
715 DynamicVariableKind::List,
716 )?;
717 if let Some(existing) = self.descriptor_index {
718 if existing != indexes.descriptor_index {
719 return Err(format!(
720 "dynamic list variable {}.{} was pre-bound to descriptor index {existing}, but logical entity ID {} resolves to descriptor index {}",
721 self.entity_type_name,
722 self.variable_name,
723 self.entity.0,
724 indexes.descriptor_index
725 ));
726 }
727 }
728 self.descriptor_index = Some(indexes.descriptor_index);
729 self.descriptor_variable_index = Some(indexes.variable_index);
730 Ok(())
731 }
732
733 pub fn resolved_against(mut self, descriptor: &SolutionDescriptor) -> Result<Self, String> {
734 self.resolve_descriptor_index(descriptor)?;
735 Ok(self)
736 }
737
738 pub fn is_descriptor_resolved(&self) -> bool {
739 self.descriptor_index.is_some() && self.descriptor_variable_index.is_some()
740 }
741
742 pub fn descriptor_index(&self) -> usize {
743 self.descriptor_index.unwrap_or_else(|| {
744 panic!(
745 "dynamic list variable {}.{} has not been resolved against a SolutionDescriptor",
746 self.entity_type_name, self.variable_name
747 )
748 })
749 }
750
751 pub fn descriptor_variable_index(&self) -> usize {
752 self.descriptor_variable_index.unwrap_or_else(|| {
753 panic!(
754 "dynamic list variable {}.{} has not been resolved against a SolutionDescriptor",
755 self.entity_type_name, self.variable_name
756 )
757 })
758 }
759
760 pub fn matches_target(&self, entity_class: Option<&str>, variable_name: Option<&str>) -> bool {
761 entity_class.is_none_or(|entity| entity == self.entity_type_name)
762 && variable_name.is_none_or(|variable| variable == self.variable_name)
763 }
764
765 pub fn entity_count(&self, solution: &S) -> usize {
766 self.access.entity_count(solution)
767 }
768
769 pub fn element_count(&self, solution: &S) -> usize {
770 self.access.element_count(solution)
771 }
772
773 pub fn element(&self, solution: &S, element_index: usize) -> Option<usize> {
774 self.access.element(solution, element_index)
775 }
776
777 pub fn assigned_elements(&self, solution: &S) -> Vec<usize> {
778 self.access.assigned_elements(solution)
779 }
780
781 pub fn list_len(&self, solution: &S, row: usize) -> usize {
782 self.access.len(solution, row)
783 }
784
785 pub fn list_get(&self, solution: &S, row: usize, pos: usize) -> Option<usize> {
786 self.access.get(solution, row, pos)
787 }
788
789 pub fn list_insert(&self, solution: &mut S, row: usize, pos: usize, value: usize) {
790 self.access.insert(solution, row, pos, value);
791 }
792
793 pub fn list_remove(&self, solution: &mut S, row: usize, pos: usize) -> Option<usize> {
794 self.access.remove(solution, row, pos)
795 }
796
797 pub fn access_capabilities(&self) -> DynamicListAccessCapabilities {
798 self.access.capabilities()
799 }
800
801 pub fn list_set(&self, solution: &mut S, row: usize, pos: usize, value: usize) -> bool {
802 self.access.set(solution, row, pos, value)
803 }
804
805 pub fn list_replace(&self, solution: &mut S, row: usize, values: Vec<usize>) -> bool {
809 self.access.replace(solution, row, values)
810 }
811
812 pub fn list_reverse(&self, solution: &mut S, row: usize, start: usize, end: usize) -> bool {
813 self.access.reverse(solution, row, start, end)
814 }
815
816 pub fn sublist_remove(
817 &self,
818 solution: &mut S,
819 row: usize,
820 start: usize,
821 end: usize,
822 ) -> Option<Vec<usize>> {
823 self.access.sublist_remove(solution, row, start, end)
824 }
825
826 pub fn sublist_insert(
827 &self,
828 solution: &mut S,
829 row: usize,
830 pos: usize,
831 values: Vec<usize>,
832 ) -> bool {
833 self.access.sublist_insert(solution, row, pos, values)
834 }
835
836 pub fn metadata(&self) -> Option<&dyn DynamicListMetadata<S>> {
841 self.metadata.as_deref()
842 }
843
844 pub fn metadata_capabilities(&self) -> Option<DynamicListMetadataCapabilities> {
845 self.metadata
846 .as_ref()
847 .map(|metadata| metadata.capabilities())
848 }
849
850 pub fn require_metadata(&self) -> Result<&dyn DynamicListMetadata<S>, String> {
851 self.metadata().ok_or_else(|| {
852 format!(
853 "dynamic list variable {}.{} has no immutable list metadata; selected list phases must declare and bind their required capability bundle before solve",
854 self.entity_type_name, self.variable_name,
855 )
856 })
857 }
858}
859
860impl<S> DynamicListVariableSlot<S>
861where
862 S: DynamicModelBackend,
863{
864 pub fn new(
865 entity: EntityClassId,
866 variable: VariableId,
867 entity_type_name: &'static str,
868 variable_name: &'static str,
869 ) -> Self {
870 Self {
871 entity,
872 variable,
873 entity_type_name,
874 variable_name,
875 descriptor_index: None,
876 descriptor_variable_index: None,
877 access: Arc::new(BackendListAccess { entity, variable }),
878 metadata: None,
879 }
880 }
881
882 pub fn new_with_metadata(
884 entity: EntityClassId,
885 variable: VariableId,
886 entity_type_name: &'static str,
887 variable_name: &'static str,
888 metadata: Arc<dyn DynamicListMetadata<S>>,
889 ) -> Result<Self, String> {
890 Self::with_access_and_metadata(
891 entity,
892 variable,
893 entity_type_name,
894 variable_name,
895 Arc::new(BackendListAccess { entity, variable }),
896 metadata,
897 )
898 }
899}