Path: blob/main/crates/bevy_ecs/src/schedule/graph/mod.rs
6849 views
use alloc::{boxed::Box, vec, vec::Vec};1use core::{2any::{Any, TypeId},3fmt::Debug,4};5use smallvec::SmallVec;67use bevy_platform::collections::{HashMap, HashSet};8use bevy_utils::TypeIdMap;910use fixedbitset::FixedBitSet;1112use crate::schedule::set::*;1314mod graph_map;15mod tarjan_scc;1617pub use graph_map::{DiGraph, Direction, GraphNodeId, UnGraph};1819/// Specifies what kind of edge should be added to the dependency graph.20#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash)]21pub(crate) enum DependencyKind {22/// A node that should be preceded.23Before,24/// A node that should be succeeded.25After,26}2728/// An edge to be added to the dependency graph.29pub(crate) struct Dependency {30pub(crate) kind: DependencyKind,31pub(crate) set: InternedSystemSet,32pub(crate) options: TypeIdMap<Box<dyn Any>>,33}3435impl Dependency {36pub fn new(kind: DependencyKind, set: InternedSystemSet) -> Self {37Self {38kind,39set,40options: Default::default(),41}42}43pub fn add_config<T: 'static>(mut self, option: T) -> Self {44self.options.insert(TypeId::of::<T>(), Box::new(option));45self46}47}4849/// Configures ambiguity detection for a single system.50#[derive(Clone, Debug, Default)]51pub(crate) enum Ambiguity {52#[default]53Check,54/// Ignore warnings with systems in any of these system sets. May contain duplicates.55IgnoreWithSet(Vec<InternedSystemSet>),56/// Ignore all warnings.57IgnoreAll,58}5960/// Metadata about how the node fits in the schedule graph61#[derive(Default)]62pub struct GraphInfo {63/// the sets that the node belongs to (hierarchy)64pub(crate) hierarchy: Vec<InternedSystemSet>,65/// the sets that the node depends on (must run before or after)66pub(crate) dependencies: Vec<Dependency>,67pub(crate) ambiguous_with: Ambiguity,68}6970/// Converts 2D row-major pair of indices into a 1D array index.71pub(crate) fn index(row: usize, col: usize, num_cols: usize) -> usize {72debug_assert!(col < num_cols);73(row * num_cols) + col74}7576/// Converts a 1D array index into a 2D row-major pair of indices.77pub(crate) fn row_col(index: usize, num_cols: usize) -> (usize, usize) {78(index / num_cols, index % num_cols)79}8081/// Stores the results of the graph analysis.82pub(crate) struct CheckGraphResults<N: GraphNodeId> {83/// Boolean reachability matrix for the graph.84pub(crate) reachable: FixedBitSet,85/// Pairs of nodes that have a path connecting them.86pub(crate) connected: HashSet<(N, N)>,87/// Pairs of nodes that don't have a path connecting them.88pub(crate) disconnected: Vec<(N, N)>,89/// Edges that are redundant because a longer path exists.90pub(crate) transitive_edges: Vec<(N, N)>,91/// Variant of the graph with no transitive edges.92pub(crate) transitive_reduction: DiGraph<N>,93/// Variant of the graph with all possible transitive edges.94// TODO: this will very likely be used by "if-needed" ordering95#[expect(dead_code, reason = "See the TODO above this attribute.")]96pub(crate) transitive_closure: DiGraph<N>,97}9899impl<N: GraphNodeId> Default for CheckGraphResults<N> {100fn default() -> Self {101Self {102reachable: FixedBitSet::new(),103connected: HashSet::default(),104disconnected: Vec::new(),105transitive_edges: Vec::new(),106transitive_reduction: DiGraph::default(),107transitive_closure: DiGraph::default(),108}109}110}111112/// Processes a DAG and computes its:113/// - transitive reduction (along with the set of removed edges)114/// - transitive closure115/// - reachability matrix (as a bitset)116/// - pairs of nodes connected by a path117/// - pairs of nodes not connected by a path118///119/// The algorithm implemented comes from120/// ["On the calculation of transitive reduction-closure of orders"][1] by Habib, Morvan and Rampon.121///122/// [1]: https://doi.org/10.1016/0012-365X(93)90164-O123pub(crate) fn check_graph<N: GraphNodeId>(124graph: &DiGraph<N>,125topological_order: &[N],126) -> CheckGraphResults<N> {127if graph.node_count() == 0 {128return CheckGraphResults::default();129}130131let n = graph.node_count();132133// build a copy of the graph where the nodes and edges appear in topsorted order134let mut map = <HashMap<_, _>>::with_capacity_and_hasher(n, Default::default());135let mut topsorted = DiGraph::<N>::default();136// iterate nodes in topological order137for (i, &node) in topological_order.iter().enumerate() {138map.insert(node, i);139topsorted.add_node(node);140// insert nodes as successors to their predecessors141for pred in graph.neighbors_directed(node, Direction::Incoming) {142topsorted.add_edge(pred, node);143}144}145146let mut reachable = FixedBitSet::with_capacity(n * n);147let mut connected = <HashSet<_>>::default();148let mut disconnected = Vec::new();149150let mut transitive_edges = Vec::new();151let mut transitive_reduction = DiGraph::default();152let mut transitive_closure = DiGraph::default();153154let mut visited = FixedBitSet::with_capacity(n);155156// iterate nodes in topological order157for node in topsorted.nodes() {158transitive_reduction.add_node(node);159transitive_closure.add_node(node);160}161162// iterate nodes in reverse topological order163for a in topsorted.nodes().rev() {164let index_a = *map.get(&a).unwrap();165// iterate their successors in topological order166for b in topsorted.neighbors_directed(a, Direction::Outgoing) {167let index_b = *map.get(&b).unwrap();168debug_assert!(index_a < index_b);169if !visited[index_b] {170// edge <a, b> is not redundant171transitive_reduction.add_edge(a, b);172transitive_closure.add_edge(a, b);173reachable.insert(index(index_a, index_b, n));174175let successors = transitive_closure176.neighbors_directed(b, Direction::Outgoing)177.collect::<Vec<_>>();178for c in successors {179let index_c = *map.get(&c).unwrap();180debug_assert!(index_b < index_c);181if !visited[index_c] {182visited.insert(index_c);183transitive_closure.add_edge(a, c);184reachable.insert(index(index_a, index_c, n));185}186}187} else {188// edge <a, b> is redundant189transitive_edges.push((a, b));190}191}192193visited.clear();194}195196// partition pairs of nodes into "connected by path" and "not connected by path"197for i in 0..(n - 1) {198// reachable is upper triangular because the nodes were topsorted199for index in index(i, i + 1, n)..=index(i, n - 1, n) {200let (a, b) = row_col(index, n);201let pair = (topological_order[a], topological_order[b]);202if reachable[index] {203connected.insert(pair);204} else {205disconnected.push(pair);206}207}208}209210// fill diagonal (nodes reach themselves)211// for i in 0..n {212// reachable.set(index(i, i, n), true);213// }214215CheckGraphResults {216reachable,217connected,218disconnected,219transitive_edges,220transitive_reduction,221transitive_closure,222}223}224225/// Returns the simple cycles in a strongly-connected component of a directed graph.226///227/// The algorithm implemented comes from228/// ["Finding all the elementary circuits of a directed graph"][1] by D. B. Johnson.229///230/// [1]: https://doi.org/10.1137/0204007231pub fn simple_cycles_in_component<N: GraphNodeId>(graph: &DiGraph<N>, scc: &[N]) -> Vec<Vec<N>> {232let mut cycles = vec![];233let mut sccs = vec![SmallVec::from_slice(scc)];234235while let Some(mut scc) = sccs.pop() {236// only look at nodes and edges in this strongly-connected component237let mut subgraph = DiGraph::<N>::default();238for &node in &scc {239subgraph.add_node(node);240}241242for &node in &scc {243for successor in graph.neighbors(node) {244if subgraph.contains_node(successor) {245subgraph.add_edge(node, successor);246}247}248}249250// path of nodes that may form a cycle251let mut path = Vec::with_capacity(subgraph.node_count());252// we mark nodes as "blocked" to avoid finding permutations of the same cycles253let mut blocked: HashSet<_> =254HashSet::with_capacity_and_hasher(subgraph.node_count(), Default::default());255// connects nodes along path segments that can't be part of a cycle (given current root)256// those nodes can be unblocked at the same time257let mut unblock_together: HashMap<N, HashSet<N>> =258HashMap::with_capacity_and_hasher(subgraph.node_count(), Default::default());259// stack for unblocking nodes260let mut unblock_stack = Vec::with_capacity(subgraph.node_count());261// nodes can be involved in multiple cycles262let mut maybe_in_more_cycles: HashSet<N> =263HashSet::with_capacity_and_hasher(subgraph.node_count(), Default::default());264// stack for DFS265let mut stack = Vec::with_capacity(subgraph.node_count());266267// we're going to look for all cycles that begin and end at this node268let root = scc.pop().unwrap();269// start a path at the root270path.clear();271path.push(root);272// mark this node as blocked273blocked.insert(root);274275// DFS276stack.clear();277stack.push((root, subgraph.neighbors(root)));278while !stack.is_empty() {279let &mut (ref node, ref mut successors) = stack.last_mut().unwrap();280if let Some(next) = successors.next() {281if next == root {282// found a cycle283maybe_in_more_cycles.extend(path.iter());284cycles.push(path.clone());285} else if !blocked.contains(&next) {286// first time seeing `next` on this path287maybe_in_more_cycles.remove(&next);288path.push(next);289blocked.insert(next);290stack.push((next, subgraph.neighbors(next)));291continue;292} else {293// not first time seeing `next` on this path294}295}296297if successors.peekable().peek().is_none() {298if maybe_in_more_cycles.contains(node) {299unblock_stack.push(*node);300// unblock this node's ancestors301while let Some(n) = unblock_stack.pop() {302if blocked.remove(&n) {303let unblock_predecessors = unblock_together.entry(n).or_default();304unblock_stack.extend(unblock_predecessors.iter());305unblock_predecessors.clear();306}307}308} else {309// if its descendants can be unblocked later, this node will be too310for successor in subgraph.neighbors(*node) {311unblock_together.entry(successor).or_default().insert(*node);312}313}314315// remove node from path and DFS stack316path.pop();317stack.pop();318}319}320321drop(stack);322323// remove node from subgraph324subgraph.remove_node(root);325326// divide remainder into smaller SCCs327sccs.extend(subgraph.iter_sccs().filter(|scc| scc.len() > 1));328}329330cycles331}332333334