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::{Any, type_name};
7use std::fmt::Debug;
8
9use downcast_rs::{Downcast, impl_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 /// merging 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 /// merging 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 /// Set a value to `None`
147 ///
148 /// # Arguments
149 ///
150 /// - `t: &mut Transaction` -- Transaction used for synchronization.
151 /// - `id: DartIdType` -- ID of the value to clear / set to `None`.
152 ///
153 /// # Errors
154 ///
155 /// This method is meant to be called in a context where the returned `Result` is used to
156 /// validate the transaction passed as argument. Errors should not be processed manually,
157 /// only processed via the `?` operator.
158 fn clear_slot(&self, t: &mut Transaction, id: DartIdType) -> StmClosureResult<()>;
159
160 /// Return the number of stored attributes, i.e. the number of used slots in the storage (not
161 /// its length).
162 #[must_use = "unused return value"]
163 fn n_attributes(&self) -> usize;
164
165 /// Merge attributes to specified index
166 ///
167 /// # Arguments
168 ///
169 /// - `t: &mut Transaction` -- Transaction used for synchronization.
170 /// - `out: DartIdentifier` -- Identifier to associate the result with.
171 /// - `lhs_inp: DartIdentifier` -- Identifier of one attribute value to merge.
172 /// - `rhs_inp: DartIdentifier` -- Identifier of the other attribute value to merge.
173 ///
174 /// # Behavior (pseudo-code)
175 ///
176 /// ```text
177 /// let new_val = match (attributes.remove(lhs_inp), attributes.remove(rhs_inp)) {
178 /// (Some(v1), Some(v2)) => AttributeUpdate::merge(v1, v2),
179 /// (Some(v), None) | (None, Some(v)) => AttributeUpdate::merge_undefined(Some(v)),
180 /// None, None => AttributeUpdate::merge_undefined(None),
181 /// }
182 /// attributes.set(out, new_val);
183 /// ```
184 ///
185 /// # Errors
186 ///
187 /// This method will fail, returning an error, if:
188 /// - the transaction cannot be completed
189 /// - the merge fails (e.g. because one merging value is missing)
190 ///
191 /// The returned error can be used in conjunction with transaction control to avoid any
192 /// modifications in case of failure at attribute level. The user can then choose, through its
193 /// transaction control policy, to retry or abort as he wishes.
194 fn merge(
195 &self,
196 t: &mut Transaction,
197 out: DartIdType,
198 lhs_inp: DartIdType,
199 rhs_inp: DartIdType,
200 ) -> TransactionClosureResult<(), AttributeError>;
201
202 /// Split attribute to specified indices
203 ///
204 /// # Arguments
205 ///
206 /// - `t: &mut Transaction` -- Transaction used for synchronization.
207 /// - `lhs_out: DartIdentifier` -- Identifier to associate the result with.
208 /// - `rhs_out: DartIdentifier` -- Identifier to associate the result with.
209 /// - `inp: DartIdentifier` -- Identifier of the attribute value to split.
210 ///
211 /// # Behavior pseudo-code
212 ///
213 /// ```text
214 /// (val_lhs, val_rhs) = AttributeUpdate::split(attributes.remove(inp).unwrap());
215 /// attributes[lhs_out] = val_lhs;
216 /// attributes[rhs_out] = val_rhs;
217 ///
218 /// ```
219 ///
220 /// # Errors
221 ///
222 /// This method will fail, returning an error, if:
223 /// - the transaction cannot be completed
224 /// - the split fails (e.g. because there is no value to split from)
225 ///
226 /// The returned error can be used in conjunction with transaction control to avoid any
227 /// modifications in case of failure at attribute level. The user can then choose, through its
228 /// transaction control policy, to retry or abort as he wishes.
229 fn split(
230 &self,
231 t: &mut Transaction,
232 lhs_out: DartIdType,
233 rhs_out: DartIdType,
234 inp: DartIdType,
235 ) -> TransactionClosureResult<(), AttributeError>;
236}
237
238impl_downcast!(UnknownAttributeStorage);
239
240/// # Generic attribute storage trait
241///
242/// This trait defines attribute-specific methods. The documentation describes the expected behavior
243/// of each method. "ID" and "index" are used interchangeably.
244pub trait AttributeStorage<A: AttributeBind>: UnknownAttributeStorage {
245 #[allow(clippy::missing_errors_doc)]
246 /// Read the value of an element at a given index.
247 ///
248 /// # Arguments
249 ///
250 /// - `t: &mut Transaction` -- Transaction used for synchronization.
251 /// - `index: A::IdentifierType` -- Cell index.
252 ///
253 /// # Return / Errors
254 ///
255 /// This method is meant to be called in a context where the returned `Result` is used to
256 /// validate the transaction passed as argument. Errors should not be processed manually,
257 /// only processed via the `?` operator.
258 ///
259 /// # Panics
260 ///
261 /// The method:
262 /// - should panic if the index lands out of bounds
263 /// - may panic if the index cannot be converted to `usize`
264 fn read(&self, t: &mut Transaction, id: A::IdentifierType) -> StmClosureResult<Option<A>>;
265
266 #[allow(clippy::missing_errors_doc)]
267 /// Write the value of an element at a given index and return the old value.
268 ///
269 /// # Arguments
270 ///
271 /// - `t: &mut Transaction` -- Transaction used for synchronization.
272 /// - `index: A::IdentifierType` -- Cell index.
273 /// - `val: A` -- Attribute value.
274 ///
275 /// # Return / Errors
276 ///
277 /// This method is meant to be called in a context where the returned `Result` is used to
278 /// validate the transaction passed as argument. Errors should not be processed manually,
279 /// only processed via the `?` operator.
280 ///
281 /// # Panics
282 ///
283 /// The method:
284 /// - should panic if the index lands out of bounds
285 /// - may panic if the index cannot be converted to `usize`
286 fn write(
287 &self,
288 t: &mut Transaction,
289 id: A::IdentifierType,
290 val: A,
291 ) -> StmClosureResult<Option<A>>;
292
293 #[allow(clippy::missing_errors_doc)]
294 /// Remove the value at a given index and return it.
295 ///
296 /// # Arguments
297 ///
298 /// - `t: &mut Transaction` -- Transaction used for synchronization.
299 /// - `index: A::IdentifierType` -- Cell index.
300 ///
301 /// # Return / Errors
302 ///
303 /// This method is meant to be called in a context where the returned `Result` is used to
304 /// validate the transaction passed as argument. Errors should not be processed manually,
305 /// only processed via the `?` operator.
306 ///
307 /// # Panics
308 ///
309 /// The method:
310 /// - should panic if the index lands out of bounds
311 /// - may panic if the index cannot be converted to `usize`
312 fn remove(&self, t: &mut Transaction, id: A::IdentifierType) -> StmClosureResult<Option<A>>;
313}