Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_ecs/src/schedule/auto_insert_apply_deferred.rs
6849 views
1
use alloc::{boxed::Box, collections::BTreeSet, vec::Vec};
2
3
use bevy_platform::collections::HashMap;
4
5
use crate::{
6
schedule::{SystemKey, SystemSetKey},
7
system::{IntoSystem, System},
8
world::World,
9
};
10
11
use super::{
12
is_apply_deferred, ApplyDeferred, DiGraph, Direction, NodeId, ReportCycles, ScheduleBuildError,
13
ScheduleBuildPass, ScheduleGraph,
14
};
15
16
/// A [`ScheduleBuildPass`] that inserts [`ApplyDeferred`] systems into the schedule graph
17
/// when there are [`Deferred`](crate::prelude::Deferred)
18
/// in one system and there are ordering dependencies on that system. [`Commands`](crate::system::Commands) is one
19
/// such deferred buffer.
20
///
21
/// This pass is typically automatically added to the schedule. You can disable this by setting
22
/// [`ScheduleBuildSettings::auto_insert_apply_deferred`](crate::schedule::ScheduleBuildSettings::auto_insert_apply_deferred)
23
/// to `false`. You may want to disable this if you only want to sync deferred params at the end of the schedule,
24
/// or want to manually insert all your sync points.
25
#[derive(Debug, Default)]
26
pub struct AutoInsertApplyDeferredPass {
27
/// Dependency edges that will **not** automatically insert an instance of `ApplyDeferred` on the edge.
28
no_sync_edges: BTreeSet<(NodeId, NodeId)>,
29
auto_sync_node_ids: HashMap<u32, SystemKey>,
30
}
31
32
/// If added to a dependency edge, the edge will not be considered for auto sync point insertions.
33
pub struct IgnoreDeferred;
34
35
impl AutoInsertApplyDeferredPass {
36
/// Returns the `NodeId` of the cached auto sync point. Will create
37
/// a new one if needed.
38
fn get_sync_point(&mut self, graph: &mut ScheduleGraph, distance: u32) -> SystemKey {
39
self.auto_sync_node_ids
40
.get(&distance)
41
.copied()
42
.unwrap_or_else(|| {
43
let key = self.add_auto_sync(graph);
44
self.auto_sync_node_ids.insert(distance, key);
45
key
46
})
47
}
48
/// add an [`ApplyDeferred`] system with no config
49
fn add_auto_sync(&mut self, graph: &mut ScheduleGraph) -> SystemKey {
50
let key = graph
51
.systems
52
.insert(Box::new(IntoSystem::into_system(ApplyDeferred)), Vec::new());
53
54
// ignore ambiguities with auto sync points
55
// They aren't under user control, so no one should know or care.
56
graph.ambiguous_with_all.insert(NodeId::System(key));
57
58
key
59
}
60
}
61
62
impl ScheduleBuildPass for AutoInsertApplyDeferredPass {
63
type EdgeOptions = IgnoreDeferred;
64
65
fn add_dependency(&mut self, from: NodeId, to: NodeId, options: Option<&Self::EdgeOptions>) {
66
if options.is_some() {
67
self.no_sync_edges.insert((from, to));
68
}
69
}
70
71
fn build(
72
&mut self,
73
_world: &mut World,
74
graph: &mut ScheduleGraph,
75
dependency_flattened: &mut DiGraph<SystemKey>,
76
) -> Result<(), ScheduleBuildError> {
77
let mut sync_point_graph = dependency_flattened.clone();
78
let topo = graph.topsort_graph(dependency_flattened, ReportCycles::Dependency)?;
79
80
fn set_has_conditions(graph: &ScheduleGraph, set: SystemSetKey) -> bool {
81
graph.system_sets.has_conditions(set)
82
|| graph
83
.hierarchy()
84
.graph()
85
.edges_directed(NodeId::Set(set), Direction::Incoming)
86
.any(|(parent, _)| {
87
parent
88
.as_set()
89
.is_some_and(|p| set_has_conditions(graph, p))
90
})
91
}
92
93
fn system_has_conditions(graph: &ScheduleGraph, key: SystemKey) -> bool {
94
graph.systems.has_conditions(key)
95
|| graph
96
.hierarchy()
97
.graph()
98
.edges_directed(NodeId::System(key), Direction::Incoming)
99
.any(|(parent, _)| {
100
parent
101
.as_set()
102
.is_some_and(|p| set_has_conditions(graph, p))
103
})
104
}
105
106
let mut system_has_conditions_cache = HashMap::<SystemKey, bool>::default();
107
let mut is_valid_explicit_sync_point = |key: SystemKey| {
108
is_apply_deferred(&graph.systems[key])
109
&& !*system_has_conditions_cache
110
.entry(key)
111
.or_insert_with(|| system_has_conditions(graph, key))
112
};
113
114
// Calculate the distance for each node.
115
// The "distance" is the number of sync points between a node and the beginning of the graph.
116
// Also store if a preceding edge would have added a sync point but was ignored to add it at
117
// a later edge that is not ignored.
118
let mut distances_and_pending_sync: HashMap<SystemKey, (u32, bool)> =
119
HashMap::with_capacity_and_hasher(topo.len(), Default::default());
120
121
// Keep track of any explicit sync nodes for a specific distance.
122
let mut distance_to_explicit_sync_node: HashMap<u32, SystemKey> = HashMap::default();
123
124
// Determine the distance for every node and collect the explicit sync points.
125
for &key in &topo {
126
let (node_distance, mut node_needs_sync) = distances_and_pending_sync
127
.get(&key)
128
.copied()
129
.unwrap_or_default();
130
131
if is_valid_explicit_sync_point(key) {
132
// The distance of this sync point does not change anymore as the iteration order
133
// makes sure that this node is no unvisited target of another node.
134
// Because of this, the sync point can be stored for this distance to be reused as
135
// automatically added sync points later.
136
distance_to_explicit_sync_node.insert(node_distance, key);
137
138
// This node just did a sync, so the only reason to do another sync is if one was
139
// explicitly scheduled afterwards.
140
node_needs_sync = false;
141
} else if !node_needs_sync {
142
// No previous node has postponed sync points to add so check if the system itself
143
// has deferred params that require a sync point to apply them.
144
node_needs_sync = graph.systems[key].has_deferred();
145
}
146
147
for target in dependency_flattened.neighbors_directed(key, Direction::Outgoing) {
148
let (target_distance, target_pending_sync) =
149
distances_and_pending_sync.entry(target).or_default();
150
151
let mut edge_needs_sync = node_needs_sync;
152
if node_needs_sync
153
&& !graph.systems[target].is_exclusive()
154
&& self
155
.no_sync_edges
156
.contains(&(NodeId::System(key), NodeId::System(target)))
157
{
158
// The node has deferred params to apply, but this edge is ignoring sync points.
159
// Mark the target as 'delaying' those commands to a future edge and the current
160
// edge as not needing a sync point.
161
*target_pending_sync = true;
162
edge_needs_sync = false;
163
}
164
165
let mut weight = 0;
166
if edge_needs_sync || is_valid_explicit_sync_point(target) {
167
// The target distance grows if a sync point is added between it and the node.
168
// Also raise the distance if the target is a sync point itself so it then again
169
// raises the distance of following nodes as that is what the distance is about.
170
weight = 1;
171
}
172
173
// The target cannot have fewer sync points in front of it than the preceding node.
174
*target_distance = (node_distance + weight).max(*target_distance);
175
}
176
}
177
178
// Find any edges which have a different number of sync points between them and make sure
179
// there is a sync point between them.
180
for &key in &topo {
181
let (node_distance, _) = distances_and_pending_sync
182
.get(&key)
183
.copied()
184
.unwrap_or_default();
185
186
for target in dependency_flattened.neighbors_directed(key, Direction::Outgoing) {
187
let (target_distance, _) = distances_and_pending_sync
188
.get(&target)
189
.copied()
190
.unwrap_or_default();
191
192
if node_distance == target_distance {
193
// These nodes are the same distance, so they don't need an edge between them.
194
continue;
195
}
196
197
if is_apply_deferred(&graph.systems[target]) {
198
// We don't need to insert a sync point since ApplyDeferred is a sync point
199
// already!
200
continue;
201
}
202
203
let sync_point = distance_to_explicit_sync_node
204
.get(&target_distance)
205
.copied()
206
.unwrap_or_else(|| self.get_sync_point(graph, target_distance));
207
208
sync_point_graph.add_edge(key, sync_point);
209
sync_point_graph.add_edge(sync_point, target);
210
211
// The edge without the sync point is now redundant.
212
sync_point_graph.remove_edge(key, target);
213
}
214
}
215
216
*dependency_flattened = sync_point_graph;
217
Ok(())
218
}
219
220
fn collapse_set(
221
&mut self,
222
set: SystemSetKey,
223
systems: &[SystemKey],
224
dependency_flattening: &DiGraph<NodeId>,
225
) -> impl Iterator<Item = (NodeId, NodeId)> {
226
if systems.is_empty() {
227
// collapse dependencies for empty sets
228
for a in dependency_flattening.neighbors_directed(NodeId::Set(set), Direction::Incoming)
229
{
230
for b in
231
dependency_flattening.neighbors_directed(NodeId::Set(set), Direction::Outgoing)
232
{
233
if self.no_sync_edges.contains(&(a, NodeId::Set(set)))
234
&& self.no_sync_edges.contains(&(NodeId::Set(set), b))
235
{
236
self.no_sync_edges.insert((a, b));
237
}
238
}
239
}
240
} else {
241
for a in dependency_flattening.neighbors_directed(NodeId::Set(set), Direction::Incoming)
242
{
243
for &sys in systems {
244
if self.no_sync_edges.contains(&(a, NodeId::Set(set))) {
245
self.no_sync_edges.insert((a, NodeId::System(sys)));
246
}
247
}
248
}
249
250
for b in dependency_flattening.neighbors_directed(NodeId::Set(set), Direction::Outgoing)
251
{
252
for &sys in systems {
253
if self.no_sync_edges.contains(&(NodeId::Set(set), b)) {
254
self.no_sync_edges.insert((NodeId::System(sys), b));
255
}
256
}
257
}
258
}
259
core::iter::empty()
260
}
261
}
262
263