Skip to main content

solverforge_core/domain/
entity_ref.rs

1//! Entity reference types for dynamic entity access.
2//!
3//! These types enable the solver to work with entities at runtime without
4//! knowing their concrete types at compile time.
5
6use std::any::Any;
7use std::fmt::Debug;
8
9/// A reference to a planning entity with its index in the solution.
10///
11/// This struct provides a way to identify and access entities during solving
12/// without needing to know the concrete entity type.
13#[derive(Debug, Clone)]
14pub struct EntityRef {
15    /// Index of this entity in its collection.
16    pub index: usize,
17    /// Name of the entity type.
18    pub type_name: &'static str,
19    /// Name of the collection field in the solution.
20    pub collection_field: &'static str,
21}
22
23impl EntityRef {
24    /// Creates a new entity reference.
25    pub fn new(index: usize, type_name: &'static str, collection_field: &'static str) -> Self {
26        Self {
27            index,
28            type_name,
29            collection_field,
30        }
31    }
32}
33
34/// Trait for extracting entities from a planning solution.
35///
36/// This trait is implemented by closures/functions that can extract
37/// entity references from a solution of a specific type.
38pub trait EntityExtractor: Send + Sync {
39    /// Returns the number of entities in the collection.
40    fn count(&self, solution: &dyn Any) -> Option<usize>;
41
42    /// Gets a reference to an entity by index.
43    fn get<'a>(&self, solution: &'a dyn Any, index: usize) -> Option<&'a dyn Any>;
44
45    /// Gets a mutable reference to an entity by index.
46    fn get_mut<'a>(&self, solution: &'a mut dyn Any, index: usize) -> Option<&'a mut dyn Any>;
47
48    /// Returns an iterator over entity references.
49    fn entity_refs(&self, solution: &dyn Any) -> Vec<EntityRef>;
50
51    /// Clone this extractor.
52    fn clone_box(&self) -> Box<dyn EntityExtractor>;
53
54    /// Clones an entity as a boxed value for insertion into the constraint session.
55    ///
56    /// This is used for incremental scoring where entities need to be inserted
57    /// into the BAVET session as owned, type-erased values.
58    fn clone_entity_boxed(
59        &self,
60        solution: &dyn Any,
61        index: usize,
62    ) -> Option<Box<dyn Any + Send + Sync>>;
63
64    /// Returns the TypeId of the entity type.
65    fn entity_type_id(&self) -> std::any::TypeId;
66}
67
68impl Clone for Box<dyn EntityExtractor> {
69    fn clone(&self) -> Self {
70        self.clone_box()
71    }
72}
73
74/// A concrete entity extractor for a specific solution and entity type.
75///
76/// # Type Parameters
77/// * `S` - The solution type
78/// * `E` - The entity type
79pub struct TypedEntityExtractor<S, E> {
80    /// Name of the entity type.
81    type_name: &'static str,
82    /// Name of the collection field in the solution.
83    collection_field: &'static str,
84    /// Function to get the entity collection from a solution.
85    get_collection: fn(&S) -> &Vec<E>,
86    /// Function to get the mutable entity collection from a solution.
87    get_collection_mut: fn(&mut S) -> &mut Vec<E>,
88}
89
90impl<S, E> TypedEntityExtractor<S, E>
91where
92    S: 'static,
93    E: 'static,
94{
95    /// Creates a new typed entity extractor.
96    pub fn new(
97        type_name: &'static str,
98        collection_field: &'static str,
99        get_collection: fn(&S) -> &Vec<E>,
100        get_collection_mut: fn(&mut S) -> &mut Vec<E>,
101    ) -> Self {
102        Self {
103            type_name,
104            collection_field,
105            get_collection,
106            get_collection_mut,
107        }
108    }
109}
110
111impl<S, E> EntityExtractor for TypedEntityExtractor<S, E>
112where
113    S: Send + Sync + 'static,
114    E: Clone + Send + Sync + 'static,
115{
116    fn count(&self, solution: &dyn Any) -> Option<usize> {
117        let solution = solution.downcast_ref::<S>()?;
118        Some((self.get_collection)(solution).len())
119    }
120
121    fn get<'a>(&self, solution: &'a dyn Any, index: usize) -> Option<&'a dyn Any> {
122        let solution = solution.downcast_ref::<S>()?;
123        let collection = (self.get_collection)(solution);
124        collection.get(index).map(|e| e as &dyn Any)
125    }
126
127    fn get_mut<'a>(&self, solution: &'a mut dyn Any, index: usize) -> Option<&'a mut dyn Any> {
128        let solution = solution.downcast_mut::<S>()?;
129        let collection = (self.get_collection_mut)(solution);
130        collection.get_mut(index).map(|e| e as &mut dyn Any)
131    }
132
133    fn entity_refs(&self, solution: &dyn Any) -> Vec<EntityRef> {
134        let Some(solution) = solution.downcast_ref::<S>() else {
135            return Vec::new();
136        };
137        let collection = (self.get_collection)(solution);
138        (0..collection.len())
139            .map(|i| EntityRef::new(i, self.type_name, self.collection_field))
140            .collect()
141    }
142
143    fn clone_box(&self) -> Box<dyn EntityExtractor> {
144        Box::new(Self {
145            type_name: self.type_name,
146            collection_field: self.collection_field,
147            get_collection: self.get_collection,
148            get_collection_mut: self.get_collection_mut,
149        })
150    }
151
152    fn clone_entity_boxed(
153        &self,
154        solution: &dyn Any,
155        index: usize,
156    ) -> Option<Box<dyn Any + Send + Sync>> {
157        let solution = solution.downcast_ref::<S>()?;
158        let collection = (self.get_collection)(solution);
159        let entity = collection.get(index)?;
160        Some(Box::new(entity.clone()) as Box<dyn Any + Send + Sync>)
161    }
162
163    fn entity_type_id(&self) -> std::any::TypeId {
164        std::any::TypeId::of::<E>()
165    }
166}
167
168impl<S, E> Debug for TypedEntityExtractor<S, E> {
169    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
170        f.debug_struct("TypedEntityExtractor")
171            .field("type_name", &self.type_name)
172            .field("collection_field", &self.collection_field)
173            .finish()
174    }
175}