Skip to main content

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 `` 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    #[must_use = "unused return value"]
140    pub fn is_unused(&self, d: DartIdType) -> bool {
141        self.unused_darts[d].read_atomic()
142    }
143
144    /// Return whether a given dart is unused or not.
145    ///
146    /// # Errors
147    ///
148    /// This method is meant to be called in a context where the returned `Result` is used to
149    /// validate the transaction passed as argument. Errors should not be processed manually,
150    /// only processed via the `?` operator.
151    #[must_use = "unused return value"]
152    pub fn is_unused_tx(&self, t: &mut Transaction, d: DartIdType) -> StmClosureResult<bool> {
153        self.unused_darts[d].read(t)
154    }
155
156    // --- edit
157
158    /// Add `n_darts` new free darts to the map.
159    fn allocate_darts_core(&mut self, n_darts: usize, unused: bool) -> DartIdType {
160        let new_id = self.n_darts() as DartIdType;
161        self.betas.extend(n_darts);
162        self.unused_darts.extend_with(n_darts, unused);
163        self.vertices.extend(n_darts);
164        self.attributes.extend_storages(n_darts);
165        new_id
166    }
167
168    /// Add `n_darts` new free darts to the map.
169    ///
170    /// Added darts are marked as used.
171    ///
172    /// # Return
173    ///
174    /// Return the ID of the first new dart. Other IDs are in the range `ID..ID+n_darts`.
175    pub fn allocate_used_darts(&mut self, n_darts: usize) -> DartIdType {
176        self.allocate_darts_core(n_darts, false)
177    }
178
179    /// Add `n_darts` new free darts to the map.
180    ///
181    /// Added dart are marked as unused.
182    ///
183    /// # Return
184    ///
185    /// Return the ID of the first new dart. Other IDs are in the range `ID..ID+n_darts`.
186    pub fn allocate_unused_darts(&mut self, n_darts: usize) -> DartIdType {
187        self.allocate_darts_core(n_darts, true)
188    }
189
190    // --- reservation / removal
191
192    #[allow(clippy::missing_errors_doc)]
193    /// Mark `n_darts` free darts as used and return them for usage.
194    ///
195    /// # Return / Errors
196    ///
197    /// This function returns a vector containing IDs of the darts marked as used. It will fail if
198    /// there are not enough unused darts to return; darts will then be left as unused.
199    pub fn reserve_darts(&self, n_darts: usize) -> Result<Vec<DartIdType>, DartReservationError> {
200        atomically_with_err(|t| self.reserve_darts_tx(t, n_darts))
201    }
202
203    #[allow(clippy::missing_errors_doc)]
204    /// Mark `n_darts` free darts as used and return them for usage.
205    ///
206    /// # Return / Errors
207    ///
208    /// This function returns a vector containing IDs of the darts marked as used. It will fail if
209    /// there are not enough unused darts to return; darts will then be left as unused.
210    ///
211    /// This method is meant to be called in a context where the returned `Result` is used to
212    /// validate the transaction passed as argument. Errors should not be processed manually,
213    /// only processed via the `?` operator.
214    pub fn reserve_darts_tx(
215        &self,
216        t: &mut Transaction,
217        n_darts: usize,
218    ) -> TransactionClosureResult<Vec<DartIdType>, DartReservationError> {
219        let mut res = Vec::with_capacity(n_darts);
220
221        for d in 1..self.n_darts() as DartIdType {
222            if self.is_unused_tx(t, d)? {
223                self.claim_dart_tx(t, d)?;
224                res.push(d);
225                if res.len() == n_darts {
226                    return Ok(res);
227                }
228            }
229        }
230
231        abort(DartReservationError(n_darts))
232    }
233
234    #[allow(clippy::missing_errors_doc)]
235    /// Mark `n_darts` free darts as used and return them for usage.
236    ///
237    /// While `reserve_darts_tx` search for free darts from dart 1, this function takes as argument
238    /// a dart ID which serve as the starting point of the search. This is useful in parallel
239    /// contexts; multiple threads may use different offsets to reserve darts without competing
240    /// repeatedly to claim the same elements.
241    ///
242    /// # Return / Errors
243    ///
244    /// This function returns a vector containing IDs of the darts marked as used. It will fail if
245    /// there are not enough unused darts to return; darts will then be left as unused.
246    ///
247    /// This method is meant to be called in a context where the returned `Result` is used to
248    /// validate the transaction passed as argument. Errors should not be processed manually,
249    /// only processed via the `?` operator.
250    pub fn reserve_darts_from_tx(
251        &self,
252        t: &mut Transaction,
253        n_darts: usize,
254        from: DartIdType,
255    ) -> TransactionClosureResult<Vec<DartIdType>, DartReservationError> {
256        let mut res = Vec::with_capacity(n_darts);
257
258        for d in (from..self.n_darts() as DartIdType).chain(1..from) {
259            if self.is_unused_tx(t, d)? {
260                self.claim_dart_tx(t, d)?;
261                res.push(d);
262                if res.len() == n_darts {
263                    return Ok(res);
264                }
265            }
266        }
267
268        abort(DartReservationError(n_darts))
269    }
270
271    /// Set a given dart as used.
272    ///
273    /// # Errors
274    ///
275    /// This method is meant to be called in a context where the returned `Result` is used to
276    /// validate the transaction passed as argument. Errors should not be processed manually,
277    /// only processed via the `?` operator.
278    pub fn claim_dart_tx(&self, t: &mut Transaction, dart_id: DartIdType) -> StmClosureResult<()> {
279        self.unused_darts[dart_id].write(t, false)
280    }
281
282    #[allow(clippy::missing_errors_doc)]
283    /// Mark a free dart from the map as unused.
284    ///
285    /// # Return / Errors
286    ///
287    /// This method return a boolean indicating whether the art was already unused or not. It will
288    /// fail if the dart is not free, i.e. if one of its beta images isn't null.
289    pub fn release_dart(&self, dart_id: DartIdType) -> Result<bool, DartReleaseError> {
290        atomically_with_err(|t| self.release_dart_tx(t, dart_id))
291    }
292
293    #[allow(clippy::missing_errors_doc)]
294    /// Mark a free dart from the map as unused.
295    ///
296    /// # Return / Errors
297    ///
298    /// This method return a boolean indicating whether the art was already unused or not. It will
299    /// fail if the dart is not free, i.e. if one of its beta images isn't null.
300    ///
301    /// This method is meant to be called in a context where the returned `Result` is used to
302    /// validate the transaction passed as argument. Errors should not be processed manually,
303    /// only processed via the `?` operator.
304    pub fn release_dart_tx(
305        &self,
306        t: &mut Transaction,
307        dart_id: DartIdType,
308    ) -> TransactionClosureResult<bool, DartReleaseError> {
309        if !self.is_free_tx(t, dart_id)? {
310            abort(DartReleaseError(dart_id))?;
311        }
312        self.attributes.clear_attribute_values(t, dart_id)?;
313        self.vertices.clear_slot(t, dart_id)?;
314        Ok(self.unused_darts[dart_id].exchange(t, true)?) // Ok(_?) necessary for err type coercion
315    }
316}