use crate::{
AuditState, DependencyList, DerivedNode, GraphError, GraphResult, InputNode, NodeHandle,
NodeId, NodeKind, NodeMeta, OutputKey, OutputMeta, Revision, ScopeId, ScopeMeta, Transaction,
TransactionId, TransactionOptions,
collection::{CollectionSpec, StoredCollection, StoredDiff},
derive::DerivedSpec,
input::{StoredInput, value_type},
output::OutputSpec,
resource::{ResourceKey, ResourcePlanner},
};
use std::collections::{BTreeMap, BTreeSet};
pub struct Graph<C = (), O = ()> {
pub(crate) next_node_id: u64,
pub(crate) next_scope_id: u64,
pub(crate) next_output_key: u64,
pub(crate) next_transaction_id: TransactionId,
pub(crate) revision: Revision,
pub(crate) nodes: BTreeMap<NodeId, NodeMeta>,
pub(crate) scopes: BTreeMap<ScopeId, ScopeMeta>,
pub(crate) input_values: BTreeMap<NodeId, Box<dyn StoredInput>>,
pub(crate) derived_specs: BTreeMap<NodeId, DerivedSpec<C, O>>,
pub(crate) derived_values: BTreeMap<NodeId, Box<dyn StoredInput>>,
pub(crate) collection_specs: BTreeMap<NodeId, CollectionSpec<C, O>>,
pub(crate) collection_values: BTreeMap<NodeId, Box<dyn StoredCollection>>,
pub(crate) previous_collection_values: BTreeMap<NodeId, Box<dyn StoredCollection>>,
pub(crate) collection_diffs: BTreeMap<NodeId, Box<dyn StoredDiff>>,
pub(crate) resource_planners: Vec<ResourcePlanner<C, O>>,
pub(crate) resource_owners: BTreeMap<ResourceKey, BTreeSet<ScopeId>>,
pub(crate) output_specs: BTreeMap<OutputKey, OutputSpec<C, O>>,
pub(crate) output_values: BTreeMap<OutputKey, O>,
pub(crate) outputs: BTreeMap<OutputKey, OutputMeta>,
pub(crate) audit: AuditState,
pub(crate) transaction_open: bool,
}
impl<C, O> Graph<C, O> {
pub fn new_with_command_type() -> Self {
Self {
next_node_id: 1,
next_scope_id: 1,
next_output_key: 1,
next_transaction_id: TransactionId::default(),
revision: Revision::default(),
nodes: BTreeMap::new(),
scopes: BTreeMap::new(),
input_values: BTreeMap::new(),
derived_specs: BTreeMap::new(),
derived_values: BTreeMap::new(),
collection_specs: BTreeMap::new(),
collection_values: BTreeMap::new(),
previous_collection_values: BTreeMap::new(),
collection_diffs: BTreeMap::new(),
resource_planners: Vec::new(),
resource_owners: BTreeMap::new(),
output_specs: BTreeMap::new(),
output_values: BTreeMap::new(),
outputs: BTreeMap::new(),
audit: AuditState::default(),
transaction_open: false,
}
}
pub fn revision(&self) -> Revision {
self.revision
}
pub fn begin_transaction(&mut self) -> GraphResult<Transaction<'_, C, O>>
where
O: Clone + PartialEq,
{
self.begin_transaction_with_options(TransactionOptions::default())
}
pub fn begin_transaction_with_options(
&mut self,
options: TransactionOptions,
) -> GraphResult<Transaction<'_, C, O>>
where
O: Clone + PartialEq,
{
if self.transaction_open {
return Err(GraphError::NestedTransaction);
}
self.transaction_open = true;
let id = self.allocate_transaction_id();
Ok(Transaction::new(self, id, options))
}
pub(crate) fn create_scope_with_parent_direct(
&mut self,
id: ScopeId,
debug_name: impl Into<String>,
parent: Option<ScopeId>,
) -> GraphResult<ScopeId> {
if let Some(parent) = parent {
let parent_meta = self.require_scope(parent)?;
if parent_meta.is_closed() {
return Err(GraphError::ScopeAlreadyClosed(parent));
}
}
self.scopes
.insert(id, ScopeMeta::new(id, debug_name, parent));
Ok(id)
}
pub(crate) fn input_direct<T>(
&mut self,
id: NodeId,
debug_name: impl Into<String>,
) -> GraphResult<InputNode<T>>
where
T: Clone + PartialEq + Send + Sync + 'static,
{
let meta = NodeMeta::new(
id,
NodeKind::Input,
debug_name,
DependencyList::empty(),
self.revision,
Some(value_type::<T>()),
);
self.nodes.insert(id, meta);
Ok(InputNode::new(id))
}
pub(crate) fn derived_direct<T>(
&mut self,
id: NodeId,
debug_name: impl Into<String>,
dependencies: DependencyList,
derive: impl for<'ctx> Fn(&crate::DeriveContext<'ctx, C, O>) -> Result<T, crate::DeriveError>
+ Send
+ Sync
+ 'static,
) -> GraphResult<DerivedNode<T>>
where
T: Clone + PartialEq + Send + Sync + 'static,
{
self.validate_dependencies(id, &dependencies)?;
self.reject_collection_dependencies(&dependencies)?;
let meta = NodeMeta::new(
id,
NodeKind::Derived,
debug_name,
dependencies,
self.revision,
Some(value_type::<T>()),
);
self.nodes.insert(id, meta);
self.derived_specs
.insert(id, DerivedSpec::<C, O>::new(derive));
Ok(DerivedNode::new(id))
}
pub(crate) fn attach_node_to_scope_direct(
&mut self,
node_id: NodeId,
scope: ScopeId,
) -> GraphResult<()> {
let scope_meta = self.require_scope(scope)?;
if scope_meta.is_closed() {
return Err(GraphError::ScopeAlreadyClosed(scope));
}
let node_meta = self
.nodes
.get_mut(&node_id)
.ok_or(GraphError::UnknownNode(node_id))?;
if node_meta.owning_scope().is_some() {
return Err(GraphError::NodeAlreadyAttached(node_id));
}
node_meta.attach_scope(scope);
Ok(())
}
pub fn node_meta<H: NodeHandle>(&self, node: H) -> Option<&NodeMeta> {
self.nodes.get(&node.id())
}
pub fn node_meta_by_id(&self, id: NodeId) -> Option<&NodeMeta> {
self.nodes.get(&id)
}
pub fn scope_meta(&self, id: ScopeId) -> Option<&ScopeMeta> {
self.scopes.get(&id)
}
pub fn output_meta(&self, key: OutputKey) -> Option<&OutputMeta> {
self.outputs.get(&key)
}
pub fn dependencies<H: NodeHandle>(&self, node: H) -> Option<&DependencyList> {
self.node_meta(node).map(NodeMeta::dependencies)
}
pub fn nodes(&self) -> impl Iterator<Item = &NodeMeta> {
self.nodes.values()
}
pub fn scopes(&self) -> impl Iterator<Item = &ScopeMeta> {
self.scopes.values()
}
pub(crate) fn allocate_node_id(&mut self) -> NodeId {
let id = NodeId::from_index(self.next_node_id);
self.next_node_id += 1;
id
}
pub(crate) fn allocate_scope_id(&mut self) -> ScopeId {
let id = ScopeId::from_index(self.next_scope_id);
self.next_scope_id += 1;
id
}
pub(crate) fn allocate_output_key(&mut self) -> OutputKey {
let key = OutputKey::from_index(self.next_output_key);
self.next_output_key += 1;
key
}
fn allocate_transaction_id(&mut self) -> TransactionId {
self.next_transaction_id = self.next_transaction_id.next();
self.next_transaction_id
}
pub(crate) fn require_scope(&self, id: ScopeId) -> GraphResult<&ScopeMeta> {
self.scopes.get(&id).ok_or(GraphError::UnknownScope(id))
}
pub(crate) fn validate_dependencies(
&self,
node_id: NodeId,
dependencies: &DependencyList,
) -> GraphResult<()> {
for dependency in dependencies.as_slice() {
if *dependency == node_id {
return Err(GraphError::SelfDependency(node_id));
}
if !self.nodes.contains_key(dependency) {
return Err(GraphError::UnknownNode(*dependency));
}
if self.depends_on(*dependency, node_id) {
return Err(GraphError::CycleDetected(node_id));
}
}
Ok(())
}
pub(crate) fn validate_output_dependencies(
&self,
dependencies: &DependencyList,
) -> GraphResult<()> {
for dependency in dependencies.as_slice() {
if !self.nodes.contains_key(dependency) {
return Err(GraphError::UnknownNode(*dependency));
}
}
Ok(())
}
fn reject_collection_dependencies(&self, dependencies: &DependencyList) -> GraphResult<()> {
for dependency in dependencies.as_slice() {
if self
.nodes
.get(dependency)
.is_some_and(|meta| meta.kind() == NodeKind::Collection)
{
return Err(GraphError::CollectionDependencyNotAllowed(*dependency));
}
}
Ok(())
}
fn depends_on(&self, start: NodeId, target: NodeId) -> bool {
let Some(meta) = self.nodes.get(&start) else {
return false;
};
meta.dependencies()
.as_slice()
.iter()
.any(|dependency| *dependency == target || self.depends_on(*dependency, target))
}
}