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}