Path: blob/main/crates/bevy_ecs/src/schedule/graph/graph_map.rs
6849 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;7use core::{8fmt::{self, Debug},9hash::{BuildHasher, Hash},10};1112use bevy_platform::{collections::HashSet, hash::FixedHasher};13use indexmap::IndexMap;14use smallvec::SmallVec;1516use Direction::{Incoming, Outgoing};1718/// Types that can be used as node identifiers in a [`DiGraph`]/[`UnGraph`].19///20/// [`DiGraph`]: crate::schedule::graph::DiGraph21/// [`UnGraph`]: crate::schedule::graph::UnGraph22pub trait GraphNodeId: Copy + Eq + Hash + Ord + Debug {23/// The type that packs and unpacks this [`GraphNodeId`] with a [`Direction`].24/// This is used to save space in the graph's adjacency list.25type Adjacent: Copy + Debug + From<(Self, Direction)> + Into<(Self, Direction)>;26/// The type that packs and unpacks this [`GraphNodeId`] with another27/// [`GraphNodeId`]. This is used to save space in the graph's edge list.28type Edge: Copy + Eq + Hash + Debug + From<(Self, Self)> + Into<(Self, Self)>;2930/// Name of the kind of this node id.31///32/// For structs, this should return a human-readable name of the struct.33/// For enums, this should return a human-readable name of the enum variant.34fn kind(&self) -> &'static str;35}3637/// A `Graph` with undirected edges of some [`GraphNodeId`] `N`.38///39/// For example, an edge between *1* and *2* is equivalent to an edge between40/// *2* and *1*.41pub type UnGraph<N, S = FixedHasher> = Graph<false, N, S>;4243/// A `Graph` with directed edges of some [`GraphNodeId`] `N`.44///45/// For example, an edge from *1* to *2* is distinct from an edge from *2* to46/// *1*.47pub type DiGraph<N, S = FixedHasher> = Graph<true, N, S>;4849/// `Graph<DIRECTED>` is a graph datastructure using an associative array50/// of its node weights of some [`GraphNodeId`].51///52/// It uses a combined adjacency list and sparse adjacency matrix53/// representation, using **O(|N| + |E|)** space, and allows testing for edge54/// existence in constant time.55///56/// `Graph` is parameterized over:57///58/// - Constant generic bool `DIRECTED` determines whether the graph edges are directed or59/// undirected.60/// - The `GraphNodeId` type `N`, which is used as the node weight.61/// - The `BuildHasher` `S`.62///63/// You can use the type aliases `UnGraph` and `DiGraph` for convenience.64///65/// `Graph` does not allow parallel edges, but self loops are allowed.66#[derive(Clone)]67pub struct Graph<const DIRECTED: bool, N: GraphNodeId, S = FixedHasher>68where69S: BuildHasher,70{71nodes: IndexMap<N, Vec<N::Adjacent>, S>,72edges: HashSet<N::Edge, S>,73}7475impl<const DIRECTED: bool, N: GraphNodeId, S: BuildHasher> Debug for Graph<DIRECTED, N, S> {76fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {77self.nodes.fmt(f)78}79}8081impl<const DIRECTED: bool, N: GraphNodeId, S: BuildHasher> Graph<DIRECTED, N, S> {82/// Create a new `Graph` with estimated capacity.83pub fn with_capacity(nodes: usize, edges: usize) -> Self84where85S: Default,86{87Self {88nodes: IndexMap::with_capacity_and_hasher(nodes, S::default()),89edges: HashSet::with_capacity_and_hasher(edges, S::default()),90}91}9293/// Use their natural order to map the node pair (a, b) to a canonical edge id.94#[inline]95fn edge_key(a: N, b: N) -> N::Edge {96let (a, b) = if DIRECTED || a <= b { (a, b) } else { (b, a) };9798N::Edge::from((a, b))99}100101/// Return the number of nodes in the graph.102pub fn node_count(&self) -> usize {103self.nodes.len()104}105106/// Return the number of edges in the graph.107pub fn edge_count(&self) -> usize {108self.edges.len()109}110111/// Add node `n` to the graph.112pub fn add_node(&mut self, n: N) {113self.nodes.entry(n).or_default();114}115116/// Remove a node `n` from the graph.117///118/// Computes in **O(N)** time, due to the removal of edges with other nodes.119pub fn remove_node(&mut self, n: N) {120let Some(links) = self.nodes.swap_remove(&n) else {121return;122};123124let links = links.into_iter().map(N::Adjacent::into);125126for (succ, dir) in links {127let edge = if dir == Outgoing {128Self::edge_key(n, succ)129} else {130Self::edge_key(succ, n)131};132// remove all successor links133self.remove_single_edge(succ, n, dir.opposite());134// Remove all edge values135self.edges.remove(&edge);136}137}138139/// Return `true` if the node is contained in the graph.140pub fn contains_node(&self, n: N) -> bool {141self.nodes.contains_key(&n)142}143144/// Add an edge connecting `a` and `b` to the graph.145/// For a directed graph, the edge is directed from `a` to `b`.146///147/// Inserts nodes `a` and/or `b` if they aren't already part of the graph.148pub fn add_edge(&mut self, a: N, b: N) {149if self.edges.insert(Self::edge_key(a, b)) {150// insert in the adjacency list if it's a new edge151self.nodes152.entry(a)153.or_insert_with(|| Vec::with_capacity(1))154.push(N::Adjacent::from((b, Outgoing)));155if a != b {156// self loops don't have the Incoming entry157self.nodes158.entry(b)159.or_insert_with(|| Vec::with_capacity(1))160.push(N::Adjacent::from((a, Incoming)));161}162}163}164165/// Remove edge relation from a to b.166///167/// Return `true` if it did exist.168fn remove_single_edge(&mut self, a: N, b: N, dir: Direction) -> bool {169let Some(sus) = self.nodes.get_mut(&a) else {170return false;171};172173let Some(index) = sus174.iter()175.copied()176.map(N::Adjacent::into)177.position(|elt| (DIRECTED && elt == (b, dir)) || (!DIRECTED && elt.0 == b))178else {179return false;180};181182sus.swap_remove(index);183true184}185186/// Remove edge from `a` to `b` from the graph.187///188/// Return `false` if the edge didn't exist.189pub fn remove_edge(&mut self, a: N, b: N) -> bool {190let exist1 = self.remove_single_edge(a, b, Outgoing);191let exist2 = if a != b {192self.remove_single_edge(b, a, Incoming)193} else {194exist1195};196let weight = self.edges.remove(&Self::edge_key(a, b));197debug_assert!(exist1 == exist2 && exist1 == weight);198weight199}200201/// Return `true` if the edge connecting `a` with `b` is contained in the graph.202pub fn contains_edge(&self, a: N, b: N) -> bool {203self.edges.contains(&Self::edge_key(a, b))204}205206/// Return an iterator over the nodes of the graph.207pub fn nodes(&self) -> impl DoubleEndedIterator<Item = N> + ExactSizeIterator<Item = N> + '_ {208self.nodes.keys().copied()209}210211/// Return an iterator of all nodes with an edge starting from `a`.212pub fn neighbors(&self, a: N) -> impl DoubleEndedIterator<Item = N> + '_ {213let iter = match self.nodes.get(&a) {214Some(neigh) => neigh.iter(),215None => [].iter(),216};217218iter.copied()219.map(N::Adjacent::into)220.filter_map(|(n, dir)| (!DIRECTED || dir == Outgoing).then_some(n))221}222223/// Return an iterator of all neighbors that have an edge between them and224/// `a`, in the specified direction.225/// If the graph's edges are undirected, this is equivalent to *.neighbors(a)*.226pub fn neighbors_directed(227&self,228a: N,229dir: Direction,230) -> impl DoubleEndedIterator<Item = N> + '_ {231let iter = match self.nodes.get(&a) {232Some(neigh) => neigh.iter(),233None => [].iter(),234};235236iter.copied()237.map(N::Adjacent::into)238.filter_map(move |(n, d)| (!DIRECTED || d == dir || n == a).then_some(n))239}240241/// Return an iterator of target nodes with an edge starting from `a`,242/// paired with their respective edge weights.243pub fn edges(&self, a: N) -> impl DoubleEndedIterator<Item = (N, N)> + '_ {244self.neighbors(a)245.map(move |b| match self.edges.get(&Self::edge_key(a, b)) {246None => unreachable!(),247Some(_) => (a, b),248})249}250251/// Return an iterator of target nodes with an edge starting from `a`,252/// paired with their respective edge weights.253pub fn edges_directed(254&self,255a: N,256dir: Direction,257) -> impl DoubleEndedIterator<Item = (N, N)> + '_ {258self.neighbors_directed(a, dir).map(move |b| {259let (a, b) = if dir == Incoming { (b, a) } else { (a, b) };260261match self.edges.get(&Self::edge_key(a, b)) {262None => unreachable!(),263Some(_) => (a, b),264}265})266}267268/// Return an iterator over all edges of the graph with their weight in arbitrary order.269pub fn all_edges(&self) -> impl ExactSizeIterator<Item = (N, N)> + '_ {270self.edges.iter().copied().map(N::Edge::into)271}272273pub(crate) fn to_index(&self, ix: N) -> usize {274self.nodes.get_index_of(&ix).unwrap()275}276277/// Converts from one [`GraphNodeId`] type to another. If the conversion fails,278/// it returns the error from the target type's [`TryFrom`] implementation.279///280/// Nodes must uniquely convert from `N` to `T` (i.e. no two `N` can convert281/// to the same `T`).282///283/// # Errors284///285/// If the conversion fails, it returns an error of type `T::Error`.286pub fn try_into<T: GraphNodeId + TryFrom<N>>(self) -> Result<Graph<DIRECTED, T, S>, T::Error>287where288S: Default,289{290// Converts the node key and every adjacency list entry from `N` to `T`.291fn try_convert_node<N: GraphNodeId, T: GraphNodeId + TryFrom<N>>(292(key, adj): (N, Vec<N::Adjacent>),293) -> Result<(T, Vec<T::Adjacent>), T::Error> {294let key = key.try_into()?;295let adj = adj296.into_iter()297.map(|node| {298let (id, dir) = node.into();299Ok(T::Adjacent::from((id.try_into()?, dir)))300})301.collect::<Result<_, T::Error>>()?;302Ok((key, adj))303}304// Unpacks the edge pair, converts the nodes from `N` to `T`, and repacks them.305fn try_convert_edge<N: GraphNodeId, T: GraphNodeId + TryFrom<N>>(306edge: N::Edge,307) -> Result<T::Edge, T::Error> {308let (a, b) = edge.into();309Ok(T::Edge::from((a.try_into()?, b.try_into()?)))310}311312let nodes = self313.nodes314.into_iter()315.map(try_convert_node::<N, T>)316.collect::<Result<_, T::Error>>()?;317let edges = self318.edges319.into_iter()320.map(try_convert_edge::<N, T>)321.collect::<Result<_, T::Error>>()?;322Ok(Graph { nodes, edges })323}324}325326/// Create a new empty `Graph`.327impl<const DIRECTED: bool, N, S> Default for Graph<DIRECTED, N, S>328where329N: GraphNodeId,330S: BuildHasher + Default,331{332fn default() -> Self {333Self::with_capacity(0, 0)334}335}336337impl<N: GraphNodeId, S: BuildHasher> DiGraph<N, S> {338/// Iterate over all *Strongly Connected Components* in this graph.339pub(crate) fn iter_sccs(&self) -> impl Iterator<Item = SmallVec<[N; 4]>> + '_ {340super::tarjan_scc::new_tarjan_scc(self)341}342}343344/// Edge direction.345#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Ord, Eq, Hash)]346#[repr(u8)]347pub enum Direction {348/// An `Outgoing` edge is an outward edge *from* the current node.349Outgoing = 0,350/// An `Incoming` edge is an inbound edge *to* the current node.351Incoming = 1,352}353354impl Direction {355/// Return the opposite `Direction`.356#[inline]357pub fn opposite(self) -> Self {358match self {359Self::Outgoing => Self::Incoming,360Self::Incoming => Self::Outgoing,361}362}363}364365#[cfg(test)]366mod tests {367use crate::schedule::{NodeId, SystemKey};368369use super::*;370use alloc::vec;371use slotmap::SlotMap;372373/// The `Graph` type _must_ preserve the order that nodes are inserted in if374/// no removals occur. Removals are permitted to swap the latest node into the375/// location of the removed node.376#[test]377fn node_order_preservation() {378use NodeId::System;379380let mut slotmap = SlotMap::<SystemKey, ()>::with_key();381let mut graph = DiGraph::<NodeId>::default();382383let sys1 = slotmap.insert(());384let sys2 = slotmap.insert(());385let sys3 = slotmap.insert(());386let sys4 = slotmap.insert(());387388graph.add_node(System(sys1));389graph.add_node(System(sys2));390graph.add_node(System(sys3));391graph.add_node(System(sys4));392393assert_eq!(394graph.nodes().collect::<Vec<_>>(),395vec![System(sys1), System(sys2), System(sys3), System(sys4)]396);397398graph.remove_node(System(sys1));399400assert_eq!(401graph.nodes().collect::<Vec<_>>(),402vec![System(sys4), System(sys2), System(sys3)]403);404405graph.remove_node(System(sys4));406407assert_eq!(408graph.nodes().collect::<Vec<_>>(),409vec![System(sys3), System(sys2)]410);411412graph.remove_node(System(sys2));413414assert_eq!(graph.nodes().collect::<Vec<_>>(), vec![System(sys3)]);415416graph.remove_node(System(sys3));417418assert_eq!(graph.nodes().collect::<Vec<_>>(), vec![]);419}420421/// Nodes that have bidirectional edges (or any edge in the case of undirected graphs) are422/// considered strongly connected. A strongly connected component is a collection of423/// nodes where there exists a path from any node to any other node in the collection.424#[test]425fn strongly_connected_components() {426use NodeId::System;427428let mut slotmap = SlotMap::<SystemKey, ()>::with_key();429let mut graph = DiGraph::<NodeId>::default();430431let sys1 = slotmap.insert(());432let sys2 = slotmap.insert(());433let sys3 = slotmap.insert(());434let sys4 = slotmap.insert(());435let sys5 = slotmap.insert(());436let sys6 = slotmap.insert(());437438graph.add_edge(System(sys1), System(sys2));439graph.add_edge(System(sys2), System(sys1));440441graph.add_edge(System(sys2), System(sys3));442graph.add_edge(System(sys3), System(sys2));443444graph.add_edge(System(sys4), System(sys5));445graph.add_edge(System(sys5), System(sys4));446447graph.add_edge(System(sys6), System(sys2));448449let sccs = graph450.iter_sccs()451.map(|scc| scc.to_vec())452.collect::<Vec<_>>();453454assert_eq!(455sccs,456vec![457vec![System(sys3), System(sys2), System(sys1)],458vec![System(sys5), System(sys4)],459vec![System(sys6)]460]461);462}463}464465466