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