trellis_core/
scope_lifecycle.rs1use crate::{Graph, GraphResult, ResourceKey, ScopeId};
2use std::collections::BTreeSet;
3
4impl<C> Graph<C> {
5 pub(crate) fn close_scope_direct(&mut self, scope: ScopeId) -> GraphResult<Vec<ScopeId>> {
6 self.require_scope(scope)?;
7 let scopes = self.scope_close_order(scope);
8 for closing in &scopes {
9 if let Some(scope_meta) = self.scopes.get_mut(closing) {
10 scope_meta.close();
11 }
12 self.resource_planners
13 .retain(|planner| planner.scope != *closing);
14 }
15 Ok(scopes)
16 }
17
18 pub(crate) fn reclaim_closed_scopes(&mut self, closed_scopes: &[ScopeId]) {
19 let reclaimed_nodes = self.reclaim_closed_scope_nodes(closed_scopes);
20 self.reclaim_closed_scope_metadata(closed_scopes);
21 if !reclaimed_nodes.is_empty() || !closed_scopes.is_empty() {
22 self.invalidate_topology_cache();
23 }
24 }
25
26 pub fn child_scopes(&self, scope: ScopeId) -> GraphResult<Vec<ScopeId>> {
28 self.require_scope(scope)?;
29 Ok(self.child_scopes_unchecked(scope))
30 }
31
32 pub fn orphan_resources(&self) -> Vec<ResourceKey> {
34 self.resource_owners
35 .iter()
36 .filter_map(|(key, owners)| {
37 let has_live_owner = owners
38 .iter()
39 .any(|scope| self.scopes.get(scope).is_some_and(|meta| !meta.is_closed()));
40 (!has_live_owner).then(|| key.clone())
41 })
42 .collect()
43 }
44
45 fn scope_close_order(&self, scope: ScopeId) -> Vec<ScopeId> {
46 let mut scopes = Vec::new();
47 let mut stack = vec![ScopeVisitFrame::Enter(scope)];
48
49 while let Some(frame) = stack.pop() {
50 match frame {
51 ScopeVisitFrame::Exit(scope) => {
52 if self
53 .scopes
54 .get(&scope)
55 .is_some_and(|scope_meta| !scope_meta.is_closed())
56 {
57 scopes.push(scope);
58 }
59 }
60 ScopeVisitFrame::Enter(scope) => {
61 stack.push(ScopeVisitFrame::Exit(scope));
62 let mut children = self.child_scopes_unchecked(scope);
63 children.reverse();
64 for child in children {
65 stack.push(ScopeVisitFrame::Enter(child));
66 }
67 }
68 }
69 }
70
71 scopes
72 }
73
74 fn child_scopes_unchecked(&self, scope: ScopeId) -> Vec<ScopeId> {
75 self.scope_children
76 .get(&scope)
77 .map(|children| children.iter().copied().collect())
78 .unwrap_or_default()
79 }
80
81 fn reclaim_closed_scope_nodes(&mut self, closed_scopes: &[ScopeId]) -> Vec<crate::NodeId> {
82 let closed_scopes = closed_scopes.iter().copied().collect::<BTreeSet<_>>();
83 let nodes = self
84 .nodes
85 .values()
86 .filter_map(|node| {
87 node.owning_scope()
88 .filter(|scope| closed_scopes.contains(scope))
89 .map(|_| node.id())
90 })
91 .collect::<Vec<_>>();
92 for node in &nodes {
93 self.remove_node_storage(*node);
94 }
95 nodes
96 }
97
98 fn remove_node_storage(&mut self, node: crate::NodeId) {
99 self.nodes.remove(&node);
100 self.input_values.remove(&node);
101 self.derived_specs.remove(&node);
102 self.derived_values.remove(&node);
103 self.collection_specs.remove(&node);
104 self.collection_values.remove(&node);
105 self.previous_collection_values.remove(&node);
106 self.collection_diffs.remove(&node);
107 self.resource_planners
108 .retain(|planner| planner.collection != node);
109 self.audit.node_changes.remove(&node);
110 }
111
112 fn reclaim_closed_scope_metadata(&mut self, closed_scopes: &[ScopeId]) {
113 for scope in closed_scopes {
114 if let Some(scope_meta) = self.scopes.remove(scope)
115 && let Some(parent) = scope_meta.parent()
116 {
117 let remove_parent = if let Some(children) = self.scope_children.get_mut(&parent) {
118 children.remove(scope);
119 children.is_empty()
120 } else {
121 false
122 };
123 if remove_parent {
124 self.scope_children.remove(&parent);
125 }
126 }
127 self.scope_children.remove(scope);
128 }
129 }
130}
131
132enum ScopeVisitFrame {
133 Enter(ScopeId),
134 Exit(ScopeId),
135}