1use alloc::{boxed::Box, vec, vec::Vec};
2use core::{
3 any::{Any, TypeId},
4 fmt::Debug,
5};
6use smallvec::SmallVec;
7
8use bevy_platform::collections::{HashMap, HashSet};
9use bevy_utils::TypeIdMap;
10
11use fixedbitset::FixedBitSet;
12
13use crate::schedule::set::*;
14
15mod graph_map;
16mod tarjan_scc;
17
18pub use graph_map::{DiGraph, Direction, GraphNodeId, UnGraph};
19
20#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash)]
22pub(crate) enum DependencyKind {
23 Before,
25 After,
27}
28
29pub(crate) struct Dependency {
31 pub(crate) kind: DependencyKind,
32 pub(crate) set: InternedSystemSet,
33 pub(crate) options: TypeIdMap<Box<dyn Any>>,
34}
35
36impl Dependency {
37 pub fn new(kind: DependencyKind, set: InternedSystemSet) -> Self {
38 Self {
39 kind,
40 set,
41 options: Default::default(),
42 }
43 }
44 pub fn add_config<T: 'static>(mut self, option: T) -> Self {
45 self.options.insert(TypeId::of::<T>(), Box::new(option));
46 self
47 }
48}
49
50#[derive(Clone, Debug, Default)]
52pub(crate) enum Ambiguity {
53 #[default]
54 Check,
55 IgnoreWithSet(Vec<InternedSystemSet>),
57 IgnoreAll,
59}
60
61#[derive(Default)]
63pub struct GraphInfo {
64 pub(crate) hierarchy: Vec<InternedSystemSet>,
66 pub(crate) dependencies: Vec<Dependency>,
68 pub(crate) ambiguous_with: Ambiguity,
69}
70
71pub(crate) fn index(row: usize, col: usize, num_cols: usize) -> usize {
73 debug_assert!(col < num_cols);
74 (row * num_cols) + col
75}
76
77pub(crate) fn row_col(index: usize, num_cols: usize) -> (usize, usize) {
79 (index / num_cols, index % num_cols)
80}
81
82pub(crate) struct CheckGraphResults<N: GraphNodeId> {
84 pub(crate) reachable: FixedBitSet,
86 pub(crate) connected: HashSet<(N, N)>,
88 pub(crate) disconnected: Vec<(N, N)>,
90 pub(crate) transitive_edges: Vec<(N, N)>,
92 pub(crate) transitive_reduction: DiGraph<N>,
94 #[expect(dead_code, reason = "See the TODO above this attribute.")]
97 pub(crate) transitive_closure: DiGraph<N>,
98}
99
100impl<N: GraphNodeId> Default for CheckGraphResults<N> {
101 fn default() -> Self {
102 Self {
103 reachable: FixedBitSet::new(),
104 connected: HashSet::default(),
105 disconnected: Vec::new(),
106 transitive_edges: Vec::new(),
107 transitive_reduction: DiGraph::default(),
108 transitive_closure: DiGraph::default(),
109 }
110 }
111}
112
113pub(crate) fn check_graph<N: GraphNodeId>(
125 graph: &DiGraph<N>,
126 topological_order: &[N],
127) -> CheckGraphResults<N> {
128 if graph.node_count() == 0 {
129 return CheckGraphResults::default();
130 }
131
132 let n = graph.node_count();
133
134 let mut map = <HashMap<_, _>>::with_capacity_and_hasher(n, Default::default());
136 let mut topsorted = DiGraph::<N>::default();
137 for (i, &node) in topological_order.iter().enumerate() {
139 map.insert(node, i);
140 topsorted.add_node(node);
141 for pred in graph.neighbors_directed(node, Direction::Incoming) {
143 topsorted.add_edge(pred, node);
144 }
145 }
146
147 let mut reachable = FixedBitSet::with_capacity(n * n);
148 let mut connected = <HashSet<_>>::default();
149 let mut disconnected = Vec::new();
150
151 let mut transitive_edges = Vec::new();
152 let mut transitive_reduction = DiGraph::default();
153 let mut transitive_closure = DiGraph::default();
154
155 let mut visited = FixedBitSet::with_capacity(n);
156
157 for node in topsorted.nodes() {
159 transitive_reduction.add_node(node);
160 transitive_closure.add_node(node);
161 }
162
163 for a in topsorted.nodes().rev() {
165 let index_a = *map.get(&a).unwrap();
166 for b in topsorted.neighbors_directed(a, Direction::Outgoing) {
168 let index_b = *map.get(&b).unwrap();
169 debug_assert!(index_a < index_b);
170 if !visited[index_b] {
171 transitive_reduction.add_edge(a, b);
173 transitive_closure.add_edge(a, b);
174 reachable.insert(index(index_a, index_b, n));
175
176 let successors = transitive_closure
177 .neighbors_directed(b, Direction::Outgoing)
178 .collect::<Vec<_>>();
179 for c in successors {
180 let index_c = *map.get(&c).unwrap();
181 debug_assert!(index_b < index_c);
182 if !visited[index_c] {
183 visited.insert(index_c);
184 transitive_closure.add_edge(a, c);
185 reachable.insert(index(index_a, index_c, n));
186 }
187 }
188 } else {
189 transitive_edges.push((a, b));
191 }
192 }
193
194 visited.clear();
195 }
196
197 for i in 0..(n - 1) {
199 for index in index(i, i + 1, n)..=index(i, n - 1, n) {
201 let (a, b) = row_col(index, n);
202 let pair = (topological_order[a], topological_order[b]);
203 if reachable[index] {
204 connected.insert(pair);
205 } else {
206 disconnected.push(pair);
207 }
208 }
209 }
210
211 CheckGraphResults {
217 reachable,
218 connected,
219 disconnected,
220 transitive_edges,
221 transitive_reduction,
222 transitive_closure,
223 }
224}
225
226pub fn simple_cycles_in_component<N: GraphNodeId>(graph: &DiGraph<N>, scc: &[N]) -> Vec<Vec<N>> {
233 let mut cycles = vec![];
234 let mut sccs = vec![SmallVec::from_slice(scc)];
235
236 while let Some(mut scc) = sccs.pop() {
237 let mut subgraph = DiGraph::<N>::default();
239 for &node in &scc {
240 subgraph.add_node(node);
241 }
242
243 for &node in &scc {
244 for successor in graph.neighbors(node) {
245 if subgraph.contains_node(successor) {
246 subgraph.add_edge(node, successor);
247 }
248 }
249 }
250
251 let mut path = Vec::with_capacity(subgraph.node_count());
253 let mut blocked: HashSet<_> =
255 HashSet::with_capacity_and_hasher(subgraph.node_count(), Default::default());
256 let mut unblock_together: HashMap<N, HashSet<N>> =
259 HashMap::with_capacity_and_hasher(subgraph.node_count(), Default::default());
260 let mut unblock_stack = Vec::with_capacity(subgraph.node_count());
262 let mut maybe_in_more_cycles: HashSet<N> =
264 HashSet::with_capacity_and_hasher(subgraph.node_count(), Default::default());
265 let mut stack = Vec::with_capacity(subgraph.node_count());
267
268 let root = scc.pop().unwrap();
270 path.clear();
272 path.push(root);
273 blocked.insert(root);
275
276 stack.clear();
278 stack.push((root, subgraph.neighbors(root)));
279 while !stack.is_empty() {
280 let &mut (ref node, ref mut successors) = stack.last_mut().unwrap();
281 if let Some(next) = successors.next() {
282 if next == root {
283 maybe_in_more_cycles.extend(path.iter());
285 cycles.push(path.clone());
286 } else if !blocked.contains(&next) {
287 maybe_in_more_cycles.remove(&next);
289 path.push(next);
290 blocked.insert(next);
291 stack.push((next, subgraph.neighbors(next)));
292 continue;
293 } else {
294 }
296 }
297
298 if successors.peekable().peek().is_none() {
299 if maybe_in_more_cycles.contains(node) {
300 unblock_stack.push(*node);
301 while let Some(n) = unblock_stack.pop() {
303 if blocked.remove(&n) {
304 let unblock_predecessors = unblock_together.entry(n).or_default();
305 unblock_stack.extend(unblock_predecessors.iter());
306 unblock_predecessors.clear();
307 }
308 }
309 } else {
310 for successor in subgraph.neighbors(*node) {
312 unblock_together.entry(successor).or_default().insert(*node);
313 }
314 }
315
316 path.pop();
318 stack.pop();
319 }
320 }
321
322 drop(stack);
323
324 subgraph.remove_node(root);
326
327 sccs.extend(subgraph.iter_sccs().filter(|scc| scc.len() > 1));
329 }
330
331 cycles
332}