Path: blob/main/crates/bevy_ecs/src/schedule/graph/graph_map.rs
9354 views
//! `Graph<DIRECTED>` is a graph datastructure where node values are mapping1//! keys.2//! Based on the `GraphMap` datastructure from [`petgraph`].3//!4//! [`petgraph`]: https://docs.rs/petgraph/0.6.5/petgraph/56use alloc::{vec, vec::Vec};7use core::{8fmt::{self, Debug},9hash::{BuildHasher, Hash},10};11use thiserror::Error;1213use bevy_platform::{14collections::{HashMap, HashSet},15hash::FixedHasher,16};17use indexmap::IndexMap;18use smallvec::SmallVec;1920use Direction::{Incoming, Outgoing};2122/// Types that can be used as node identifiers in a [`DiGraph`]/[`UnGraph`].23///24/// [`DiGraph`]: crate::schedule::graph::DiGraph25/// [`UnGraph`]: crate::schedule::graph::UnGraph26pub trait GraphNodeId: Copy + Eq + Hash + Ord + Debug {27/// The type that packs and unpacks this [`GraphNodeId`] with a [`Direction`].28/// This is used to save space in the graph's adjacency list.29type Adjacent: Copy + Debug + From<(Self, Direction)> + Into<(Self, Direction)>;30/// The type that packs and unpacks this [`GraphNodeId`] with another31/// [`GraphNodeId`]. This is used to save space in the graph's edge list.32type Edge: Copy + Eq + Hash + Debug + From<(Self, Self)> + Into<(Self, Self)>;3334/// Name of the kind of this node id.35///36/// For structs, this should return a human-readable name of the struct.37/// For enums, this should return a human-readable name of the enum variant.38fn kind(&self) -> &'static str;39}4041/// A `Graph` with undirected edges of some [`GraphNodeId`] `N`.42///43/// For example, an edge between *1* and *2* is equivalent to an edge between44/// *2* and *1*.45pub type UnGraph<N, S = FixedHasher> = Graph<false, N, S>;4647/// A `Graph` with directed edges of some [`GraphNodeId`] `N`.48///49/// For example, an edge from *1* to *2* is distinct from an edge from *2* to50/// *1*.51pub type DiGraph<N, S = FixedHasher> = Graph<true, N, S>;5253/// `Graph<DIRECTED>` is a graph datastructure using an associative array54/// of its node weights of some [`GraphNodeId`].55///56/// It uses a combined adjacency list and sparse adjacency matrix57/// representation, using **O(|N| + |E|)** space, and allows testing for edge58/// existence in constant time.59///60/// `Graph` is parameterized over:61///62/// - Constant generic bool `DIRECTED` determines whether the graph edges are directed or63/// undirected.64/// - The `GraphNodeId` type `N`, which is used as the node weight.65/// - The `BuildHasher` `S`.66///67/// You can use the type aliases `UnGraph` and `DiGraph` for convenience.68///69/// `Graph` does not allow parallel edges, but self loops are allowed.70#[derive(Clone)]71pub struct Graph<const DIRECTED: bool, N: GraphNodeId, S = FixedHasher>72where73S: BuildHasher,74{75nodes: IndexMap<N, Vec<N::Adjacent>, S>,76edges: HashSet<N::Edge, S>,77}7879impl<const DIRECTED: bool, N: GraphNodeId, S: BuildHasher> Debug for Graph<DIRECTED, N, S> {80fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {81self.nodes.fmt(f)82}83}8485impl<const DIRECTED: bool, N: GraphNodeId, S: BuildHasher> Graph<DIRECTED, N, S> {86/// Create a new `Graph` with estimated capacity.87pub fn with_capacity(nodes: usize, edges: usize) -> Self88where89S: Default,90{91Self {92nodes: IndexMap::with_capacity_and_hasher(nodes, S::default()),93edges: HashSet::with_capacity_and_hasher(edges, S::default()),94}95}9697/// Use their natural order to map the node pair (a, b) to a canonical edge id.98#[inline]99fn edge_key(a: N, b: N) -> N::Edge {100let (a, b) = if DIRECTED || a <= b { (a, b) } else { (b, a) };101102N::Edge::from((a, b))103}104105/// Return the number of nodes in the graph.106pub fn node_count(&self) -> usize {107self.nodes.len()108}109110/// Return the number of edges in the graph.111pub fn edge_count(&self) -> usize {112self.edges.len()113}114115/// Add node `n` to the graph.116pub fn add_node(&mut self, n: N) {117self.nodes.entry(n).or_default();118}119120/// Remove a node `n` from the graph.121///122/// Computes in **O(N)** time, due to the removal of edges with other nodes.123pub fn remove_node(&mut self, n: N) {124let Some(links) = self.nodes.swap_remove(&n) else {125return;126};127128let links = links.into_iter().map(N::Adjacent::into);129130for (succ, dir) in links {131let edge = if dir == Outgoing {132Self::edge_key(n, succ)133} else {134Self::edge_key(succ, n)135};136// remove all successor links137self.remove_single_edge(succ, n, dir.opposite());138// Remove all edge values139self.edges.remove(&edge);140}141}142143/// Return `true` if the node is contained in the graph.144pub fn contains_node(&self, n: N) -> bool {145self.nodes.contains_key(&n)146}147148/// Add an edge connecting `a` and `b` to the graph.149/// For a directed graph, the edge is directed from `a` to `b`.150///151/// Inserts nodes `a` and/or `b` if they aren't already part of the graph.152pub fn add_edge(&mut self, a: N, b: N) {153if self.edges.insert(Self::edge_key(a, b)) {154// insert in the adjacency list if it's a new edge155self.nodes156.entry(a)157.or_insert_with(|| Vec::with_capacity(1))158.push(N::Adjacent::from((b, Outgoing)));159if a != b {160// self loops don't have the Incoming entry161self.nodes162.entry(b)163.or_insert_with(|| Vec::with_capacity(1))164.push(N::Adjacent::from((a, Incoming)));165}166}167}168169/// Remove edge relation from a to b.170///171/// Return `true` if it did exist.172fn remove_single_edge(&mut self, a: N, b: N, dir: Direction) -> bool {173let Some(sus) = self.nodes.get_mut(&a) else {174return false;175};176177let Some(index) = sus178.iter()179.copied()180.map(N::Adjacent::into)181.position(|elt| (DIRECTED && elt == (b, dir)) || (!DIRECTED && elt.0 == b))182else {183return false;184};185186sus.swap_remove(index);187true188}189190/// Remove edge from `a` to `b` from the graph.191///192/// Return `false` if the edge didn't exist.193pub fn remove_edge(&mut self, a: N, b: N) -> bool {194let exist1 = self.remove_single_edge(a, b, Outgoing);195let exist2 = if a != b {196self.remove_single_edge(b, a, Incoming)197} else {198exist1199};200let weight = self.edges.remove(&Self::edge_key(a, b));201debug_assert!(exist1 == exist2 && exist1 == weight);202weight203}204205/// Return `true` if the edge connecting `a` with `b` is contained in the graph.206pub fn contains_edge(&self, a: N, b: N) -> bool {207self.edges.contains(&Self::edge_key(a, b))208}209210/// Reserve capacity for at least `additional` more nodes to be inserted211/// in the graph.212pub fn reserve_nodes(&mut self, additional: usize) {213self.nodes.reserve(additional);214}215216/// Reserve capacity for at least `additional` more edges to be inserted217/// in the graph.218pub fn reserve_edges(&mut self, additional: usize) {219self.edges.reserve(additional);220}221222/// Return an iterator over the nodes of the graph.223pub fn nodes(&self) -> impl DoubleEndedIterator<Item = N> + ExactSizeIterator<Item = N> + '_ {224self.nodes.keys().copied()225}226227/// Return an iterator of all nodes with an edge starting from `a`.228pub fn neighbors(&self, a: N) -> impl DoubleEndedIterator<Item = N> + '_ {229let iter = match self.nodes.get(&a) {230Some(neigh) => neigh.iter(),231None => [].iter(),232};233234iter.copied()235.map(N::Adjacent::into)236.filter_map(|(n, dir)| (!DIRECTED || dir == Outgoing).then_some(n))237}238239/// Return an iterator of all neighbors that have an edge between them and240/// `a`, in the specified direction.241/// If the graph's edges are undirected, this is equivalent to *.neighbors(a)*.242pub fn neighbors_directed(243&self,244a: N,245dir: Direction,246) -> impl DoubleEndedIterator<Item = N> + '_ {247let iter = match self.nodes.get(&a) {248Some(neigh) => neigh.iter(),249None => [].iter(),250};251252iter.copied()253.map(N::Adjacent::into)254.filter_map(move |(n, d)| (!DIRECTED || d == dir || n == a).then_some(n))255}256257/// Return an iterator of target nodes with an edge starting from `a`,258/// paired with their respective edge weights.259pub fn edges(&self, a: N) -> impl DoubleEndedIterator<Item = (N, N)> + '_ {260self.neighbors(a)261.map(move |b| match self.edges.get(&Self::edge_key(a, b)) {262None => unreachable!(),263Some(_) => (a, b),264})265}266267/// Return an iterator of target nodes with an edge starting from `a`,268/// paired with their respective edge weights.269pub fn edges_directed(270&self,271a: N,272dir: Direction,273) -> impl DoubleEndedIterator<Item = (N, N)> + '_ {274self.neighbors_directed(a, dir).map(move |b| {275let (a, b) = if dir == Incoming { (b, a) } else { (a, b) };276277match self.edges.get(&Self::edge_key(a, b)) {278None => unreachable!(),279Some(_) => (a, b),280}281})282}283284/// Return an iterator over all edges of the graph with their weight in arbitrary order.285pub fn all_edges(&self) -> impl ExactSizeIterator<Item = (N, N)> + '_ {286self.edges.iter().copied().map(N::Edge::into)287}288289pub(crate) fn to_index(&self, ix: N) -> usize {290self.nodes.get_index_of(&ix).unwrap()291}292293/// Converts from one [`GraphNodeId`] type to another. If the conversion fails,294/// it returns the error from the target type's [`TryFrom`] implementation.295///296/// Nodes must uniquely convert from `N` to `T` (i.e. no two `N` can convert297/// to the same `T`).298///299/// # Errors300///301/// If the conversion fails, it returns an error of type `N::Error`.302pub fn try_convert<T>(self) -> Result<Graph<DIRECTED, T, S>, N::Error>303where304N: TryInto<T>,305T: GraphNodeId,306S: Default,307{308// Converts the node key and every adjacency list entry from `N` to `T`.309fn try_convert_node<N: GraphNodeId + TryInto<T>, T: GraphNodeId>(310(key, adj): (N, Vec<N::Adjacent>),311) -> Result<(T, Vec<T::Adjacent>), N::Error> {312let key = key.try_into()?;313let adj = adj314.into_iter()315.map(|node| {316let (id, dir) = node.into();317Ok(T::Adjacent::from((id.try_into()?, dir)))318})319.collect::<Result<_, N::Error>>()?;320Ok((key, adj))321}322// Unpacks the edge pair, converts the nodes from `N` to `T`, and repacks them.323fn try_convert_edge<N: GraphNodeId + TryInto<T>, T: GraphNodeId>(324edge: N::Edge,325) -> Result<T::Edge, N::Error> {326let (a, b) = edge.into();327Ok(T::Edge::from((a.try_into()?, b.try_into()?)))328}329330let nodes = self331.nodes332.into_iter()333.map(try_convert_node::<N, T>)334.collect::<Result<_, N::Error>>()?;335let edges = self336.edges337.into_iter()338.map(try_convert_edge::<N, T>)339.collect::<Result<_, N::Error>>()?;340Ok(Graph { nodes, edges })341}342}343344/// Create a new empty `Graph`.345impl<const DIRECTED: bool, N, S> Default for Graph<DIRECTED, N, S>346where347N: GraphNodeId,348S: BuildHasher + Default,349{350fn default() -> Self {351Self::with_capacity(0, 0)352}353}354355impl<N: GraphNodeId, S: BuildHasher> DiGraph<N, S> {356/// Tries to topologically sort this directed graph.357///358/// If the graph is acyclic, returns [`Ok`] with the list of [`GraphNodeId`]s359/// in a valid topological order. If the graph contains cycles, returns [`Err`]360/// with the list of strongly-connected components that contain cycles361/// (also in a valid topological order).362///363/// # Errors364///365/// - If the graph contains a self-loop, returns [`DiGraphToposortError::Loop`].366/// - If the graph contains cycles, returns [`DiGraphToposortError::Cycle`].367pub fn toposort(&self, mut scratch: Vec<N>) -> Result<Vec<N>, DiGraphToposortError<N>> {368// Check explicitly for self-edges.369// `iter_sccs` won't report them as cycles because they still form components of one node.370if let Some((node, _)) = self.all_edges().find(|(left, right)| left == right) {371return Err(DiGraphToposortError::Loop(node));372}373374// Tarjan's SCC algorithm returns elements in *reverse* topological order.375scratch.clear();376scratch.reserve_exact(self.node_count().saturating_sub(scratch.capacity()));377let mut top_sorted_nodes = scratch;378let mut sccs_with_cycles = Vec::new();379380for scc in self.iter_sccs() {381// A strongly-connected component is a group of nodes who can all reach each other382// through one or more paths. If an SCC contains more than one node, there must be383// at least one cycle within them.384top_sorted_nodes.extend_from_slice(&scc);385if scc.len() > 1 {386sccs_with_cycles.push(scc);387}388}389390if sccs_with_cycles.is_empty() {391// reverse to get topological order392top_sorted_nodes.reverse();393Ok(top_sorted_nodes)394} else {395let mut cycles = Vec::new();396for scc in &sccs_with_cycles {397cycles.append(&mut self.simple_cycles_in_component(scc));398}399400Err(DiGraphToposortError::Cycle(cycles))401}402}403404/// Returns the simple cycles in a strongly-connected component of a directed graph.405///406/// The algorithm implemented comes from407/// ["Finding all the elementary circuits of a directed graph"][1] by D. B. Johnson.408///409/// [1]: https://doi.org/10.1137/0204007410pub fn simple_cycles_in_component(&self, scc: &[N]) -> Vec<Vec<N>> {411let mut cycles = vec![];412let mut sccs = vec![SmallVec::from_slice(scc)];413414while let Some(mut scc) = sccs.pop() {415// only look at nodes and edges in this strongly-connected component416let mut subgraph = DiGraph::<N>::with_capacity(scc.len(), 0);417for &node in &scc {418subgraph.add_node(node);419}420421for &node in &scc {422for successor in self.neighbors(node) {423if subgraph.contains_node(successor) {424subgraph.add_edge(node, successor);425}426}427}428429// path of nodes that may form a cycle430let mut path = Vec::with_capacity(subgraph.node_count());431// we mark nodes as "blocked" to avoid finding permutations of the same cycles432let mut blocked: HashSet<_> =433HashSet::with_capacity_and_hasher(subgraph.node_count(), Default::default());434// connects nodes along path segments that can't be part of a cycle (given current root)435// those nodes can be unblocked at the same time436let mut unblock_together: HashMap<N, HashSet<N>> =437HashMap::with_capacity_and_hasher(subgraph.node_count(), Default::default());438// stack for unblocking nodes439let mut unblock_stack = Vec::with_capacity(subgraph.node_count());440// nodes can be involved in multiple cycles441let mut maybe_in_more_cycles: HashSet<N> =442HashSet::with_capacity_and_hasher(subgraph.node_count(), Default::default());443// stack for DFS444let mut stack = Vec::with_capacity(subgraph.node_count());445446// we're going to look for all cycles that begin and end at this node447let root = scc.pop().unwrap();448// start a path at the root449path.clear();450path.push(root);451// mark this node as blocked452blocked.insert(root);453454// DFS455stack.clear();456stack.push((root, subgraph.neighbors(root)));457while !stack.is_empty() {458let &mut (ref node, ref mut successors) = stack.last_mut().unwrap();459if let Some(next) = successors.next() {460if next == root {461// found a cycle462maybe_in_more_cycles.extend(path.iter());463cycles.push(path.clone());464} else if !blocked.contains(&next) {465// first time seeing `next` on this path466maybe_in_more_cycles.remove(&next);467path.push(next);468blocked.insert(next);469stack.push((next, subgraph.neighbors(next)));470continue;471} else {472// not first time seeing `next` on this path473}474}475476if successors.peekable().peek().is_none() {477if maybe_in_more_cycles.contains(node) {478unblock_stack.push(*node);479// unblock this node's ancestors480while let Some(n) = unblock_stack.pop() {481if blocked.remove(&n) {482let unblock_predecessors = unblock_together.entry(n).or_default();483unblock_stack.extend(unblock_predecessors.iter());484unblock_predecessors.clear();485}486}487} else {488// if its descendants can be unblocked later, this node will be too489for successor in subgraph.neighbors(*node) {490unblock_together.entry(successor).or_default().insert(*node);491}492}493494// remove node from path and DFS stack495path.pop();496stack.pop();497}498}499500drop(stack);501502// remove node from subgraph503subgraph.remove_node(root);504505// divide remainder into smaller SCCs506sccs.extend(subgraph.iter_sccs().filter(|scc| scc.len() > 1));507}508509cycles510}511512/// Iterate over all *Strongly Connected Components* in this graph.513pub(crate) fn iter_sccs(&self) -> impl Iterator<Item = SmallVec<[N; 4]>> + '_ {514super::tarjan_scc::new_tarjan_scc(self)515}516}517518/// Error returned when topologically sorting a directed graph fails.519#[derive(Error, Debug)]520pub enum DiGraphToposortError<N: GraphNodeId> {521/// A self-loop was detected.522#[error("self-loop detected at node `{0:?}`")]523Loop(N),524/// Cycles were detected.525#[error("cycles detected: {0:?}")]526Cycle(Vec<Vec<N>>),527}528529/// Edge direction.530#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Ord, Eq, Hash)]531#[repr(u8)]532pub enum Direction {533/// An `Outgoing` edge is an outward edge *from* the current node.534Outgoing = 0,535/// An `Incoming` edge is an inbound edge *to* the current node.536Incoming = 1,537}538539impl Direction {540/// Return the opposite `Direction`.541#[inline]542pub fn opposite(self) -> Self {543match self {544Self::Outgoing => Self::Incoming,545Self::Incoming => Self::Outgoing,546}547}548}549550#[cfg(test)]551mod tests {552use crate::schedule::{NodeId, SystemKey};553554use super::*;555use alloc::vec;556use slotmap::SlotMap;557558/// The `Graph` type _must_ preserve the order that nodes are inserted in if559/// no removals occur. Removals are permitted to swap the latest node into the560/// location of the removed node.561#[test]562fn node_order_preservation() {563use NodeId::System;564565let mut slotmap = SlotMap::<SystemKey, ()>::with_key();566let mut graph = DiGraph::<NodeId>::default();567568let sys1 = slotmap.insert(());569let sys2 = slotmap.insert(());570let sys3 = slotmap.insert(());571let sys4 = slotmap.insert(());572573graph.add_node(System(sys1));574graph.add_node(System(sys2));575graph.add_node(System(sys3));576graph.add_node(System(sys4));577578assert_eq!(579graph.nodes().collect::<Vec<_>>(),580vec![System(sys1), System(sys2), System(sys3), System(sys4)]581);582583graph.remove_node(System(sys1));584585assert_eq!(586graph.nodes().collect::<Vec<_>>(),587vec![System(sys4), System(sys2), System(sys3)]588);589590graph.remove_node(System(sys4));591592assert_eq!(593graph.nodes().collect::<Vec<_>>(),594vec![System(sys3), System(sys2)]595);596597graph.remove_node(System(sys2));598599assert_eq!(graph.nodes().collect::<Vec<_>>(), vec![System(sys3)]);600601graph.remove_node(System(sys3));602603assert_eq!(graph.nodes().collect::<Vec<_>>(), vec![]);604}605606/// Nodes that have bidirectional edges (or any edge in the case of undirected graphs) are607/// considered strongly connected. A strongly connected component is a collection of608/// nodes where there exists a path from any node to any other node in the collection.609#[test]610fn strongly_connected_components() {611use NodeId::System;612613let mut slotmap = SlotMap::<SystemKey, ()>::with_key();614let mut graph = DiGraph::<NodeId>::default();615616let sys1 = slotmap.insert(());617let sys2 = slotmap.insert(());618let sys3 = slotmap.insert(());619let sys4 = slotmap.insert(());620let sys5 = slotmap.insert(());621let sys6 = slotmap.insert(());622623graph.add_edge(System(sys1), System(sys2));624graph.add_edge(System(sys2), System(sys1));625626graph.add_edge(System(sys2), System(sys3));627graph.add_edge(System(sys3), System(sys2));628629graph.add_edge(System(sys4), System(sys5));630graph.add_edge(System(sys5), System(sys4));631632graph.add_edge(System(sys6), System(sys2));633634let sccs = graph635.iter_sccs()636.map(|scc| scc.to_vec())637.collect::<Vec<_>>();638639assert_eq!(640sccs,641vec![642vec![System(sys3), System(sys2), System(sys1)],643vec![System(sys5), System(sys4)],644vec![System(sys6)]645]646);647}648}649650651