mod builder;
mod validation;
use crate::edge::{Edge, EdgeId, SequentialEdge};
use crate::node::{Node, NodeId};
pub use builder::SystemNodeBuilder;
use hashbrown::HashSet;
use std::any::TypeId;
use std::time::Duration;
pub use validation::{MergeError, ValidationError, ValidationResult, ValidationWarning};
#[derive(Debug, Default)]
pub struct Graph {
pub(crate) nodes: Vec<Node>,
pub(crate) edges: Vec<Edge>,
pub(crate) entry: Option<NodeId>,
pub(crate) last_node: Option<NodeId>,
pub(crate) max_duration: Option<Duration>,
}
impl Graph {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn nodes(&self) -> &[Node] {
&self.nodes
}
#[must_use]
pub fn edges(&self) -> &[Edge] {
&self.edges
}
#[must_use]
pub fn entry(&self) -> Option<NodeId> {
self.entry.clone()
}
#[must_use]
pub fn node_count(&self) -> usize {
self.nodes.len()
}
#[must_use]
pub fn edge_count(&self) -> usize {
self.edges.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.nodes.is_empty()
}
#[must_use]
pub fn last_node(&self) -> Option<NodeId> {
self.last_node.clone()
}
#[must_use]
pub fn max_duration(&self) -> Option<Duration> {
self.max_duration
}
#[must_use]
pub fn get_node(&self, id: NodeId) -> Option<&Node> {
self.nodes.iter().find(|node| node.id() == id)
}
#[must_use]
pub fn get_edge(&self, id: EdgeId) -> Option<&Edge> {
self.edges.iter().find(|edge| edge.id() == id)
}
pub(crate) fn add_sequential_edge(&mut self, from: NodeId, to: NodeId) {
let edge = Edge::Sequential(SequentialEdge::new(from, to));
self.edges.push(edge);
}
pub(crate) fn check_connectivity(&self, entry: &NodeId) -> Result<(), MergeError> {
let reachable: HashSet<NodeId> =
self.reachable_nodes(entry).iter().map(|n| n.id()).collect();
if reachable.len() == self.node_count() {
return Ok(());
}
let orphans: Vec<NodeId> = self
.nodes
.iter()
.map(Node::id)
.filter(|id| !reachable.contains(id))
.collect();
Err(MergeError::DisconnectedNodes {
orphan_count: orphans.len(),
orphans,
})
}
pub(crate) fn reachable_nodes(&self, entry: &NodeId) -> Vec<&Node> {
let mut visited = HashSet::new();
let mut result = Vec::new();
let mut stack = vec![entry.clone()];
while let Some(current) = stack.pop() {
if !visited.insert(current.clone()) {
continue;
}
let Some(node) = self.get_node(current.clone()) else {
continue;
};
result.push(node);
match node {
Node::Decision(dec) => {
if let Some(t) = &dec.true_branch {
stack.push(t.clone());
}
if let Some(f) = &dec.false_branch {
stack.push(f.clone());
}
}
Node::Switch(sw) => {
for (_, target) in &sw.cases {
stack.push(target.clone());
}
if let Some(d) = &sw.default {
stack.push(d.clone());
}
}
Node::Loop(lp) => {
if let Some(body) = &lp.body_entry {
stack.push(body.clone());
}
}
Node::Parallel(par) => {
for branch in &par.branches {
stack.push(branch.clone());
}
}
Node::System(_) | Node::Scope(_) => {}
}
for edge in &self.edges {
if let Edge::Sequential(seq) = edge
&& seq.from == current
{
stack.push(seq.to.clone());
}
}
}
result
}
pub(crate) fn collect_branch_output_types(
&self,
entry: &NodeId,
) -> Vec<(TypeId, &'static str)> {
self.reachable_nodes(entry)
.into_iter()
.filter_map(|node| match node {
Node::System(sys) => Some((sys.output_type_id(), sys.output_type_name())),
_ => None,
})
.collect()
}
}