Path: blob/main/crates/bevy_ecs/src/schedule/graph/tarjan_scc.rs
6849 views
use alloc::vec::Vec;1use core::{hash::BuildHasher, num::NonZeroUsize};23use smallvec::SmallVec;45use crate::schedule::graph::{DiGraph, GraphNodeId};67/// Create an iterator over *strongly connected components* using Algorithm 3 in8/// [A Space-Efficient Algorithm for Finding Strongly Connected Components][1] by David J. Pierce,9/// which is a memory-efficient variation of [Tarjan's algorithm][2].10///11///12/// [1]: https://homepages.ecs.vuw.ac.nz/~djp/files/P05.pdf13/// [2]: https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm14///15/// Returns each strongly strongly connected component (scc).16/// The order of node ids within each scc is arbitrary, but the order of17/// the sccs is their postorder (reverse topological sort).18pub(crate) fn new_tarjan_scc<N: GraphNodeId, S: BuildHasher>(19graph: &DiGraph<N, S>,20) -> impl Iterator<Item = SmallVec<[N; 4]>> + '_ {21// Create a list of all nodes we need to visit.22let unchecked_nodes = graph.nodes();2324// For each node we need to visit, we also need to visit its neighbors.25// Storing the iterator for each set of neighbors allows this list to be computed without26// an additional allocation.27let nodes = graph28.nodes()29.map(|node| NodeData {30root_index: None,31neighbors: graph.neighbors(node),32})33.collect::<Vec<_>>();3435TarjanScc {36graph,37unchecked_nodes,38index: 1, // Invariant: index < component_count at all times.39component_count: usize::MAX, // Will hold if component_count is initialized to number of nodes - 1 or higher.40nodes,41stack: Vec::new(),42visitation_stack: Vec::new(),43start: None,44index_adjustment: None,45}46}4748struct NodeData<Neighbors: Iterator<Item: GraphNodeId>> {49root_index: Option<NonZeroUsize>,50neighbors: Neighbors,51}5253/// A state for computing the *strongly connected components* using [Tarjan's algorithm][1].54///55/// This is based on [`TarjanScc`] from [`petgraph`].56///57/// [1]: https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm58/// [`petgraph`]: https://docs.rs/petgraph/0.6.5/petgraph/59/// [`TarjanScc`]: https://docs.rs/petgraph/0.6.5/petgraph/algo/struct.TarjanScc.html60struct TarjanScc<'graph, N, Hasher, AllNodes, Neighbors>61where62N: GraphNodeId,63Hasher: BuildHasher,64AllNodes: Iterator<Item = N>,65Neighbors: Iterator<Item = N>,66{67/// Source of truth [`DiGraph`]68graph: &'graph DiGraph<N, Hasher>,69/// An [`Iterator`] of [`GraphNodeId`]s from the `graph` which may not have been visited yet.70unchecked_nodes: AllNodes,71/// The index of the next SCC72index: usize,73/// A count of potentially remaining SCCs74component_count: usize,75/// Information about each [`GraphNodeId`], including a possible SCC index and an76/// [`Iterator`] of possibly unvisited neighbors.77nodes: Vec<NodeData<Neighbors>>,78/// A stack of [`GraphNodeId`]s where a SCC will be found starting at the top of the stack.79stack: Vec<N>,80/// A stack of [`GraphNodeId`]s which need to be visited to determine which SCC they belong to.81visitation_stack: Vec<(N, bool)>,82/// An index into the `stack` indicating the starting point of a SCC.83start: Option<usize>,84/// An adjustment to the `index` which will be applied once the current SCC is found.85index_adjustment: Option<usize>,86}8788impl<89'graph,90N: GraphNodeId,91S: BuildHasher,92A: Iterator<Item = N>,93Neighbors: Iterator<Item = N>,94> TarjanScc<'graph, N, S, A, Neighbors>95{96/// Compute the next *strongly connected component* using Algorithm 3 in97/// [A Space-Efficient Algorithm for Finding Strongly Connected Components][1] by David J. Pierce,98/// which is a memory-efficient variation of [Tarjan's algorithm][2].99///100///101/// [1]: https://homepages.ecs.vuw.ac.nz/~djp/files/P05.pdf102/// [2]: https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm103///104/// Returns `Some` for each strongly strongly connected component (scc).105/// The order of node ids within each scc is arbitrary, but the order of106/// the sccs is their postorder (reverse topological sort).107fn next_scc(&mut self) -> Option<&[N]> {108// Cleanup from possible previous iteration109if let (Some(start), Some(index_adjustment)) =110(self.start.take(), self.index_adjustment.take())111{112self.stack.truncate(start);113self.index -= index_adjustment; // Backtrack index back to where it was before we ever encountered the component.114self.component_count -= 1;115}116117loop {118// If there are items on the visitation stack, then we haven't finished visiting119// the node at the bottom of the stack yet.120// Must visit all nodes in the stack from top to bottom before visiting the next node.121while let Some((v, v_is_local_root)) = self.visitation_stack.pop() {122// If this visitation finds a complete SCC, return it immediately.123if let Some(start) = self.visit_once(v, v_is_local_root) {124return Some(&self.stack[start..]);125};126}127128// Get the next node to check, otherwise we're done and can return None.129let Some(node) = self.unchecked_nodes.next() else {130break None;131};132133let visited = self.nodes[self.graph.to_index(node)].root_index.is_some();134135// If this node hasn't already been visited (e.g., it was the neighbor of a previously checked node)136// add it to the visitation stack.137if !visited {138self.visitation_stack.push((node, true));139}140}141}142143/// Attempt to find the starting point on the stack for a new SCC without visiting neighbors.144/// If a visitation is required, this will return `None` and mark the required neighbor and the145/// current node as in need of visitation again.146/// If no SCC can be found in the current visitation stack, returns `None`.147fn visit_once(&mut self, v: N, mut v_is_local_root: bool) -> Option<usize> {148let node_v = &mut self.nodes[self.graph.to_index(v)];149150if node_v.root_index.is_none() {151let v_index = self.index;152node_v.root_index = NonZeroUsize::new(v_index);153self.index += 1;154}155156while let Some(w) = self.nodes[self.graph.to_index(v)].neighbors.next() {157// If a neighbor hasn't been visited yet...158if self.nodes[self.graph.to_index(w)].root_index.is_none() {159// Push the current node and the neighbor back onto the visitation stack.160// On the next execution of `visit_once`, the neighbor will be visited.161self.visitation_stack.push((v, v_is_local_root));162self.visitation_stack.push((w, true));163164return None;165}166167if self.nodes[self.graph.to_index(w)].root_index168< self.nodes[self.graph.to_index(v)].root_index169{170self.nodes[self.graph.to_index(v)].root_index =171self.nodes[self.graph.to_index(w)].root_index;172v_is_local_root = false;173}174}175176if !v_is_local_root {177self.stack.push(v); // Stack is filled up when backtracking, unlike in Tarjans original algorithm.178return None;179}180181// Pop the stack and generate an SCC.182let mut index_adjustment = 1;183let c = NonZeroUsize::new(self.component_count);184let nodes = &mut self.nodes;185let start = self186.stack187.iter()188.rposition(|&w| {189if nodes[self.graph.to_index(v)].root_index190> nodes[self.graph.to_index(w)].root_index191{192true193} else {194nodes[self.graph.to_index(w)].root_index = c;195index_adjustment += 1;196false197}198})199.map(|x| x + 1)200.unwrap_or_default();201nodes[self.graph.to_index(v)].root_index = c;202self.stack.push(v); // Pushing the component root to the back right before getting rid of it is somewhat ugly, but it lets it be included in f.203204self.start = Some(start);205self.index_adjustment = Some(index_adjustment);206207Some(start)208}209}210211impl<212'graph,213N: GraphNodeId,214S: BuildHasher,215A: Iterator<Item = N>,216Neighbors: Iterator<Item = N>,217> Iterator for TarjanScc<'graph, N, S, A, Neighbors>218{219// It is expected that the `DiGraph` is sparse, and as such wont have many large SCCs.220// Returning a `SmallVec` allows this iterator to skip allocation in cases where that221// assumption holds.222type Item = SmallVec<[N; 4]>;223224fn next(&mut self) -> Option<Self::Item> {225let next = SmallVec::from_slice(self.next_scc()?);226Some(next)227}228229fn size_hint(&self) -> (usize, Option<usize>) {230// There can be no more than the number of nodes in a graph worth of SCCs231(0, Some(self.nodes.len()))232}233}234235236