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