extract topsort logic to a new method, one pass to detect cycles and … (#7727)
…top sort. reduce mem alloc # Objective - Reduce alloc count. - Improve code quality. ## Solution - use `TarjanScc::run` directly, which calls a closure with each scc, in closure, we can detect cycles and flatten nodes
This commit is contained in:
		
							parent
							
								
									5a5125671b
								
							
						
					
					
						commit
						bd54c4d2d1
					
				@ -7,7 +7,7 @@ use bevy_utils::default;
 | 
			
		||||
#[cfg(feature = "trace")]
 | 
			
		||||
use bevy_utils::tracing::info_span;
 | 
			
		||||
use bevy_utils::{
 | 
			
		||||
    petgraph::{algo::tarjan_scc, prelude::*},
 | 
			
		||||
    petgraph::prelude::*,
 | 
			
		||||
    thiserror::Error,
 | 
			
		||||
    tracing::{error, warn},
 | 
			
		||||
    HashMap, HashSet,
 | 
			
		||||
@ -941,15 +941,9 @@ impl ScheduleGraph {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // check hierarchy for cycles
 | 
			
		||||
        let hier_scc = tarjan_scc(&self.hierarchy.graph);
 | 
			
		||||
        // PERF: in theory we can skip this contains_cycles because we've already detected cycles
 | 
			
		||||
        // using calculate_base_sets_and_detect_cycles
 | 
			
		||||
        if self.contains_cycles(&hier_scc) {
 | 
			
		||||
            self.report_cycles(&hier_scc);
 | 
			
		||||
            return Err(ScheduleBuildError::HierarchyCycle);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self.hierarchy.topsort = hier_scc.into_iter().flatten().rev().collect::<Vec<_>>();
 | 
			
		||||
        self.hierarchy.topsort = self
 | 
			
		||||
            .topsort_graph(&self.hierarchy.graph)
 | 
			
		||||
            .map_err(|_| ScheduleBuildError::HierarchyCycle)?;
 | 
			
		||||
 | 
			
		||||
        let hier_results = check_graph(&self.hierarchy.graph, &self.hierarchy.topsort);
 | 
			
		||||
        if self.settings.hierarchy_detection != LogLevel::Ignore
 | 
			
		||||
@ -965,13 +959,9 @@ impl ScheduleGraph {
 | 
			
		||||
        self.hierarchy.graph = hier_results.transitive_reduction;
 | 
			
		||||
 | 
			
		||||
        // check dependencies for cycles
 | 
			
		||||
        let dep_scc = tarjan_scc(&self.dependency.graph);
 | 
			
		||||
        if self.contains_cycles(&dep_scc) {
 | 
			
		||||
            self.report_cycles(&dep_scc);
 | 
			
		||||
            return Err(ScheduleBuildError::DependencyCycle);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self.dependency.topsort = dep_scc.into_iter().flatten().rev().collect::<Vec<_>>();
 | 
			
		||||
        self.dependency.topsort = self
 | 
			
		||||
            .topsort_graph(&self.dependency.graph)
 | 
			
		||||
            .map_err(|_| ScheduleBuildError::DependencyCycle)?;
 | 
			
		||||
 | 
			
		||||
        // check for systems or system sets depending on sets they belong to
 | 
			
		||||
        let dep_results = check_graph(&self.dependency.graph, &self.dependency.topsort);
 | 
			
		||||
@ -1092,15 +1082,10 @@ impl ScheduleGraph {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // topsort
 | 
			
		||||
        let flat_scc = tarjan_scc(&dependency_flattened);
 | 
			
		||||
        if self.contains_cycles(&flat_scc) {
 | 
			
		||||
            self.report_cycles(&flat_scc);
 | 
			
		||||
            return Err(ScheduleBuildError::DependencyCycle);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        self.dependency_flattened.topsort = self
 | 
			
		||||
            .topsort_graph(&dependency_flattened)
 | 
			
		||||
            .map_err(|_| ScheduleBuildError::DependencyCycle)?;
 | 
			
		||||
        self.dependency_flattened.graph = dependency_flattened;
 | 
			
		||||
        self.dependency_flattened.topsort =
 | 
			
		||||
            flat_scc.into_iter().flatten().rev().collect::<Vec<_>>();
 | 
			
		||||
 | 
			
		||||
        let flat_results = check_graph(
 | 
			
		||||
            &self.dependency_flattened.graph,
 | 
			
		||||
@ -1377,31 +1362,43 @@ impl ScheduleGraph {
 | 
			
		||||
        error!("{}", message);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn contains_cycles(&self, strongly_connected_components: &[Vec<NodeId>]) -> bool {
 | 
			
		||||
        if strongly_connected_components
 | 
			
		||||
            .iter()
 | 
			
		||||
            .all(|scc| scc.len() == 1)
 | 
			
		||||
        {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    /// Get topology sorted [`NodeId`], also ensures the graph contains no cycle
 | 
			
		||||
    /// returns Err(()) if there are cycles
 | 
			
		||||
    fn topsort_graph(&self, graph: &DiGraphMap<NodeId, ()>) -> Result<Vec<NodeId>, ()> {
 | 
			
		||||
        // tarjon_scc's run order is reverse topological order
 | 
			
		||||
        let mut rev_top_sorted_nodes = Vec::<NodeId>::with_capacity(graph.node_count());
 | 
			
		||||
        let mut tarjan_scc = bevy_utils::petgraph::algo::TarjanScc::new();
 | 
			
		||||
        let mut sccs_with_cycle = Vec::<Vec<NodeId>>::new();
 | 
			
		||||
 | 
			
		||||
        true
 | 
			
		||||
        tarjan_scc.run(graph, |scc| {
 | 
			
		||||
            // by scc's definition, each scc is the cluster of nodes that they can reach each other
 | 
			
		||||
            // so scc with size larger than 1, means there is/are cycle(s).
 | 
			
		||||
            if scc.len() > 1 {
 | 
			
		||||
                sccs_with_cycle.push(scc.to_vec());
 | 
			
		||||
            }
 | 
			
		||||
            rev_top_sorted_nodes.extend_from_slice(scc);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        if sccs_with_cycle.is_empty() {
 | 
			
		||||
            // reverse the reverted to get topological order
 | 
			
		||||
            let mut top_sorted_nodes = rev_top_sorted_nodes;
 | 
			
		||||
            top_sorted_nodes.reverse();
 | 
			
		||||
            Ok(top_sorted_nodes)
 | 
			
		||||
        } else {
 | 
			
		||||
            self.report_cycles(&sccs_with_cycle);
 | 
			
		||||
            Err(())
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn report_cycles(&self, strongly_connected_components: &[Vec<NodeId>]) {
 | 
			
		||||
        let components_with_cycles = strongly_connected_components
 | 
			
		||||
            .iter()
 | 
			
		||||
            .filter(|scc| scc.len() > 1)
 | 
			
		||||
            .cloned()
 | 
			
		||||
            .collect::<Vec<_>>();
 | 
			
		||||
 | 
			
		||||
    /// Print detailed cycle messages
 | 
			
		||||
    fn report_cycles(&self, sccs_with_cycles: &[Vec<NodeId>]) {
 | 
			
		||||
        let mut message = format!(
 | 
			
		||||
            "schedule contains at least {} cycle(s)",
 | 
			
		||||
            components_with_cycles.len()
 | 
			
		||||
            sccs_with_cycles.len()
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        writeln!(message, " -- cycle(s) found within:").unwrap();
 | 
			
		||||
        for (i, scc) in components_with_cycles.into_iter().enumerate() {
 | 
			
		||||
        for (i, scc) in sccs_with_cycles.iter().enumerate() {
 | 
			
		||||
            let names = scc
 | 
			
		||||
                .iter()
 | 
			
		||||
                .map(|id| self.get_node_name(id))
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user