Skip to main content

trellis_core/
graph.rs

1use crate::{
2    AuditState, DependencyList, DerivedNode, GraphError, GraphResult, InputNode, NodeHandle,
3    NodeId, NodeKind, NodeMeta, OutputKey, OutputMeta, ResourceKey, Revision, ScopeId, ScopeMeta,
4    Transaction, TransactionId, TransactionOptions,
5    collection::{CollectionSpec, StoredCollection, StoredDiff},
6    derive::DerivedSpec,
7    input::{StoredInput, value_type},
8    output::OutputSpec,
9    output_payload::StoredOutput,
10    resource::ResourcePlanner,
11    topology::TopologyCache,
12};
13use std::collections::{BTreeMap, BTreeSet};
14
15/// Trellis graph skeleton with transactional input mutation.
16pub struct Graph<C = ()> {
17    pub(crate) next_node_id: u64,
18    pub(crate) next_scope_id: u64,
19    pub(crate) next_output_key: u64,
20    pub(crate) next_transaction_id: TransactionId,
21    pub(crate) revision: Revision,
22    pub(crate) nodes: BTreeMap<NodeId, NodeMeta>,
23    pub(crate) scopes: BTreeMap<ScopeId, ScopeMeta>,
24    pub(crate) scope_children: BTreeMap<ScopeId, BTreeSet<ScopeId>>,
25    pub(crate) input_values: BTreeMap<NodeId, Box<dyn StoredInput>>,
26    pub(crate) derived_specs: BTreeMap<NodeId, DerivedSpec<C>>,
27    pub(crate) derived_values: BTreeMap<NodeId, Box<dyn StoredInput>>,
28    pub(crate) collection_specs: BTreeMap<NodeId, CollectionSpec<C>>,
29    pub(crate) collection_values: BTreeMap<NodeId, Box<dyn StoredCollection>>,
30    pub(crate) previous_collection_values: BTreeMap<NodeId, Box<dyn StoredCollection>>,
31    pub(crate) collection_diffs: BTreeMap<NodeId, Box<dyn StoredDiff>>,
32    pub(crate) resource_planners: Vec<ResourcePlanner<C>>,
33    pub(crate) resource_owners: BTreeMap<ResourceKey, BTreeSet<ScopeId>>,
34    pub(crate) resource_payloads: BTreeMap<ResourceKey, C>,
35    pub(crate) resource_acquisitions: BTreeMap<(ScopeId, ResourceKey), u64>,
36    pub(crate) next_resource_acquisition: u64,
37    pub(crate) output_specs: BTreeMap<OutputKey, OutputSpec<C>>,
38    pub(crate) output_values: BTreeMap<OutputKey, Box<dyn StoredOutput>>,
39    pub(crate) outputs: BTreeMap<OutputKey, OutputMeta>,
40    pub(crate) topology_cache: TopologyCache,
41    pub(crate) audit: AuditState,
42    pub(crate) transaction_open: bool,
43}
44
45impl<C> Graph<C> {
46    /// Creates an empty graph.
47    pub fn new_with_command_type() -> Self {
48        Self {
49            next_node_id: 1,
50            next_scope_id: 1,
51            next_output_key: 1,
52            next_transaction_id: TransactionId::default(),
53            revision: Revision::default(),
54            nodes: BTreeMap::new(),
55            scopes: BTreeMap::new(),
56            scope_children: BTreeMap::new(),
57            input_values: BTreeMap::new(),
58            derived_specs: BTreeMap::new(),
59            derived_values: BTreeMap::new(),
60            collection_specs: BTreeMap::new(),
61            collection_values: BTreeMap::new(),
62            previous_collection_values: BTreeMap::new(),
63            collection_diffs: BTreeMap::new(),
64            resource_planners: Vec::new(),
65            resource_owners: BTreeMap::new(),
66            resource_payloads: BTreeMap::new(),
67            resource_acquisitions: BTreeMap::new(),
68            next_resource_acquisition: 1,
69            output_specs: BTreeMap::new(),
70            output_values: BTreeMap::new(),
71            outputs: BTreeMap::new(),
72            topology_cache: TopologyCache::default(),
73            audit: AuditState::default(),
74            transaction_open: false,
75        }
76    }
77
78    /// Returns the graph revision.
79    pub fn revision(&self) -> Revision {
80        self.revision
81    }
82
83    /// Begins an input transaction with default options.
84    pub fn begin_transaction(&mut self) -> GraphResult<Transaction<'_, C>>
85    where
86        C: Clone,
87    {
88        self.begin_transaction_with_options(TransactionOptions::default())
89    }
90
91    /// Begins an input transaction with explicit options.
92    pub fn begin_transaction_with_options(
93        &mut self,
94        options: TransactionOptions,
95    ) -> GraphResult<Transaction<'_, C>>
96    where
97        C: Clone,
98    {
99        if self.transaction_open {
100            return Err(GraphError::NestedTransaction);
101        }
102
103        self.transaction_open = true;
104        let id = self.allocate_transaction_id();
105        Ok(Transaction::new(self, id, options))
106    }
107
108    pub(crate) fn create_scope_with_parent_direct(
109        &mut self,
110        id: ScopeId,
111        debug_name: impl Into<String>,
112        parent: Option<ScopeId>,
113    ) -> GraphResult<ScopeId> {
114        if let Some(parent) = parent {
115            let parent_meta = self.require_scope(parent)?;
116            if parent_meta.is_closed() {
117                return Err(GraphError::ScopeAlreadyClosed(parent));
118            }
119        }
120
121        self.scopes
122            .insert(id, ScopeMeta::new(id, debug_name, parent));
123        if let Some(parent) = parent {
124            self.scope_children.entry(parent).or_default().insert(id);
125        }
126        Ok(id)
127    }
128
129    pub(crate) fn input_direct<T>(
130        &mut self,
131        id: NodeId,
132        debug_name: impl Into<String>,
133    ) -> GraphResult<InputNode<T>>
134    where
135        T: Clone + PartialEq + Send + Sync + 'static,
136    {
137        let meta = NodeMeta::new(
138            id,
139            NodeKind::Input,
140            debug_name,
141            DependencyList::empty(),
142            self.revision,
143            Some(value_type::<T>()),
144        );
145        self.invalidate_topology_cache();
146        self.nodes.insert(id, meta);
147        Ok(InputNode::new(id))
148    }
149
150    pub(crate) fn derived_direct<T>(
151        &mut self,
152        id: NodeId,
153        debug_name: impl Into<String>,
154        dependencies: DependencyList,
155        derive: impl for<'ctx> Fn(&crate::DeriveContext<'ctx, C>) -> Result<T, crate::DeriveError>
156        + Send
157        + Sync
158        + 'static,
159    ) -> GraphResult<DerivedNode<T>>
160    where
161        T: Clone + PartialEq + Send + Sync + 'static,
162    {
163        self.validate_dependencies(id, &dependencies)?;
164        self.reject_collection_dependencies(&dependencies)?;
165        let meta = NodeMeta::new(
166            id,
167            NodeKind::Derived,
168            debug_name,
169            dependencies,
170            self.revision,
171            Some(value_type::<T>()),
172        );
173        self.invalidate_topology_cache();
174        self.nodes.insert(id, meta);
175        self.derived_specs.insert(id, DerivedSpec::<C>::new(derive));
176        Ok(DerivedNode::new(id))
177    }
178
179    pub(crate) fn attach_node_to_scope_direct(
180        &mut self,
181        node_id: NodeId,
182        scope: ScopeId,
183    ) -> GraphResult<()> {
184        let scope_meta = self.require_scope(scope)?;
185        if scope_meta.is_closed() {
186            return Err(GraphError::ScopeAlreadyClosed(scope));
187        }
188
189        let node_meta = self
190            .nodes
191            .get_mut(&node_id)
192            .ok_or(GraphError::UnknownNode(node_id))?;
193
194        if node_meta.owning_scope().is_some() {
195            return Err(GraphError::NodeAlreadyAttached(node_id));
196        }
197
198        node_meta.attach_scope(scope);
199        Ok(())
200    }
201
202    /// Returns metadata for a node.
203    pub fn node_meta<H: NodeHandle>(&self, node: H) -> Option<&NodeMeta> {
204        self.nodes.get(&node.id())
205    }
206
207    /// Returns metadata for a node id.
208    pub fn node_meta_by_id(&self, id: NodeId) -> Option<&NodeMeta> {
209        self.nodes.get(&id)
210    }
211
212    /// Returns metadata for a scope.
213    pub fn scope_meta(&self, id: ScopeId) -> Option<&ScopeMeta> {
214        self.scopes.get(&id)
215    }
216
217    /// Returns metadata for a materialized output.
218    pub fn output_meta(&self, key: OutputKey) -> Option<&OutputMeta> {
219        self.outputs.get(&key)
220    }
221
222    /// Returns declared dependencies for a node.
223    pub fn dependencies<H: NodeHandle>(&self, node: H) -> Option<&DependencyList> {
224        self.node_meta(node).map(NodeMeta::dependencies)
225    }
226
227    /// Returns all node metadata in stable id order.
228    pub fn nodes(&self) -> impl Iterator<Item = &NodeMeta> {
229        self.nodes.values()
230    }
231
232    /// Returns all scope metadata in stable id order.
233    pub fn scopes(&self) -> impl Iterator<Item = &ScopeMeta> {
234        self.scopes.values()
235    }
236
237    pub(crate) fn allocate_node_id(&mut self) -> NodeId {
238        let id = NodeId::from_index(self.next_node_id);
239        self.next_node_id += 1;
240        id
241    }
242
243    pub(crate) fn allocate_scope_id(&mut self) -> ScopeId {
244        let id = ScopeId::from_index(self.next_scope_id);
245        self.next_scope_id += 1;
246        id
247    }
248
249    pub(crate) fn allocate_output_key(&mut self) -> OutputKey {
250        let key = OutputKey::from_index(self.next_output_key);
251        self.next_output_key += 1;
252        key
253    }
254
255    fn allocate_transaction_id(&mut self) -> TransactionId {
256        self.next_transaction_id = self.next_transaction_id.next();
257        self.next_transaction_id
258    }
259
260    pub(crate) fn require_scope(&self, id: ScopeId) -> GraphResult<&ScopeMeta> {
261        self.scopes.get(&id).ok_or(GraphError::UnknownScope(id))
262    }
263}