@@ -7,7 +7,7 @@ use bevy_utils::default;
7
7
#[ cfg( feature = "trace" ) ]
8
8
use bevy_utils:: tracing:: info_span;
9
9
use bevy_utils:: {
10
- petgraph:: { algo :: tarjan_scc , prelude:: * } ,
10
+ petgraph:: prelude:: * ,
11
11
thiserror:: Error ,
12
12
tracing:: { error, warn} ,
13
13
HashMap , HashSet ,
@@ -941,15 +941,9 @@ impl ScheduleGraph {
941
941
}
942
942
943
943
// check hierarchy for cycles
944
- let hier_scc = tarjan_scc ( & self . hierarchy . graph ) ;
945
- // PERF: in theory we can skip this contains_cycles because we've already detected cycles
946
- // using calculate_base_sets_and_detect_cycles
947
- if self . contains_cycles ( & hier_scc) {
948
- self . report_cycles ( & hier_scc) ;
949
- return Err ( ScheduleBuildError :: HierarchyCycle ) ;
950
- }
951
-
952
- self . hierarchy . topsort = hier_scc. into_iter ( ) . flatten ( ) . rev ( ) . collect :: < Vec < _ > > ( ) ;
944
+ self . hierarchy . topsort = self
945
+ . topsort_graph ( & self . hierarchy . graph )
946
+ . map_err ( |_| ScheduleBuildError :: HierarchyCycle ) ?;
953
947
954
948
let hier_results = check_graph ( & self . hierarchy . graph , & self . hierarchy . topsort ) ;
955
949
if self . settings . hierarchy_detection != LogLevel :: Ignore
@@ -965,13 +959,9 @@ impl ScheduleGraph {
965
959
self . hierarchy . graph = hier_results. transitive_reduction ;
966
960
967
961
// check dependencies for cycles
968
- let dep_scc = tarjan_scc ( & self . dependency . graph ) ;
969
- if self . contains_cycles ( & dep_scc) {
970
- self . report_cycles ( & dep_scc) ;
971
- return Err ( ScheduleBuildError :: DependencyCycle ) ;
972
- }
973
-
974
- self . dependency . topsort = dep_scc. into_iter ( ) . flatten ( ) . rev ( ) . collect :: < Vec < _ > > ( ) ;
962
+ self . dependency . topsort = self
963
+ . topsort_graph ( & self . dependency . graph )
964
+ . map_err ( |_| ScheduleBuildError :: DependencyCycle ) ?;
975
965
976
966
// check for systems or system sets depending on sets they belong to
977
967
let dep_results = check_graph ( & self . dependency . graph , & self . dependency . topsort ) ;
@@ -1092,15 +1082,10 @@ impl ScheduleGraph {
1092
1082
}
1093
1083
1094
1084
// topsort
1095
- let flat_scc = tarjan_scc ( & dependency_flattened) ;
1096
- if self . contains_cycles ( & flat_scc) {
1097
- self . report_cycles ( & flat_scc) ;
1098
- return Err ( ScheduleBuildError :: DependencyCycle ) ;
1099
- }
1100
-
1085
+ self . dependency_flattened . topsort = self
1086
+ . topsort_graph ( & dependency_flattened)
1087
+ . map_err ( |_| ScheduleBuildError :: DependencyCycle ) ?;
1101
1088
self . dependency_flattened . graph = dependency_flattened;
1102
- self . dependency_flattened . topsort =
1103
- flat_scc. into_iter ( ) . flatten ( ) . rev ( ) . collect :: < Vec < _ > > ( ) ;
1104
1089
1105
1090
let flat_results = check_graph (
1106
1091
& self . dependency_flattened . graph ,
@@ -1377,31 +1362,43 @@ impl ScheduleGraph {
1377
1362
error ! ( "{}" , message) ;
1378
1363
}
1379
1364
1380
- fn contains_cycles ( & self , strongly_connected_components : & [ Vec < NodeId > ] ) -> bool {
1381
- if strongly_connected_components
1382
- . iter ( )
1383
- . all ( |scc| scc . len ( ) == 1 )
1384
- {
1385
- return false ;
1386
- }
1365
+ /// Get topology sorted [` NodeId`], also ensures the graph contains no cycle
1366
+ /// returns Err(()) if there are cycles
1367
+ fn topsort_graph ( & self , graph : & DiGraphMap < NodeId , ( ) > ) -> Result < Vec < NodeId > , ( ) > {
1368
+ // tarjon_scc's run order is reverse topological order
1369
+ let mut rev_top_sorted_nodes = Vec :: < NodeId > :: with_capacity ( graph . node_count ( ) ) ;
1370
+ let mut tarjan_scc = bevy_utils :: petgraph :: algo :: TarjanScc :: new ( ) ;
1371
+ let mut sccs_with_cycle = Vec :: < Vec < NodeId > > :: new ( ) ;
1387
1372
1388
- true
1373
+ tarjan_scc. run ( graph, |scc| {
1374
+ // by scc's definition, each scc is the cluster of nodes that they can reach each other
1375
+ // so scc with size larger than 1, means there is/are cycle(s).
1376
+ if scc. len ( ) > 1 {
1377
+ sccs_with_cycle. push ( scc. to_vec ( ) ) ;
1378
+ }
1379
+ rev_top_sorted_nodes. extend_from_slice ( scc) ;
1380
+ } ) ;
1381
+
1382
+ if sccs_with_cycle. is_empty ( ) {
1383
+ // reverse the reverted to get topological order
1384
+ let mut top_sorted_nodes = rev_top_sorted_nodes;
1385
+ top_sorted_nodes. reverse ( ) ;
1386
+ Ok ( top_sorted_nodes)
1387
+ } else {
1388
+ self . report_cycles ( & sccs_with_cycle) ;
1389
+ Err ( ( ) )
1390
+ }
1389
1391
}
1390
1392
1391
- fn report_cycles ( & self , strongly_connected_components : & [ Vec < NodeId > ] ) {
1392
- let components_with_cycles = strongly_connected_components
1393
- . iter ( )
1394
- . filter ( |scc| scc. len ( ) > 1 )
1395
- . cloned ( )
1396
- . collect :: < Vec < _ > > ( ) ;
1397
-
1393
+ /// Print detailed cycle messages
1394
+ fn report_cycles ( & self , sccs_with_cycles : & [ Vec < NodeId > ] ) {
1398
1395
let mut message = format ! (
1399
1396
"schedule contains at least {} cycle(s)" ,
1400
- components_with_cycles . len( )
1397
+ sccs_with_cycles . len( )
1401
1398
) ;
1402
1399
1403
1400
writeln ! ( message, " -- cycle(s) found within:" ) . unwrap ( ) ;
1404
- for ( i, scc) in components_with_cycles . into_iter ( ) . enumerate ( ) {
1401
+ for ( i, scc) in sccs_with_cycles . iter ( ) . enumerate ( ) {
1405
1402
let names = scc
1406
1403
. iter ( )
1407
1404
. map ( |id| self . get_node_name ( id) )
0 commit comments