Skip to main content

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}