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}