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)]
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}