Path: blob/main/crates/bevy_ecs/src/schedule/auto_insert_apply_deferred.rs
6849 views
use alloc::{boxed::Box, collections::BTreeSet, vec::Vec};12use bevy_platform::collections::HashMap;34use crate::{5schedule::{SystemKey, SystemSetKey},6system::{IntoSystem, System},7world::World,8};910use super::{11is_apply_deferred, ApplyDeferred, DiGraph, Direction, NodeId, ReportCycles, ScheduleBuildError,12ScheduleBuildPass, ScheduleGraph,13};1415/// A [`ScheduleBuildPass`] that inserts [`ApplyDeferred`] systems into the schedule graph16/// when there are [`Deferred`](crate::prelude::Deferred)17/// in one system and there are ordering dependencies on that system. [`Commands`](crate::system::Commands) is one18/// such deferred buffer.19///20/// This pass is typically automatically added to the schedule. You can disable this by setting21/// [`ScheduleBuildSettings::auto_insert_apply_deferred`](crate::schedule::ScheduleBuildSettings::auto_insert_apply_deferred)22/// to `false`. You may want to disable this if you only want to sync deferred params at the end of the schedule,23/// or want to manually insert all your sync points.24#[derive(Debug, Default)]25pub struct AutoInsertApplyDeferredPass {26/// Dependency edges that will **not** automatically insert an instance of `ApplyDeferred` on the edge.27no_sync_edges: BTreeSet<(NodeId, NodeId)>,28auto_sync_node_ids: HashMap<u32, SystemKey>,29}3031/// If added to a dependency edge, the edge will not be considered for auto sync point insertions.32pub struct IgnoreDeferred;3334impl AutoInsertApplyDeferredPass {35/// Returns the `NodeId` of the cached auto sync point. Will create36/// a new one if needed.37fn get_sync_point(&mut self, graph: &mut ScheduleGraph, distance: u32) -> SystemKey {38self.auto_sync_node_ids39.get(&distance)40.copied()41.unwrap_or_else(|| {42let key = self.add_auto_sync(graph);43self.auto_sync_node_ids.insert(distance, key);44key45})46}47/// add an [`ApplyDeferred`] system with no config48fn add_auto_sync(&mut self, graph: &mut ScheduleGraph) -> SystemKey {49let key = graph50.systems51.insert(Box::new(IntoSystem::into_system(ApplyDeferred)), Vec::new());5253// ignore ambiguities with auto sync points54// They aren't under user control, so no one should know or care.55graph.ambiguous_with_all.insert(NodeId::System(key));5657key58}59}6061impl ScheduleBuildPass for AutoInsertApplyDeferredPass {62type EdgeOptions = IgnoreDeferred;6364fn add_dependency(&mut self, from: NodeId, to: NodeId, options: Option<&Self::EdgeOptions>) {65if options.is_some() {66self.no_sync_edges.insert((from, to));67}68}6970fn build(71&mut self,72_world: &mut World,73graph: &mut ScheduleGraph,74dependency_flattened: &mut DiGraph<SystemKey>,75) -> Result<(), ScheduleBuildError> {76let mut sync_point_graph = dependency_flattened.clone();77let topo = graph.topsort_graph(dependency_flattened, ReportCycles::Dependency)?;7879fn set_has_conditions(graph: &ScheduleGraph, set: SystemSetKey) -> bool {80graph.system_sets.has_conditions(set)81|| graph82.hierarchy()83.graph()84.edges_directed(NodeId::Set(set), Direction::Incoming)85.any(|(parent, _)| {86parent87.as_set()88.is_some_and(|p| set_has_conditions(graph, p))89})90}9192fn system_has_conditions(graph: &ScheduleGraph, key: SystemKey) -> bool {93graph.systems.has_conditions(key)94|| graph95.hierarchy()96.graph()97.edges_directed(NodeId::System(key), Direction::Incoming)98.any(|(parent, _)| {99parent100.as_set()101.is_some_and(|p| set_has_conditions(graph, p))102})103}104105let mut system_has_conditions_cache = HashMap::<SystemKey, bool>::default();106let mut is_valid_explicit_sync_point = |key: SystemKey| {107is_apply_deferred(&graph.systems[key])108&& !*system_has_conditions_cache109.entry(key)110.or_insert_with(|| system_has_conditions(graph, key))111};112113// Calculate the distance for each node.114// The "distance" is the number of sync points between a node and the beginning of the graph.115// Also store if a preceding edge would have added a sync point but was ignored to add it at116// a later edge that is not ignored.117let mut distances_and_pending_sync: HashMap<SystemKey, (u32, bool)> =118HashMap::with_capacity_and_hasher(topo.len(), Default::default());119120// Keep track of any explicit sync nodes for a specific distance.121let mut distance_to_explicit_sync_node: HashMap<u32, SystemKey> = HashMap::default();122123// Determine the distance for every node and collect the explicit sync points.124for &key in &topo {125let (node_distance, mut node_needs_sync) = distances_and_pending_sync126.get(&key)127.copied()128.unwrap_or_default();129130if is_valid_explicit_sync_point(key) {131// The distance of this sync point does not change anymore as the iteration order132// makes sure that this node is no unvisited target of another node.133// Because of this, the sync point can be stored for this distance to be reused as134// automatically added sync points later.135distance_to_explicit_sync_node.insert(node_distance, key);136137// This node just did a sync, so the only reason to do another sync is if one was138// explicitly scheduled afterwards.139node_needs_sync = false;140} else if !node_needs_sync {141// No previous node has postponed sync points to add so check if the system itself142// has deferred params that require a sync point to apply them.143node_needs_sync = graph.systems[key].has_deferred();144}145146for target in dependency_flattened.neighbors_directed(key, Direction::Outgoing) {147let (target_distance, target_pending_sync) =148distances_and_pending_sync.entry(target).or_default();149150let mut edge_needs_sync = node_needs_sync;151if node_needs_sync152&& !graph.systems[target].is_exclusive()153&& self154.no_sync_edges155.contains(&(NodeId::System(key), NodeId::System(target)))156{157// The node has deferred params to apply, but this edge is ignoring sync points.158// Mark the target as 'delaying' those commands to a future edge and the current159// edge as not needing a sync point.160*target_pending_sync = true;161edge_needs_sync = false;162}163164let mut weight = 0;165if edge_needs_sync || is_valid_explicit_sync_point(target) {166// The target distance grows if a sync point is added between it and the node.167// Also raise the distance if the target is a sync point itself so it then again168// raises the distance of following nodes as that is what the distance is about.169weight = 1;170}171172// The target cannot have fewer sync points in front of it than the preceding node.173*target_distance = (node_distance + weight).max(*target_distance);174}175}176177// Find any edges which have a different number of sync points between them and make sure178// there is a sync point between them.179for &key in &topo {180let (node_distance, _) = distances_and_pending_sync181.get(&key)182.copied()183.unwrap_or_default();184185for target in dependency_flattened.neighbors_directed(key, Direction::Outgoing) {186let (target_distance, _) = distances_and_pending_sync187.get(&target)188.copied()189.unwrap_or_default();190191if node_distance == target_distance {192// These nodes are the same distance, so they don't need an edge between them.193continue;194}195196if is_apply_deferred(&graph.systems[target]) {197// We don't need to insert a sync point since ApplyDeferred is a sync point198// already!199continue;200}201202let sync_point = distance_to_explicit_sync_node203.get(&target_distance)204.copied()205.unwrap_or_else(|| self.get_sync_point(graph, target_distance));206207sync_point_graph.add_edge(key, sync_point);208sync_point_graph.add_edge(sync_point, target);209210// The edge without the sync point is now redundant.211sync_point_graph.remove_edge(key, target);212}213}214215*dependency_flattened = sync_point_graph;216Ok(())217}218219fn collapse_set(220&mut self,221set: SystemSetKey,222systems: &[SystemKey],223dependency_flattening: &DiGraph<NodeId>,224) -> impl Iterator<Item = (NodeId, NodeId)> {225if systems.is_empty() {226// collapse dependencies for empty sets227for a in dependency_flattening.neighbors_directed(NodeId::Set(set), Direction::Incoming)228{229for b in230dependency_flattening.neighbors_directed(NodeId::Set(set), Direction::Outgoing)231{232if self.no_sync_edges.contains(&(a, NodeId::Set(set)))233&& self.no_sync_edges.contains(&(NodeId::Set(set), b))234{235self.no_sync_edges.insert((a, b));236}237}238}239} else {240for a in dependency_flattening.neighbors_directed(NodeId::Set(set), Direction::Incoming)241{242for &sys in systems {243if self.no_sync_edges.contains(&(a, NodeId::Set(set))) {244self.no_sync_edges.insert((a, NodeId::System(sys)));245}246}247}248249for b in dependency_flattening.neighbors_directed(NodeId::Set(set), Direction::Outgoing)250{251for &sys in systems {252if self.no_sync_edges.contains(&(NodeId::Set(set), b)) {253self.no_sync_edges.insert((NodeId::System(sys), b));254}255}256}257}258core::iter::empty()259}260}261262263