axiolid_arrangement/id.rs
1// SPDX-License-Identifier: MPL-2.0
2
3//! Typed arena handles for the planar arrangement.
4//!
5//! Mirrors the convention in `axiolid-topology`: a bare `usize` cannot be
6//! passed where a vertex, half-edge, or face is expected, because the three
7//! are different types. The arrangement's handles are deliberately NOT the
8//! same types as the B-rep's -- a planar face and a B-rep face are different
9//! concepts, and letting one stand in for the other would be a bug the
10//! compiler should catch.
11
12use core::fmt;
13
14macro_rules! arrangement_id {
15 ($name:ident, $label:literal) => {
16 #[doc = concat!("Stable handle into the arrangement's ", $label, " arena.")]
17 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
18 pub struct $name(u32);
19
20 impl $name {
21 pub(crate) fn from_index(index: usize) -> Self {
22 Self(u32::try_from(index).expect("arrangement arena exceeds u32 capacity"))
23 }
24
25 /// Zero-based arena index.
26 #[must_use]
27 pub const fn index(self) -> usize {
28 self.0 as usize
29 }
30 }
31
32 impl fmt::Display for $name {
33 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34 write!(f, concat!($label, "#{}"), self.0)
35 }
36 }
37 };
38}
39
40arrangement_id!(VertexId, "vertex");
41arrangement_id!(HalfEdgeId, "halfedge");
42arrangement_id!(FaceId, "face");
43
44impl FaceId {
45 /// The unbounded face is always arena slot zero, created with the
46 /// arrangement itself. Fixing it by construction means `outer_face()`
47 /// needs no bookkeeping and cannot drift.
48 pub(crate) const fn outer() -> Self {
49 Self(0)
50 }
51}