ifc_model/traverse/budget.rs
1//! Limits that keep a walk over a malformed file finite.
2//!
3//! A cycle in a reference graph is not hypothetical: an `IfcRelAggregates`
4//! whose relating object is also one of its related objects appears in real
5//! exports. An unbounded walk hangs the caller with no diagnostic, so every
6//! traversal here takes a budget and reports why it stopped.
7
8/// Limits applied to a single walk.
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub struct Budget {
11 /// Maximum edges followed from the start before stopping.
12 pub max_depth: usize,
13 /// Maximum distinct entities visited.
14 pub max_nodes: usize,
15}
16
17impl Budget {
18 /// A budget large enough for well-formed building models.
19 ///
20 /// Depth 64 clears the deepest real spatial nesting by a wide margin;
21 /// 1_000_000 nodes bounds memory without truncating a large model.
22 pub const DEFAULT: Self = Self {
23 max_depth: 64,
24 max_nodes: 1_000_000,
25 };
26
27 /// A budget with the given depth and the default node ceiling.
28 #[must_use]
29 pub const fn with_depth(max_depth: usize) -> Self {
30 Self {
31 max_depth,
32 ..Self::DEFAULT
33 }
34 }
35}
36
37impl Default for Budget {
38 fn default() -> Self {
39 Self::DEFAULT
40 }
41}
42
43/// Why a walk stopped.
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45#[non_exhaustive]
46pub enum Stop {
47 /// Every reachable entity was visited.
48 Exhausted,
49 /// The depth limit was hit; results are partial.
50 DepthLimit,
51 /// The node limit was hit; results are partial.
52 NodeLimit,
53}
54
55impl Stop {
56 /// Whether the walk finished rather than being truncated.
57 #[must_use]
58 pub const fn is_complete(self) -> bool {
59 matches!(self, Self::Exhausted)
60 }
61}
62
63/// The outcome of a bounded walk.
64#[derive(Debug, Clone, PartialEq, Eq)]
65pub struct Walk {
66 /// Entities visited, in traversal order, each exactly once.
67 pub visited: Vec<crate::value::EntityId>,
68 /// Why the walk ended.
69 pub stop: Stop,
70 /// Entities that were reached again after being visited.
71 ///
72 /// Non-empty means the graph is cyclic along the followed edges. Reported
73 /// rather than silently skipped: in a spatial tree it is a defect worth
74 /// surfacing to the caller.
75 pub revisited: Vec<crate::value::EntityId>,
76}