honeycomb_core/attributes/
traits.rs

1//! Generic attributes implementation
2//!
3//! This module contains all code used to handle attribute genericity in the context of mesh
4//! representation through combinatorial maps embedded data.
5
6use std::any::{type_name, Any};
7use std::fmt::Debug;
8
9use downcast_rs::{impl_downcast, Downcast};
10use fast_stm::TransactionClosureResult;
11
12use crate::attributes::AttributeError;
13use crate::cmap::{DartIdType, OrbitPolicy};
14use crate::stm::{StmClosureResult, Transaction};
15
16/// # Generic attribute trait
17///
18/// This trait is used to describe how a values of a given attribute are merged and split during
19/// sewing and unsewing operations.
20///
21/// ## Example
22///
23/// A detailed example is provided in the [user guide][UG].
24///
25/// [UG]: https://lihpc-computational-geometry.github.io/honeycomb/user-guide/usage/attributes.html
26pub trait AttributeUpdate: Sized + Send + Sync + Clone + Copy {
27    /// Merging routine, i.e. how to obtain a new value from two existing ones.
28    ///
29    /// # Errors
30    ///
31    /// You may use [`AttributeError::FailedMerge`] to model a possible failure in your attribute
32    /// mergin process.
33    fn merge(attr1: Self, attr2: Self) -> Result<Self, AttributeError>;
34
35    /// Splitting routine, i.e. how to obtain the two new values from a single one.
36    ///
37    /// # Errors
38    ///
39    /// You may use [`AttributeError::FailedSplit`] to model a possible failure in your attribute
40    /// mergin process.
41    fn split(attr: Self) -> Result<(Self, Self), AttributeError>;
42
43    #[allow(clippy::missing_errors_doc)]
44    /// Fallback merging routine, i.e. how to obtain a new value from a single existing one.
45    ///
46    /// The returned value directly affects the behavior of sewing methods: For example, if this
47    /// method returns an error for a given attribute, the `sew` method will fail. This allows the
48    /// user to define some attribute-specific behavior and enable fallbacks when it makes sense.
49    ///
50    /// # Return / Errors
51    ///
52    /// The default implementation fails and returns [`AttributeError::InsufficientData`]. You may
53    /// override the implementation and use [`AttributeError::FailedMerge`] to model another
54    /// possible failure.
55    fn merge_incomplete(_: Self) -> Result<Self, AttributeError> {
56        Err(AttributeError::InsufficientData(
57            "merge",
58            type_name::<Self>(),
59        ))
60    }
61
62    /// Fallback merging routine, i.e. how to obtain a new value from no existing one.
63    ///
64    /// The returned value directly affects the behavior of sewing methods: For example, if this
65    /// method returns an error for a given attribute, the `sew` method will fail. This allows the
66    /// user to define some attribute-specific behavior and enable fallbacks when it makes sense.
67    ///
68    /// # Errors
69    ///
70    /// The default implementation fails and returns [`AttributeError::InsufficientData`].
71    fn merge_from_none() -> Result<Self, AttributeError> {
72        Err(AttributeError::InsufficientData(
73            "merge",
74            type_name::<Self>(),
75        ))
76    }
77
78    /// Fallback splitting routine, i.e. how to obtain two new values from no existing one.
79    ///
80    /// The returned value directly affects the behavior of sewing methods: For example, if this
81    /// method returns an error for a given attribute, the `unsew` method will fail. This allows the
82    /// user to define some attribute-specific behavior and enable fallbacks when it makes sense.
83    /// value).
84    ///
85    /// # Errors
86    ///
87    /// The default implementation fails and returns [`AttributeError::InsufficientData`].
88    fn split_from_none() -> Result<(Self, Self), AttributeError> {
89        Err(AttributeError::InsufficientData(
90            "split",
91            type_name::<Self>(),
92        ))
93    }
94}
95
96/// # Generic attribute trait
97///
98/// This trait is used to describe how a given attribute binds to the map, and how it should be
99/// stored in memory.
100///
101/// ## Example
102///
103/// A detailed example is provided in the [user guide][UG].
104///
105/// [UG]: https://lihpc-computational-geometry.github.io/honeycomb/user-guide/usage/attributes.html
106pub trait AttributeBind: Debug + Sized + Any {
107    /// Storage type used for the attribute.
108    type StorageType: AttributeStorage<Self>;
109
110    /// Identifier type of the entity the attribute is bound to.
111    type IdentifierType: From<DartIdType> + num_traits::ToPrimitive + Clone;
112
113    /// [`OrbitPolicy`] determining the kind of topological entity to which the attribute
114    /// is associated.
115    const BIND_POLICY: OrbitPolicy;
116}
117
118/// # Generic attribute storage trait
119///
120/// This trait defines attribute-agnostic functions & methods. The documentation describes the
121/// expected behavior of each item. “ID” and “index” are used interchangeably.
122pub trait UnknownAttributeStorage: Any + Debug + Downcast {
123    /// Constructor
124    ///
125    /// # Arguments
126    ///
127    /// - `length: usize` -- Initial length/capacity of the storage. It should correspond to
128    ///   the upper bound of IDs used to index the attribute's values, i.e. the number of darts
129    ///   including the null dart.
130    ///
131    /// # Return
132    ///
133    /// Return a [Self] instance which yields correct accesses over the ID range `0..length`.
134    #[must_use = "unused return value"]
135    fn new(length: usize) -> Self
136    where
137        Self: Sized;
138
139    /// Extend the storage's length
140    ///
141    /// # Arguments
142    ///
143    /// - `length: usize` -- length of which the storage should be extended.
144    fn extend(&mut self, length: usize);
145
146    /// Return the number of stored attributes, i.e. the number of used slots in the storage (not
147    /// its length).
148    #[must_use = "unused return value"]
149    fn n_attributes(&self) -> usize;
150
151    /// Merge attributes to specified index
152    ///
153    /// # Arguments
154    ///
155    /// - `trans: &mut Transaction` -- Transaction used for synchronization.
156    /// - `out: DartIdentifier` -- Identifier to associate the result with.
157    /// - `lhs_inp: DartIdentifier` -- Identifier of one attribute value to merge.
158    /// - `rhs_inp: DartIdentifier` -- Identifier of the other attribute value to merge.
159    ///
160    /// # Behavior (pseudo-code)
161    ///
162    /// ```text
163    /// let new_val = match (attributes.remove(lhs_inp), attributes.remove(rhs_inp)) {
164    ///     (Some(v1), Some(v2)) => AttributeUpdate::merge(v1, v2),
165    ///     (Some(v), None) | (None, Some(v)) => AttributeUpdate::merge_undefined(Some(v)),
166    ///     None, None => AttributeUpdate::merge_undefined(None),
167    /// }
168    /// attributes.set(out, new_val);
169    /// ```
170    ///
171    /// # Errors
172    ///
173    /// This method will fail, returning an error, if:
174    /// - the transaction cannot be completed
175    /// - the merge fails (e.g. because one merging value is missing)
176    ///
177    /// The returned error can be used in conjunction with transaction control to avoid any
178    /// modifications in case of failure at attribute level. The user can then choose, through its
179    /// transaction control policy, to retry or abort as he wishes.
180    fn merge(
181        &self,
182        trans: &mut Transaction,
183        out: DartIdType,
184        lhs_inp: DartIdType,
185        rhs_inp: DartIdType,
186    ) -> TransactionClosureResult<(), AttributeError>;
187
188    /// Split attribute to specified indices
189    ///
190    /// # Arguments
191    ///
192    /// - `trans: &mut Transaction` -- Transaction used for synchronization.
193    /// - `lhs_out: DartIdentifier` -- Identifier to associate the result with.
194    /// - `rhs_out: DartIdentifier` -- Identifier to associate the result with.
195    /// - `inp: DartIdentifier` -- Identifier of the attribute value to split.
196    ///
197    /// # Behavior pseudo-code
198    ///
199    /// ```text
200    /// (val_lhs, val_rhs) = AttributeUpdate::split(attributes.remove(inp).unwrap());
201    /// attributes[lhs_out] = val_lhs;
202    /// attributes[rhs_out] = val_rhs;
203    ///
204    /// ```
205    ///
206    /// # Errors
207    ///
208    /// This method will fail, returning an error, if:
209    /// - the transaction cannot be completed
210    /// - the split fails (e.g. because there is no value to split from)
211    ///
212    /// The returned error can be used in conjunction with transaction control to avoid any
213    /// modifications in case of failure at attribute level. The user can then choose, through its
214    /// transaction control policy, to retry or abort as he wishes.
215    fn split(
216        &self,
217        trans: &mut Transaction,
218        lhs_out: DartIdType,
219        rhs_out: DartIdType,
220        inp: DartIdType,
221    ) -> TransactionClosureResult<(), AttributeError>;
222}
223
224impl_downcast!(UnknownAttributeStorage);
225
226/// # Generic attribute storage trait
227///
228/// This trait defines attribute-specific methods. The documentation describes the expected behavior
229/// of each method. "ID" and "index" are used interchangeably.
230pub trait AttributeStorage<A: AttributeBind>: UnknownAttributeStorage {
231    #[allow(clippy::missing_errors_doc)]
232    /// Read the value of an element at a given index.
233    ///
234    /// # Arguments
235    ///
236    /// - `trans: &mut Transaction` -- Transaction used for synchronization.
237    /// - `index: A::IdentifierType` -- Cell index.
238    ///
239    /// # Return / Errors
240    ///
241    /// This method is meant to be called in a context where the returned `Result` is used to
242    /// validate the transaction passed as argument. Errors should not be processed manually,
243    /// only processed via the `?` operator.
244    ///
245    /// # Panics
246    ///
247    /// The method:
248    /// - should panic if the index lands out of bounds
249    /// - may panic if the index cannot be converted to `usize`
250    fn read(&self, trans: &mut Transaction, id: A::IdentifierType) -> StmClosureResult<Option<A>>;
251
252    #[allow(clippy::missing_errors_doc)]
253    /// Write the value of an element at a given index and return the old value.
254    ///
255    /// # Arguments
256    ///
257    /// - `trans: &mut Transaction` -- Transaction used for synchronization.
258    /// - `index: A::IdentifierType` -- Cell index.
259    /// - `val: A` -- Attribute value.
260    ///
261    /// # Return / Errors
262    ///
263    /// This method is meant to be called in a context where the returned `Result` is used to
264    /// validate the transaction passed as argument. Errors should not be processed manually,
265    /// only processed via the `?` operator.
266    ///
267    /// # Panics
268    ///
269    /// The method:
270    /// - should panic if the index lands out of bounds
271    /// - may panic if the index cannot be converted to `usize`
272    fn write(
273        &self,
274        trans: &mut Transaction,
275        id: A::IdentifierType,
276        val: A,
277    ) -> StmClosureResult<Option<A>>;
278
279    #[allow(clippy::missing_errors_doc)]
280    /// Remove the value at a given index and return it.
281    ///
282    /// # Arguments
283    ///
284    /// - `trans: &mut Transaction` -- Transaction used for synchronization.
285    /// - `index: A::IdentifierType` -- Cell index.
286    ///
287    /// # Return / Errors
288    ///
289    /// This method is meant to be called in a context where the returned `Result` is used to
290    /// validate the transaction passed as argument. Errors should not be processed manually,
291    /// only processed via the `?` operator.
292    ///
293    /// # Panics
294    ///
295    /// The method:
296    /// - should panic if the index lands out of bounds
297    /// - may panic if the index cannot be converted to `usize`
298    fn remove(&self, trans: &mut Transaction, id: A::IdentifierType)
299        -> StmClosureResult<Option<A>>;
300}