Skip to content

Commit ebf87f5

Browse files
authored
Use SlotMaps to store systems and system sets in Schedules (#19352)
# Objective - First step towards #279 ## Solution Makes the necessary internal data structure changes in order to allow system removal to be added in a future PR: `Vec`s storing systems and system sets in `ScheduleGraph` have been replaced with `SlotMap`s. See the included migration guide for the required changes. ## Testing Internal changes only and no new features *should* mean no new tests are requried.
1 parent bbf91a6 commit ebf87f5

File tree

11 files changed

+505
-377
lines changed

11 files changed

+505
-377
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ keywords = ["game", "engine", "gamedev", "graphics", "bevy"]
1010
license = "MIT OR Apache-2.0"
1111
repository = "https://github.com/bevyengine/bevy"
1212
documentation = "https://docs.rs/bevy"
13-
rust-version = "1.86.0"
13+
rust-version = "1.88.0"
1414

1515
[workspace]
1616
resolver = "2"

crates/bevy_ecs/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ tracing = { version = "0.1", default-features = false, optional = true }
119119
log = { version = "0.4", default-features = false }
120120
bumpalo = "3"
121121
subsecond = { version = "0.7.0-alpha.1", optional = true }
122+
slotmap = { version = "1.0.7", default-features = false }
122123

123124
concurrent-queue = { version = "2.5.0", default-features = false }
124125
[target.'cfg(not(all(target_has_atomic = "8", target_has_atomic = "16", target_has_atomic = "32", target_has_atomic = "64", target_has_atomic = "ptr")))'.dependencies]

crates/bevy_ecs/src/schedule/auto_insert_apply_deferred.rs

Lines changed: 81 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@ use alloc::{boxed::Box, collections::BTreeSet, vec::Vec};
22

33
use bevy_platform::collections::HashMap;
44

5-
use crate::system::IntoSystem;
6-
use crate::world::World;
5+
use crate::{
6+
schedule::{SystemKey, SystemSetKey},
7+
system::IntoSystem,
8+
world::World,
9+
};
710

811
use super::{
912
is_apply_deferred, ApplyDeferred, DiGraph, Direction, NodeId, ReportCycles, ScheduleBuildError,
@@ -36,29 +39,26 @@ impl AutoInsertApplyDeferredPass {
3639
self.auto_sync_node_ids
3740
.get(&distance)
3841
.copied()
39-
.or_else(|| {
40-
let node_id = self.add_auto_sync(graph);
42+
.unwrap_or_else(|| {
43+
let node_id = NodeId::System(self.add_auto_sync(graph));
4144
self.auto_sync_node_ids.insert(distance, node_id);
42-
Some(node_id)
45+
node_id
4346
})
44-
.unwrap()
4547
}
4648
/// add an [`ApplyDeferred`] system with no config
47-
fn add_auto_sync(&mut self, graph: &mut ScheduleGraph) -> NodeId {
48-
let id = NodeId::System(graph.systems.len());
49-
50-
graph
49+
fn add_auto_sync(&mut self, graph: &mut ScheduleGraph) -> SystemKey {
50+
let key = graph
5151
.systems
52-
.push(SystemNode::new(Box::new(IntoSystem::into_system(
52+
.insert(SystemNode::new(Box::new(IntoSystem::into_system(
5353
ApplyDeferred,
5454
))));
55-
graph.system_conditions.push(Vec::new());
55+
graph.system_conditions.insert(key, Vec::new());
5656

5757
// ignore ambiguities with auto sync points
5858
// They aren't under user control, so no one should know or care.
59-
graph.ambiguous_with_all.insert(id);
59+
graph.ambiguous_with_all.insert(NodeId::System(key));
6060

61-
id
61+
key
6262
}
6363
}
6464

@@ -80,84 +80,90 @@ impl ScheduleBuildPass for AutoInsertApplyDeferredPass {
8080
let mut sync_point_graph = dependency_flattened.clone();
8181
let topo = graph.topsort_graph(dependency_flattened, ReportCycles::Dependency)?;
8282

83-
fn set_has_conditions(graph: &ScheduleGraph, node: NodeId) -> bool {
84-
!graph.set_conditions_at(node).is_empty()
83+
fn set_has_conditions(graph: &ScheduleGraph, set: SystemSetKey) -> bool {
84+
!graph.set_conditions_at(set).is_empty()
8585
|| graph
8686
.hierarchy()
8787
.graph()
88-
.edges_directed(node, Direction::Incoming)
89-
.any(|(parent, _)| set_has_conditions(graph, parent))
88+
.edges_directed(NodeId::Set(set), Direction::Incoming)
89+
.any(|(parent, _)| {
90+
parent
91+
.as_set()
92+
.is_some_and(|p| set_has_conditions(graph, p))
93+
})
9094
}
9195

92-
fn system_has_conditions(graph: &ScheduleGraph, node: NodeId) -> bool {
93-
assert!(node.is_system());
94-
!graph.system_conditions[node.index()].is_empty()
96+
fn system_has_conditions(graph: &ScheduleGraph, key: SystemKey) -> bool {
97+
!graph.system_conditions[key].is_empty()
9598
|| graph
9699
.hierarchy()
97100
.graph()
98-
.edges_directed(node, Direction::Incoming)
99-
.any(|(parent, _)| set_has_conditions(graph, parent))
101+
.edges_directed(NodeId::System(key), Direction::Incoming)
102+
.any(|(parent, _)| {
103+
parent
104+
.as_set()
105+
.is_some_and(|p| set_has_conditions(graph, p))
106+
})
100107
}
101108

102-
let mut system_has_conditions_cache = HashMap::<usize, bool>::default();
103-
let mut is_valid_explicit_sync_point = |system: NodeId| {
104-
let index = system.index();
105-
is_apply_deferred(&graph.systems[index].get().unwrap().system)
109+
let mut system_has_conditions_cache = HashMap::<SystemKey, bool>::default();
110+
let mut is_valid_explicit_sync_point = |key: SystemKey| {
111+
is_apply_deferred(&graph.systems[key].get().unwrap().system)
106112
&& !*system_has_conditions_cache
107-
.entry(index)
108-
.or_insert_with(|| system_has_conditions(graph, system))
113+
.entry(key)
114+
.or_insert_with(|| system_has_conditions(graph, key))
109115
};
110116

111117
// Calculate the distance for each node.
112118
// The "distance" is the number of sync points between a node and the beginning of the graph.
113119
// Also store if a preceding edge would have added a sync point but was ignored to add it at
114120
// a later edge that is not ignored.
115-
let mut distances_and_pending_sync: HashMap<usize, (u32, bool)> =
121+
let mut distances_and_pending_sync: HashMap<SystemKey, (u32, bool)> =
116122
HashMap::with_capacity_and_hasher(topo.len(), Default::default());
117123

118124
// Keep track of any explicit sync nodes for a specific distance.
119125
let mut distance_to_explicit_sync_node: HashMap<u32, NodeId> = HashMap::default();
120126

121127
// Determine the distance for every node and collect the explicit sync points.
122128
for node in &topo {
129+
let &NodeId::System(key) = node else {
130+
panic!("Encountered a non-system node in the flattened dependency graph: {node:?}");
131+
};
132+
123133
let (node_distance, mut node_needs_sync) = distances_and_pending_sync
124-
.get(&node.index())
134+
.get(&key)
125135
.copied()
126136
.unwrap_or_default();
127137

128-
if is_valid_explicit_sync_point(*node) {
138+
if is_valid_explicit_sync_point(key) {
129139
// The distance of this sync point does not change anymore as the iteration order
130140
// makes sure that this node is no unvisited target of another node.
131141
// Because of this, the sync point can be stored for this distance to be reused as
132142
// automatically added sync points later.
133-
distance_to_explicit_sync_node.insert(node_distance, *node);
143+
distance_to_explicit_sync_node.insert(node_distance, NodeId::System(key));
134144

135145
// This node just did a sync, so the only reason to do another sync is if one was
136146
// explicitly scheduled afterwards.
137147
node_needs_sync = false;
138148
} else if !node_needs_sync {
139149
// No previous node has postponed sync points to add so check if the system itself
140150
// has deferred params that require a sync point to apply them.
141-
node_needs_sync = graph.systems[node.index()]
142-
.get()
143-
.unwrap()
144-
.system
145-
.has_deferred();
151+
node_needs_sync = graph.systems[key].get().unwrap().system.has_deferred();
146152
}
147153

148154
for target in dependency_flattened.neighbors_directed(*node, Direction::Outgoing) {
149-
let (target_distance, target_pending_sync) = distances_and_pending_sync
150-
.entry(target.index())
151-
.or_default();
155+
let NodeId::System(target) = target else {
156+
panic!("Encountered a non-system node in the flattened dependency graph: {target:?}");
157+
};
158+
let (target_distance, target_pending_sync) =
159+
distances_and_pending_sync.entry(target).or_default();
152160

153161
let mut edge_needs_sync = node_needs_sync;
154162
if node_needs_sync
155-
&& !graph.systems[target.index()]
156-
.get()
157-
.unwrap()
158-
.system
159-
.is_exclusive()
160-
&& self.no_sync_edges.contains(&(*node, target))
163+
&& !graph.systems[target].get().unwrap().system.is_exclusive()
164+
&& self
165+
.no_sync_edges
166+
.contains(&(*node, NodeId::System(target)))
161167
{
162168
// The node has deferred params to apply, but this edge is ignoring sync points.
163169
// Mark the target as 'delaying' those commands to a future edge and the current
@@ -182,14 +188,20 @@ impl ScheduleBuildPass for AutoInsertApplyDeferredPass {
182188
// Find any edges which have a different number of sync points between them and make sure
183189
// there is a sync point between them.
184190
for node in &topo {
191+
let &NodeId::System(key) = node else {
192+
panic!("Encountered a non-system node in the flattened dependency graph: {node:?}");
193+
};
185194
let (node_distance, _) = distances_and_pending_sync
186-
.get(&node.index())
195+
.get(&key)
187196
.copied()
188197
.unwrap_or_default();
189198

190199
for target in dependency_flattened.neighbors_directed(*node, Direction::Outgoing) {
200+
let NodeId::System(target) = target else {
201+
panic!("Encountered a non-system node in the flattened dependency graph: {target:?}");
202+
};
191203
let (target_distance, _) = distances_and_pending_sync
192-
.get(&target.index())
204+
.get(&target)
193205
.copied()
194206
.unwrap_or_default();
195207

@@ -198,7 +210,7 @@ impl ScheduleBuildPass for AutoInsertApplyDeferredPass {
198210
continue;
199211
}
200212

201-
if is_apply_deferred(&graph.systems[target.index()].get().unwrap().system) {
213+
if is_apply_deferred(&graph.systems[target].get().unwrap().system) {
202214
// We don't need to insert a sync point since ApplyDeferred is a sync point
203215
// already!
204216
continue;
@@ -210,10 +222,10 @@ impl ScheduleBuildPass for AutoInsertApplyDeferredPass {
210222
.unwrap_or_else(|| self.get_sync_point(graph, target_distance));
211223

212224
sync_point_graph.add_edge(*node, sync_point);
213-
sync_point_graph.add_edge(sync_point, target);
225+
sync_point_graph.add_edge(sync_point, NodeId::System(target));
214226

215227
// The edge without the sync point is now redundant.
216-
sync_point_graph.remove_edge(*node, target);
228+
sync_point_graph.remove_edge(*node, NodeId::System(target));
217229
}
218230
}
219231

@@ -223,34 +235,39 @@ impl ScheduleBuildPass for AutoInsertApplyDeferredPass {
223235

224236
fn collapse_set(
225237
&mut self,
226-
set: NodeId,
227-
systems: &[NodeId],
238+
set: SystemSetKey,
239+
systems: &[SystemKey],
228240
dependency_flattened: &DiGraph,
229241
) -> impl Iterator<Item = (NodeId, NodeId)> {
230242
if systems.is_empty() {
231243
// collapse dependencies for empty sets
232-
for a in dependency_flattened.neighbors_directed(set, Direction::Incoming) {
233-
for b in dependency_flattened.neighbors_directed(set, Direction::Outgoing) {
234-
if self.no_sync_edges.contains(&(a, set))
235-
&& self.no_sync_edges.contains(&(set, b))
244+
for a in dependency_flattened.neighbors_directed(NodeId::Set(set), Direction::Incoming)
245+
{
246+
for b in
247+
dependency_flattened.neighbors_directed(NodeId::Set(set), Direction::Outgoing)
248+
{
249+
if self.no_sync_edges.contains(&(a, NodeId::Set(set)))
250+
&& self.no_sync_edges.contains(&(NodeId::Set(set), b))
236251
{
237252
self.no_sync_edges.insert((a, b));
238253
}
239254
}
240255
}
241256
} else {
242-
for a in dependency_flattened.neighbors_directed(set, Direction::Incoming) {
257+
for a in dependency_flattened.neighbors_directed(NodeId::Set(set), Direction::Incoming)
258+
{
243259
for &sys in systems {
244-
if self.no_sync_edges.contains(&(a, set)) {
245-
self.no_sync_edges.insert((a, sys));
260+
if self.no_sync_edges.contains(&(a, NodeId::Set(set))) {
261+
self.no_sync_edges.insert((a, NodeId::System(sys)));
246262
}
247263
}
248264
}
249265

250-
for b in dependency_flattened.neighbors_directed(set, Direction::Outgoing) {
266+
for b in dependency_flattened.neighbors_directed(NodeId::Set(set), Direction::Outgoing)
267+
{
251268
for &sys in systems {
252-
if self.no_sync_edges.contains(&(set, b)) {
253-
self.no_sync_edges.insert((sys, b));
269+
if self.no_sync_edges.contains(&(NodeId::Set(set), b)) {
270+
self.no_sync_edges.insert((NodeId::System(sys), b));
254271
}
255272
}
256273
}

crates/bevy_ecs/src/schedule/executor/mod.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ use crate::{
2020
error::{BevyError, ErrorContext, Result},
2121
prelude::{IntoSystemSet, SystemSet},
2222
query::FilteredAccessSet,
23-
schedule::{ConditionWithAccess, InternedSystemSet, NodeId, SystemTypeSet, SystemWithAccess},
23+
schedule::{
24+
ConditionWithAccess, InternedSystemSet, SystemKey, SystemSetKey, SystemTypeSet,
25+
SystemWithAccess,
26+
},
2427
system::{ScheduleSystem, System, SystemIn, SystemParamValidationError, SystemStateFlags},
2528
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World},
2629
};
@@ -73,7 +76,7 @@ pub enum ExecutorKind {
7376
#[derive(Default)]
7477
pub struct SystemSchedule {
7578
/// List of system node ids.
76-
pub(super) system_ids: Vec<NodeId>,
79+
pub(super) system_ids: Vec<SystemKey>,
7780
/// Indexed by system node id.
7881
pub(super) systems: Vec<SystemWithAccess>,
7982
/// Indexed by system node id.
@@ -96,7 +99,7 @@ pub struct SystemSchedule {
9699
/// List of sets containing the system that have conditions
97100
pub(super) sets_with_conditions_of_systems: Vec<FixedBitSet>,
98101
/// List of system set node ids.
99-
pub(super) set_ids: Vec<NodeId>,
102+
pub(super) set_ids: Vec<SystemSetKey>,
100103
/// Indexed by system set node id.
101104
pub(super) set_conditions: Vec<Vec<ConditionWithAccess>>,
102105
/// Indexed by system set node id.

0 commit comments

Comments
 (0)