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}