1use alloc::{borrow::ToOwned, boxed::Box, collections::BTreeSet, vec::Vec};
23use bevy_platform::{collections::HashMap, hash::FixedHasher};
4use indexmap::IndexSet;
56use crate::{
7 schedule::{FlattenedDependencies, SystemKey, SystemSetKey},
8 system::{IntoSystem, System},
9world::World,
10};
1112use super::{
13is_apply_deferred, ApplyDeferred, DiGraph, Direction, NodeId, ScheduleBuildError,
14ScheduleBuildPass, ScheduleGraph,
15};
1617/// A [`ScheduleBuildPass`] that inserts [`ApplyDeferred`] systems into the schedule graph
18/// when there are [`Deferred`](crate::prelude::Deferred)
19/// in one system and there are ordering dependencies on that system. [`Commands`](crate::system::Commands) is one
20/// such deferred buffer.
21///
22/// This pass is typically automatically added to the schedule. You can disable this by setting
23/// [`ScheduleBuildSettings::auto_insert_apply_deferred`](crate::schedule::ScheduleBuildSettings::auto_insert_apply_deferred)
24/// to `false`. You may want to disable this if you only want to sync deferred params at the end of the schedule,
25/// or want to manually insert all your sync points.
26#[derive(#[automatically_derived]
impl ::core::fmt::Debug for AutoInsertApplyDeferredPass {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field2_finish(f,
"AutoInsertApplyDeferredPass", "no_sync_edges",
&self.no_sync_edges, "auto_sync_node_ids",
&&self.auto_sync_node_ids)
}
}Debug, #[automatically_derived]
impl ::core::default::Default for AutoInsertApplyDeferredPass {
#[inline]
fn default() -> AutoInsertApplyDeferredPass {
AutoInsertApplyDeferredPass {
no_sync_edges: ::core::default::Default::default(),
auto_sync_node_ids: ::core::default::Default::default(),
}
}
}Default)]
27pub struct AutoInsertApplyDeferredPass {
28/// Dependency edges that will **not** automatically insert an instance of `ApplyDeferred` on the edge.
29no_sync_edges: BTreeSet<(NodeId, NodeId)>,
30 auto_sync_node_ids: HashMap<u32, SystemKey>,
31}
3233/// If added to a dependency edge, the edge will not be considered for auto sync point insertions.
34pub struct IgnoreDeferred;
3536impl AutoInsertApplyDeferredPass {
37/// Returns the `NodeId` of the cached auto sync point. Will create
38 /// a new one if needed.
39fn get_sync_point(&mut self, graph: &mut ScheduleGraph, distance: u32) -> SystemKey {
40self.auto_sync_node_ids
41 .get(&distance)
42 .copied()
43 .unwrap_or_else(|| {
44let key = self.add_auto_sync(graph);
45self.auto_sync_node_ids.insert(distance, key);
46key47 })
48 }
49/// add an [`ApplyDeferred`] system with no config
50fn add_auto_sync(&mut self, graph: &mut ScheduleGraph) -> SystemKey {
51let key = graph52 .systems
53 .insert(Box::new(IntoSystem::into_system(ApplyDeferred)), Vec::new());
5455// ignore ambiguities with auto sync points
56 // They aren't under user control, so no one should know or care.
57graph.ambiguous_with_all.insert(NodeId::System(key));
5859key60 }
61}
6263impl ScheduleBuildPassfor AutoInsertApplyDeferredPass {
64type EdgeOptions = IgnoreDeferred;
6566fn add_dependency(&mut self, from: NodeId, to: NodeId, options: Option<&Self::EdgeOptions>) {
67if options.is_some() {
68self.no_sync_edges.insert((from, to));
69 }
70 }
7172fn build(
73&mut self,
74 _world: &mut World,
75 graph: &mut ScheduleGraph,
76mut dependency_flattened: FlattenedDependencies<'_>,
77 ) -> Result<(), ScheduleBuildError> {
78let (topo, flat_dependency) = dependency_flattened
79 .toposort_and_graph()
80 .map_err(ScheduleBuildError::FlatDependencySort)?;
81let topo = topo.to_owned();
82let flat_dependency = flat_dependency.to_owned();
8384fn set_has_conditions(graph: &ScheduleGraph, set: SystemSetKey) -> bool {
85graph.system_sets.has_conditions(set)
86 || graph87 .hierarchy()
88 .graph()
89 .edges_directed(NodeId::Set(set), Direction::Incoming)
90 .any(|(parent, _)| {
91parent92 .as_set()
93 .is_some_and(|p| set_has_conditions(graph, p))
94 })
95 }
9697fn system_has_conditions(graph: &ScheduleGraph, key: SystemKey) -> bool {
98graph.systems.has_conditions(key)
99 || graph100 .hierarchy()
101 .graph()
102 .edges_directed(NodeId::System(key), Direction::Incoming)
103 .any(|(parent, _)| {
104parent105 .as_set()
106 .is_some_and(|p| set_has_conditions(graph, p))
107 })
108 }
109110let mut system_has_conditions_cache = HashMap::<SystemKey, bool>::default();
111let mut is_valid_explicit_sync_point = |key: SystemKey| {
112is_apply_deferred(&graph.systems[key])
113 && !*system_has_conditions_cache114 .entry(key)
115 .or_insert_with(|| system_has_conditions(graph, key))
116 };
117118// Calculate the distance for each node.
119 // The "distance" is the number of sync points between a node and the beginning of the graph.
120 // Also store if a preceding edge would have added a sync point but was ignored to add it at
121 // a later edge that is not ignored.
122let mut distances_and_pending_sync: HashMap<SystemKey, (u32, bool)> =
123HashMap::with_capacity_and_hasher(topo.len(), Default::default());
124125// Keep track of any explicit sync nodes for a specific distance.
126let mut distance_to_explicit_sync_node: HashMap<u32, SystemKey> = HashMap::default();
127128// Determine the distance for every node and collect the explicit sync points.
129for &key in topo.iter() {
130let (node_distance, mut node_needs_sync) = distances_and_pending_sync
131 .get(&key)
132 .copied()
133 .unwrap_or_default();
134135if is_valid_explicit_sync_point(key) {
136// The distance of this sync point does not change anymore as the iteration order
137 // makes sure that this node is no unvisited target of another node.
138 // Because of this, the sync point can be stored for this distance to be reused as
139 // automatically added sync points later.
140distance_to_explicit_sync_node.insert(node_distance, key);
141142// This node just did a sync, so the only reason to do another sync is if one was
143 // explicitly scheduled afterwards.
144node_needs_sync = false;
145 } else if !node_needs_sync {
146// No previous node has postponed sync points to add so check if the system itself
147 // has deferred params that require a sync point to apply them.
148node_needs_sync = graph.systems[key].has_deferred();
149 }
150151for target in flat_dependency.neighbors_directed(key, Direction::Outgoing) {
152let (target_distance, target_pending_sync) =
153 distances_and_pending_sync.entry(target).or_default();
154155let mut edge_needs_sync = node_needs_sync;
156if node_needs_sync
157 && !graph.systems[target].is_exclusive()
158 && self
159.no_sync_edges
160 .contains(&(NodeId::System(key), NodeId::System(target)))
161 {
162// The node has deferred params to apply, but this edge is ignoring sync points.
163 // Mark the target as 'delaying' those commands to a future edge and the current
164 // edge as not needing a sync point.
165*target_pending_sync = true;
166 edge_needs_sync = false;
167 }
168169let mut weight = 0;
170if edge_needs_sync || is_valid_explicit_sync_point(target) {
171// The target distance grows if a sync point is added between it and the node.
172 // Also raise the distance if the target is a sync point itself so it then again
173 // raises the distance of following nodes as that is what the distance is about.
174weight = 1;
175 }
176177// The target cannot have fewer sync points in front of it than the preceding node.
178*target_distance = (node_distance + weight).max(*target_distance);
179 }
180 }
181182// Find any edges which have a different number of sync points between them and make sure
183 // there is a sync point between them.
184for &key in topo.iter() {
185let (node_distance, _) = distances_and_pending_sync
186 .get(&key)
187 .copied()
188 .unwrap_or_default();
189190for target in flat_dependency.neighbors_directed(key, Direction::Outgoing) {
191let (target_distance, _) = distances_and_pending_sync
192 .get(&target)
193 .copied()
194 .unwrap_or_default();
195196if node_distance == target_distance {
197// These nodes are the same distance, so they don't need an edge between them.
198continue;
199 }
200201if is_apply_deferred(&graph.systems[target]) {
202// We don't need to insert a sync point since ApplyDeferred is a sync point
203 // already!
204continue;
205 }
206207let sync_point = distance_to_explicit_sync_node
208 .get(&target_distance)
209 .copied()
210 .unwrap_or_else(|| self.get_sync_point(graph, target_distance));
211212 dependency_flattened.add_edge(key, sync_point);
213 dependency_flattened.add_edge(sync_point, target);
214215// The edge without the sync point is now redundant.
216dependency_flattened.remove_edge(key, target);
217 }
218 }
219Ok(())
220 }
221222fn collapse_set(
223&mut self,
224 set: SystemSetKey,
225 systems: &IndexSet<SystemKey, FixedHasher>,
226 dependency_flattening: &DiGraph<NodeId>,
227 ) -> impl Iterator<Item = (NodeId, NodeId)> {
228if systems.is_empty() {
229// collapse dependencies for empty sets
230for a in dependency_flattening.neighbors_directed(NodeId::Set(set), Direction::Incoming)
231 {
232for b in
233dependency_flattening.neighbors_directed(NodeId::Set(set), Direction::Outgoing)
234 {
235if self.no_sync_edges.contains(&(a, NodeId::Set(set)))
236 && self.no_sync_edges.contains(&(NodeId::Set(set), b))
237 {
238self.no_sync_edges.insert((a, b));
239 }
240 }
241 }
242 } else {
243for a in dependency_flattening.neighbors_directed(NodeId::Set(set), Direction::Incoming)
244 {
245for &sys in systems {
246if self.no_sync_edges.contains(&(a, NodeId::Set(set))) {
247self.no_sync_edges.insert((a, NodeId::System(sys)));
248 }
249 }
250 }
251252for b in dependency_flattening.neighbors_directed(NodeId::Set(set), Direction::Outgoing)
253 {
254for &sys in systems {
255if self.no_sync_edges.contains(&(NodeId::Set(set), b)) {
256self.no_sync_edges.insert((NodeId::System(sys), b));
257 }
258 }
259 }
260 }
261 core::iter::empty()
262 }
263}