honeycomb_core/cmap/dim3/
structure.rs

1//! Main definitions
2//!
3//! This module contains the main structure definition ([`CMap3`]) as well as its constructor
4//! implementation.
5
6#[cfg(feature = "par-internals")]
7use rayon::prelude::*;
8
9use crate::{
10    attributes::{AttrSparseVec, AttrStorageManager, UnknownAttributeStorage},
11    cmap::{
12        DartIdType, DartReleaseError, DartReservationError,
13        components::{betas::BetaFunctions, unused::UnusedDarts},
14    },
15    geometry::{CoordsFloat, Vertex3},
16    stm::{StmClosureResult, Transaction, TransactionClosureResult, abort, atomically_with_err},
17};
18
19use super::CMAP3_BETA;
20
21/// Main map object.
22pub struct CMap3<T: CoordsFloat> {
23    /// List of vertices making up the represented mesh
24    pub(super) attributes: AttrStorageManager,
25    /// List of vertices making up the represented mesh
26    pub(super) vertices: AttrSparseVec<Vertex3<T>>,
27    /// List of free darts identifiers, i.e. empty spots
28    /// in the current dart list
29    pub(super) unused_darts: UnusedDarts,
30    /// Array representation of the beta functions
31    pub(super) betas: BetaFunctions<CMAP3_BETA>,
32}
33
34unsafe impl<T: CoordsFloat> Send for CMap3<T> {}
35unsafe impl<T: CoordsFloat> Sync for CMap3<T> {}
36#[doc(hidden)]
37/// # 3D combinatorial map implementation
38///
39/// Information regarding maps can be found in the [user guide][UG].
40/// This documentation focuses on the implementation and its API.
41///
42/// [UG]: https://lihpc-computational-geometry.github.io/honeycomb/user-guide/definitions/cmaps
43///
44/// Notes on implementation:
45/// - We encode *β<sub>0</sub>* as the inverse function of *β<sub>1</sub>*. This is extremely
46///   useful (read *required*) to implement correct and efficient i-cell computation. Additionally,
47///   while *β<sub>0</sub>* can be accessed using the [`beta`][Self::beta] method, we do not define
48///   the 0-sew / 0-unsew operations.
49/// - We chose a boundary-less representation of meshes (i.e. darts on the boundary are 3-free).
50/// - The null dart will always be encoded as `0`.
51///
52/// ## Generics
53///
54/// - `T: CoordsFloat` -- Generic FP type for coordinates representation
55///
56/// ## Example
57///
58/// The following example corresponds to this flow of operations:
59///
60/// - Building a tetrahedron (A)
61/// - Building another tetrahedron (B)
62/// - Sewing both tetrahedrons along a face (C)
63/// - Adjusting shared vertices (D)
64/// - Separating and removing the shared face (E)
65///
66/// ```rust
67/// # fn main() {
68/// // TODO: complete with test example once the structure is integrated to the builder
69/// # }
70/// ```
71///
72/// Note that:
73/// - We use the builder structure: [`CMapBuilder`][crate::prelude::CMapBuilder]
74/// - We insert a few assertions to demonstrate the progressive changes applied to the structure
75/// - Even though volumes are represented in the figure, they are not stored in the structure
76/// - We use a lot of methods with the `force_` prefix; these are convenience methods when
77///   synchronization isn't needed
78impl<T: CoordsFloat> CMap3<T> {
79    /// Creates a new 3D combinatorial map.
80    #[allow(unused)]
81    #[must_use = "unused return value"]
82    pub(crate) fn new(n_darts: usize) -> Self {
83        Self {
84            attributes: AttrStorageManager::default(),
85            vertices: AttrSparseVec::new(n_darts + 1),
86            unused_darts: UnusedDarts::new(n_darts + 1),
87            betas: BetaFunctions::new(n_darts + 1),
88        }
89    }
90
91    /// Creates a new 3D combinatorial map with user-defined attributes
92    ///
93    /// We expect the passed storages to be defined but empty, i.e. attributes are known,
94    /// but no space has been used/ allocated yet.
95    #[must_use = "unused return value"]
96    pub(crate) fn new_with_undefined_attributes(
97        n_darts: usize,
98        mut attr_storage_manager: AttrStorageManager,
99    ) -> Self {
100        // extend all storages to the expected length: n_darts + 1 (for the null dart)
101        attr_storage_manager.extend_storages(n_darts + 1);
102        Self {
103            attributes: attr_storage_manager,
104            vertices: AttrSparseVec::new(n_darts + 1),
105            unused_darts: UnusedDarts::new(n_darts + 1),
106            betas: BetaFunctions::new(n_darts + 1),
107        }
108    }
109}
110
111/// **Dart-related methods**
112impl<T: CoordsFloat> CMap3<T> {
113    // --- read
114
115    /// Return the current number of darts.
116    #[must_use = "unused return value"]
117    pub fn n_darts(&self) -> usize {
118        self.unused_darts.len()
119    }
120
121    #[cfg(not(feature = "par-internals"))]
122    /// Return the current number of unused darts.
123    #[must_use = "unused return value"]
124    pub fn n_unused_darts(&self) -> usize {
125        self.unused_darts.iter().filter(|v| v.read_atomic()).count()
126    }
127
128    #[cfg(feature = "par-internals")]
129    /// Return the current number of unused darts.
130    #[must_use = "unused return value"]
131    pub fn n_unused_darts(&self) -> usize {
132        self.unused_darts
133            .par_iter()
134            .filter(|v| v.read_atomic())
135            .count()
136    }
137
138    /// Return whether a given dart is unused or not.
139    ///
140    /// # Errors
141    ///
142    /// This method is meant to be called in a context where the returned `Result` is used to
143    /// validate the transaction passed as argument. Errors should not be processed manually,
144    /// only processed via the `?` operator.
145    #[must_use = "unused return value"]
146    pub fn is_unused_tx(&self, t: &mut Transaction, d: DartIdType) -> StmClosureResult<bool> {
147        self.unused_darts[d].read(t)
148    }
149
150    // --- edit
151
152    /// Add `n_darts` new free darts to the map.
153    fn allocate_darts_core(&mut self, n_darts: usize, unused: bool) -> DartIdType {
154        let new_id = self.n_darts() as DartIdType;
155        self.betas.extend(n_darts);
156        self.unused_darts.extend_with(n_darts, unused);
157        self.vertices.extend(n_darts);
158        self.attributes.extend_storages(n_darts);
159        new_id
160    }
161
162    /// Add `n_darts` new free darts to the map.
163    ///
164    /// Added darts are marked as used.
165    ///
166    /// # Return
167    ///
168    /// Return the ID of the first new dart. Other IDs are in the range `ID..ID+n_darts`.
169    pub fn allocate_used_darts(&mut self, n_darts: usize) -> DartIdType {
170        self.allocate_darts_core(n_darts, false)
171    }
172
173    /// Add `n_darts` new free darts to the map.
174    ///
175    /// Added dart are marked as unused.
176    ///
177    /// # Return
178    ///
179    /// Return the ID of the first new dart. Other IDs are in the range `ID..ID+n_darts`.
180    pub fn allocate_unused_darts(&mut self, n_darts: usize) -> DartIdType {
181        self.allocate_darts_core(n_darts, true)
182    }
183
184    // --- reservation / removal
185
186    #[allow(clippy::missing_errors_doc)]
187    /// Mark `n_darts` free darts as used and return them for usage.
188    ///
189    /// # Return / Errors
190    ///
191    /// This function returns a vector containing IDs of the darts marked as used. It will fail if
192    /// there are not enough unused darts to return; darts will then be left as unused.
193    pub fn reserve_darts(&self, n_darts: usize) -> Result<Vec<DartIdType>, DartReservationError> {
194        atomically_with_err(|t| self.reserve_darts_tx(t, n_darts))
195    }
196
197    #[allow(clippy::missing_errors_doc)]
198    /// Mark `n_darts` free darts as used and return them for usage.
199    ///
200    /// # Return / Errors
201    ///
202    /// This function returns a vector containing IDs of the darts marked as used. It will fail if
203    /// there are not enough unused darts to return; darts will then be left as unused.
204    ///
205    /// This method is meant to be called in a context where the returned `Result` is used to
206    /// validate the transaction passed as argument. Errors should not be processed manually,
207    /// only processed via the `?` operator.
208    pub fn reserve_darts_tx(
209        &self,
210        t: &mut Transaction,
211        n_darts: usize,
212    ) -> TransactionClosureResult<Vec<DartIdType>, DartReservationError> {
213        let mut res = Vec::with_capacity(n_darts);
214
215        for d in 1..self.n_darts() as DartIdType {
216            if self.is_unused_tx(t, d)? {
217                self.claim_dart_tx(t, d)?;
218                res.push(d);
219                if res.len() == n_darts {
220                    return Ok(res);
221                }
222            }
223        }
224
225        abort(DartReservationError(n_darts))
226    }
227
228    #[allow(clippy::missing_errors_doc)]
229    /// Mark `n_darts` free darts as used and return them for usage.
230    ///
231    /// While `reserve_darts_tx` search for free darts from dart 1, this function takes as argument
232    /// a dart ID which serve as the starting point of the search. This is useful in parallel
233    /// contexts; multiple threads may use different offsets to reserve darts without competing
234    /// repeatedly to claim the same elements.
235    ///
236    /// # Return / Errors
237    ///
238    /// This function returns a vector containing IDs of the darts marked as used. It will fail if
239    /// there are not enough unused darts to return; darts will then be left as unused.
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    pub fn reserve_darts_from_tx(
245        &self,
246        t: &mut Transaction,
247        n_darts: usize,
248        from: DartIdType,
249    ) -> TransactionClosureResult<Vec<DartIdType>, DartReservationError> {
250        let mut res = Vec::with_capacity(n_darts);
251
252        for d in (from..self.n_darts() as DartIdType).chain(1..from) {
253            if self.is_unused_tx(t, d)? {
254                self.claim_dart_tx(t, d)?;
255                res.push(d);
256                if res.len() == n_darts {
257                    return Ok(res);
258                }
259            }
260        }
261
262        abort(DartReservationError(n_darts))
263    }
264
265    /// Set a given dart as used.
266    ///
267    /// # Errors
268    ///
269    /// This method is meant to be called in a context where the returned `Result` is used to
270    /// validate the transaction passed as argument. Errors should not be processed manually,
271    /// only processed via the `?` operator.
272    pub fn claim_dart_tx(&self, t: &mut Transaction, dart_id: DartIdType) -> StmClosureResult<()> {
273        self.unused_darts[dart_id].write(t, false)
274    }
275
276    #[allow(clippy::missing_errors_doc)]
277    /// Mark a free dart from the map as unused.
278    ///
279    /// # Return / Errors
280    ///
281    /// This method return a boolean indicating whether the art was already unused or not. It will
282    /// fail if the dart is not free, i.e. if one of its beta images isn't null.
283    pub fn release_dart(&self, dart_id: DartIdType) -> Result<bool, DartReleaseError> {
284        atomically_with_err(|t| self.release_dart_tx(t, dart_id))
285    }
286
287    #[allow(clippy::missing_errors_doc)]
288    /// Mark a free dart from the map as unused.
289    ///
290    /// # Return / Errors
291    ///
292    /// This method return a boolean indicating whether the art was already unused or not. It will
293    /// fail if the dart is not free, i.e. if one of its beta images isn't null.
294    ///
295    /// This method is meant to be called in a context where the returned `Result` is used to
296    /// validate the transaction passed as argument. Errors should not be processed manually,
297    /// only processed via the `?` operator.
298    pub fn release_dart_tx(
299        &self,
300        t: &mut Transaction,
301        dart_id: DartIdType,
302    ) -> TransactionClosureResult<bool, DartReleaseError> {
303        if !self.is_free_tx(t, dart_id)? {
304            abort(DartReleaseError(dart_id))?;
305        }
306        self.attributes.clear_attribute_values(t, dart_id)?;
307        self.vertices.clear_slot(t, dart_id)?;
308        Ok(self.unused_darts[dart_id].replace(t, true)?) // Ok(_?) necessary for err type coercion
309    }
310}