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)]
45pub enum Stop {
46 /// Every reachable entity was visited.
47 Exhausted,
48 /// The depth limit was hit; results are partial.
49 DepthLimit,
50 /// The node limit was hit; results are partial.
51 NodeLimit,
52}
53
54impl Stop {
55 /// Whether the walk finished rather than being truncated.
56 #[must_use]
57 pub const fn is_complete(self) -> bool {
58 matches!(self, Self::Exhausted)
59 }
60}
61
62/// The outcome of a bounded walk.
63#[derive(Debug, Clone, PartialEq, Eq)]
64pub struct Walk {
65 /// Entities visited, in traversal order, each exactly once.
66 pub visited: Vec<crate::value::EntityId>,
67 /// Why the walk ended.
68 pub stop: Stop,
69 /// Entities that were reached again after being visited.
70 ///
71 /// Non-empty means the graph is cyclic along the followed edges. Reported
72 /// rather than silently skipped: in a spatial tree it is a defect worth
73 /// surfacing to the caller.
74 pub revisited: Vec<crate::value::EntityId>,
75}