Use SlotMap
s to store systems and system sets in Schedule
s (#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.
This commit is contained in:
parent
bbf91a6964
commit
ebf87f56ef
@ -10,7 +10,7 @@ keywords = ["game", "engine", "gamedev", "graphics", "bevy"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/bevyengine/bevy"
|
||||
documentation = "https://docs.rs/bevy"
|
||||
rust-version = "1.86.0"
|
||||
rust-version = "1.88.0"
|
||||
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
|
@ -119,6 +119,7 @@ tracing = { version = "0.1", default-features = false, optional = true }
|
||||
log = { version = "0.4", default-features = false }
|
||||
bumpalo = "3"
|
||||
subsecond = { version = "0.7.0-alpha.1", optional = true }
|
||||
slotmap = { version = "1.0.7", default-features = false }
|
||||
|
||||
concurrent-queue = { version = "2.5.0", default-features = false }
|
||||
[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]
|
||||
|
@ -2,8 +2,11 @@ use alloc::{boxed::Box, collections::BTreeSet, vec::Vec};
|
||||
|
||||
use bevy_platform::collections::HashMap;
|
||||
|
||||
use crate::system::IntoSystem;
|
||||
use crate::world::World;
|
||||
use crate::{
|
||||
schedule::{SystemKey, SystemSetKey},
|
||||
system::IntoSystem,
|
||||
world::World,
|
||||
};
|
||||
|
||||
use super::{
|
||||
is_apply_deferred, ApplyDeferred, DiGraph, Direction, NodeId, ReportCycles, ScheduleBuildError,
|
||||
@ -36,29 +39,26 @@ impl AutoInsertApplyDeferredPass {
|
||||
self.auto_sync_node_ids
|
||||
.get(&distance)
|
||||
.copied()
|
||||
.or_else(|| {
|
||||
let node_id = self.add_auto_sync(graph);
|
||||
.unwrap_or_else(|| {
|
||||
let node_id = NodeId::System(self.add_auto_sync(graph));
|
||||
self.auto_sync_node_ids.insert(distance, node_id);
|
||||
Some(node_id)
|
||||
node_id
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
/// add an [`ApplyDeferred`] system with no config
|
||||
fn add_auto_sync(&mut self, graph: &mut ScheduleGraph) -> NodeId {
|
||||
let id = NodeId::System(graph.systems.len());
|
||||
|
||||
graph
|
||||
fn add_auto_sync(&mut self, graph: &mut ScheduleGraph) -> SystemKey {
|
||||
let key = graph
|
||||
.systems
|
||||
.push(SystemNode::new(Box::new(IntoSystem::into_system(
|
||||
.insert(SystemNode::new(Box::new(IntoSystem::into_system(
|
||||
ApplyDeferred,
|
||||
))));
|
||||
graph.system_conditions.push(Vec::new());
|
||||
graph.system_conditions.insert(key, Vec::new());
|
||||
|
||||
// ignore ambiguities with auto sync points
|
||||
// They aren't under user control, so no one should know or care.
|
||||
graph.ambiguous_with_all.insert(id);
|
||||
graph.ambiguous_with_all.insert(NodeId::System(key));
|
||||
|
||||
id
|
||||
key
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,39 +80,45 @@ impl ScheduleBuildPass for AutoInsertApplyDeferredPass {
|
||||
let mut sync_point_graph = dependency_flattened.clone();
|
||||
let topo = graph.topsort_graph(dependency_flattened, ReportCycles::Dependency)?;
|
||||
|
||||
fn set_has_conditions(graph: &ScheduleGraph, node: NodeId) -> bool {
|
||||
!graph.set_conditions_at(node).is_empty()
|
||||
fn set_has_conditions(graph: &ScheduleGraph, set: SystemSetKey) -> bool {
|
||||
!graph.set_conditions_at(set).is_empty()
|
||||
|| graph
|
||||
.hierarchy()
|
||||
.graph()
|
||||
.edges_directed(node, Direction::Incoming)
|
||||
.any(|(parent, _)| set_has_conditions(graph, parent))
|
||||
.edges_directed(NodeId::Set(set), Direction::Incoming)
|
||||
.any(|(parent, _)| {
|
||||
parent
|
||||
.as_set()
|
||||
.is_some_and(|p| set_has_conditions(graph, p))
|
||||
})
|
||||
}
|
||||
|
||||
fn system_has_conditions(graph: &ScheduleGraph, node: NodeId) -> bool {
|
||||
assert!(node.is_system());
|
||||
!graph.system_conditions[node.index()].is_empty()
|
||||
fn system_has_conditions(graph: &ScheduleGraph, key: SystemKey) -> bool {
|
||||
!graph.system_conditions[key].is_empty()
|
||||
|| graph
|
||||
.hierarchy()
|
||||
.graph()
|
||||
.edges_directed(node, Direction::Incoming)
|
||||
.any(|(parent, _)| set_has_conditions(graph, parent))
|
||||
.edges_directed(NodeId::System(key), Direction::Incoming)
|
||||
.any(|(parent, _)| {
|
||||
parent
|
||||
.as_set()
|
||||
.is_some_and(|p| set_has_conditions(graph, p))
|
||||
})
|
||||
}
|
||||
|
||||
let mut system_has_conditions_cache = HashMap::<usize, bool>::default();
|
||||
let mut is_valid_explicit_sync_point = |system: NodeId| {
|
||||
let index = system.index();
|
||||
is_apply_deferred(&graph.systems[index].get().unwrap().system)
|
||||
let mut system_has_conditions_cache = HashMap::<SystemKey, bool>::default();
|
||||
let mut is_valid_explicit_sync_point = |key: SystemKey| {
|
||||
is_apply_deferred(&graph.systems[key].get().unwrap().system)
|
||||
&& !*system_has_conditions_cache
|
||||
.entry(index)
|
||||
.or_insert_with(|| system_has_conditions(graph, system))
|
||||
.entry(key)
|
||||
.or_insert_with(|| system_has_conditions(graph, key))
|
||||
};
|
||||
|
||||
// Calculate the distance for each node.
|
||||
// The "distance" is the number of sync points between a node and the beginning of the graph.
|
||||
// Also store if a preceding edge would have added a sync point but was ignored to add it at
|
||||
// a later edge that is not ignored.
|
||||
let mut distances_and_pending_sync: HashMap<usize, (u32, bool)> =
|
||||
let mut distances_and_pending_sync: HashMap<SystemKey, (u32, bool)> =
|
||||
HashMap::with_capacity_and_hasher(topo.len(), Default::default());
|
||||
|
||||
// Keep track of any explicit sync nodes for a specific distance.
|
||||
@ -120,17 +126,21 @@ impl ScheduleBuildPass for AutoInsertApplyDeferredPass {
|
||||
|
||||
// Determine the distance for every node and collect the explicit sync points.
|
||||
for node in &topo {
|
||||
let &NodeId::System(key) = node else {
|
||||
panic!("Encountered a non-system node in the flattened dependency graph: {node:?}");
|
||||
};
|
||||
|
||||
let (node_distance, mut node_needs_sync) = distances_and_pending_sync
|
||||
.get(&node.index())
|
||||
.get(&key)
|
||||
.copied()
|
||||
.unwrap_or_default();
|
||||
|
||||
if is_valid_explicit_sync_point(*node) {
|
||||
if is_valid_explicit_sync_point(key) {
|
||||
// The distance of this sync point does not change anymore as the iteration order
|
||||
// makes sure that this node is no unvisited target of another node.
|
||||
// Because of this, the sync point can be stored for this distance to be reused as
|
||||
// automatically added sync points later.
|
||||
distance_to_explicit_sync_node.insert(node_distance, *node);
|
||||
distance_to_explicit_sync_node.insert(node_distance, NodeId::System(key));
|
||||
|
||||
// This node just did a sync, so the only reason to do another sync is if one was
|
||||
// explicitly scheduled afterwards.
|
||||
@ -138,26 +148,22 @@ impl ScheduleBuildPass for AutoInsertApplyDeferredPass {
|
||||
} else if !node_needs_sync {
|
||||
// No previous node has postponed sync points to add so check if the system itself
|
||||
// has deferred params that require a sync point to apply them.
|
||||
node_needs_sync = graph.systems[node.index()]
|
||||
.get()
|
||||
.unwrap()
|
||||
.system
|
||||
.has_deferred();
|
||||
node_needs_sync = graph.systems[key].get().unwrap().system.has_deferred();
|
||||
}
|
||||
|
||||
for target in dependency_flattened.neighbors_directed(*node, Direction::Outgoing) {
|
||||
let (target_distance, target_pending_sync) = distances_and_pending_sync
|
||||
.entry(target.index())
|
||||
.or_default();
|
||||
let NodeId::System(target) = target else {
|
||||
panic!("Encountered a non-system node in the flattened dependency graph: {target:?}");
|
||||
};
|
||||
let (target_distance, target_pending_sync) =
|
||||
distances_and_pending_sync.entry(target).or_default();
|
||||
|
||||
let mut edge_needs_sync = node_needs_sync;
|
||||
if node_needs_sync
|
||||
&& !graph.systems[target.index()]
|
||||
.get()
|
||||
.unwrap()
|
||||
.system
|
||||
.is_exclusive()
|
||||
&& self.no_sync_edges.contains(&(*node, target))
|
||||
&& !graph.systems[target].get().unwrap().system.is_exclusive()
|
||||
&& self
|
||||
.no_sync_edges
|
||||
.contains(&(*node, NodeId::System(target)))
|
||||
{
|
||||
// The node has deferred params to apply, but this edge is ignoring sync points.
|
||||
// Mark the target as 'delaying' those commands to a future edge and the current
|
||||
@ -182,14 +188,20 @@ impl ScheduleBuildPass for AutoInsertApplyDeferredPass {
|
||||
// Find any edges which have a different number of sync points between them and make sure
|
||||
// there is a sync point between them.
|
||||
for node in &topo {
|
||||
let &NodeId::System(key) = node else {
|
||||
panic!("Encountered a non-system node in the flattened dependency graph: {node:?}");
|
||||
};
|
||||
let (node_distance, _) = distances_and_pending_sync
|
||||
.get(&node.index())
|
||||
.get(&key)
|
||||
.copied()
|
||||
.unwrap_or_default();
|
||||
|
||||
for target in dependency_flattened.neighbors_directed(*node, Direction::Outgoing) {
|
||||
let NodeId::System(target) = target else {
|
||||
panic!("Encountered a non-system node in the flattened dependency graph: {target:?}");
|
||||
};
|
||||
let (target_distance, _) = distances_and_pending_sync
|
||||
.get(&target.index())
|
||||
.get(&target)
|
||||
.copied()
|
||||
.unwrap_or_default();
|
||||
|
||||
@ -198,7 +210,7 @@ impl ScheduleBuildPass for AutoInsertApplyDeferredPass {
|
||||
continue;
|
||||
}
|
||||
|
||||
if is_apply_deferred(&graph.systems[target.index()].get().unwrap().system) {
|
||||
if is_apply_deferred(&graph.systems[target].get().unwrap().system) {
|
||||
// We don't need to insert a sync point since ApplyDeferred is a sync point
|
||||
// already!
|
||||
continue;
|
||||
@ -210,10 +222,10 @@ impl ScheduleBuildPass for AutoInsertApplyDeferredPass {
|
||||
.unwrap_or_else(|| self.get_sync_point(graph, target_distance));
|
||||
|
||||
sync_point_graph.add_edge(*node, sync_point);
|
||||
sync_point_graph.add_edge(sync_point, target);
|
||||
sync_point_graph.add_edge(sync_point, NodeId::System(target));
|
||||
|
||||
// The edge without the sync point is now redundant.
|
||||
sync_point_graph.remove_edge(*node, target);
|
||||
sync_point_graph.remove_edge(*node, NodeId::System(target));
|
||||
}
|
||||
}
|
||||
|
||||
@ -223,34 +235,39 @@ impl ScheduleBuildPass for AutoInsertApplyDeferredPass {
|
||||
|
||||
fn collapse_set(
|
||||
&mut self,
|
||||
set: NodeId,
|
||||
systems: &[NodeId],
|
||||
set: SystemSetKey,
|
||||
systems: &[SystemKey],
|
||||
dependency_flattened: &DiGraph,
|
||||
) -> impl Iterator<Item = (NodeId, NodeId)> {
|
||||
if systems.is_empty() {
|
||||
// collapse dependencies for empty sets
|
||||
for a in dependency_flattened.neighbors_directed(set, Direction::Incoming) {
|
||||
for b in dependency_flattened.neighbors_directed(set, Direction::Outgoing) {
|
||||
if self.no_sync_edges.contains(&(a, set))
|
||||
&& self.no_sync_edges.contains(&(set, b))
|
||||
for a in dependency_flattened.neighbors_directed(NodeId::Set(set), Direction::Incoming)
|
||||
{
|
||||
for b in
|
||||
dependency_flattened.neighbors_directed(NodeId::Set(set), Direction::Outgoing)
|
||||
{
|
||||
if self.no_sync_edges.contains(&(a, NodeId::Set(set)))
|
||||
&& self.no_sync_edges.contains(&(NodeId::Set(set), b))
|
||||
{
|
||||
self.no_sync_edges.insert((a, b));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for a in dependency_flattened.neighbors_directed(set, Direction::Incoming) {
|
||||
for a in dependency_flattened.neighbors_directed(NodeId::Set(set), Direction::Incoming)
|
||||
{
|
||||
for &sys in systems {
|
||||
if self.no_sync_edges.contains(&(a, set)) {
|
||||
self.no_sync_edges.insert((a, sys));
|
||||
if self.no_sync_edges.contains(&(a, NodeId::Set(set))) {
|
||||
self.no_sync_edges.insert((a, NodeId::System(sys)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for b in dependency_flattened.neighbors_directed(set, Direction::Outgoing) {
|
||||
for b in dependency_flattened.neighbors_directed(NodeId::Set(set), Direction::Outgoing)
|
||||
{
|
||||
for &sys in systems {
|
||||
if self.no_sync_edges.contains(&(set, b)) {
|
||||
self.no_sync_edges.insert((sys, b));
|
||||
if self.no_sync_edges.contains(&(NodeId::Set(set), b)) {
|
||||
self.no_sync_edges.insert((NodeId::System(sys), b));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,10 @@ use crate::{
|
||||
error::{BevyError, ErrorContext, Result},
|
||||
prelude::{IntoSystemSet, SystemSet},
|
||||
query::FilteredAccessSet,
|
||||
schedule::{ConditionWithAccess, InternedSystemSet, NodeId, SystemTypeSet, SystemWithAccess},
|
||||
schedule::{
|
||||
ConditionWithAccess, InternedSystemSet, SystemKey, SystemSetKey, SystemTypeSet,
|
||||
SystemWithAccess,
|
||||
},
|
||||
system::{ScheduleSystem, System, SystemIn, SystemParamValidationError, SystemStateFlags},
|
||||
world::{unsafe_world_cell::UnsafeWorldCell, DeferredWorld, World},
|
||||
};
|
||||
@ -73,7 +76,7 @@ pub enum ExecutorKind {
|
||||
#[derive(Default)]
|
||||
pub struct SystemSchedule {
|
||||
/// List of system node ids.
|
||||
pub(super) system_ids: Vec<NodeId>,
|
||||
pub(super) system_ids: Vec<SystemKey>,
|
||||
/// Indexed by system node id.
|
||||
pub(super) systems: Vec<SystemWithAccess>,
|
||||
/// Indexed by system node id.
|
||||
@ -96,7 +99,7 @@ pub struct SystemSchedule {
|
||||
/// List of sets containing the system that have conditions
|
||||
pub(super) sets_with_conditions_of_systems: Vec<FixedBitSet>,
|
||||
/// List of system set node ids.
|
||||
pub(super) set_ids: Vec<NodeId>,
|
||||
pub(super) set_ids: Vec<SystemSetKey>,
|
||||
/// Indexed by system set node id.
|
||||
pub(super) set_conditions: Vec<Vec<ConditionWithAccess>>,
|
||||
/// Indexed by system set node id.
|
||||
|
@ -11,6 +11,7 @@ use core::{
|
||||
hash::{BuildHasher, Hash},
|
||||
};
|
||||
use indexmap::IndexMap;
|
||||
use slotmap::{Key, KeyData};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use super::NodeId;
|
||||
@ -298,7 +299,7 @@ impl Direction {
|
||||
/// Compact storage of a [`NodeId`] and a [`Direction`].
|
||||
#[derive(Clone, Copy)]
|
||||
struct CompactNodeIdAndDirection {
|
||||
index: usize,
|
||||
key: KeyData,
|
||||
is_system: bool,
|
||||
direction: Direction,
|
||||
}
|
||||
@ -310,27 +311,30 @@ impl fmt::Debug for CompactNodeIdAndDirection {
|
||||
}
|
||||
|
||||
impl CompactNodeIdAndDirection {
|
||||
const fn store(node: NodeId, direction: Direction) -> Self {
|
||||
let index = node.index();
|
||||
fn store(node: NodeId, direction: Direction) -> Self {
|
||||
let key = match node {
|
||||
NodeId::System(key) => key.data(),
|
||||
NodeId::Set(key) => key.data(),
|
||||
};
|
||||
let is_system = node.is_system();
|
||||
|
||||
Self {
|
||||
index,
|
||||
key,
|
||||
is_system,
|
||||
direction,
|
||||
}
|
||||
}
|
||||
|
||||
const fn load(self) -> (NodeId, Direction) {
|
||||
fn load(self) -> (NodeId, Direction) {
|
||||
let Self {
|
||||
index,
|
||||
key,
|
||||
is_system,
|
||||
direction,
|
||||
} = self;
|
||||
|
||||
let node = match is_system {
|
||||
true => NodeId::System(index),
|
||||
false => NodeId::Set(index),
|
||||
true => NodeId::System(key.into()),
|
||||
false => NodeId::Set(key.into()),
|
||||
};
|
||||
|
||||
(node, direction)
|
||||
@ -340,8 +344,8 @@ impl CompactNodeIdAndDirection {
|
||||
/// Compact storage of a [`NodeId`] pair.
|
||||
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
|
||||
struct CompactNodeIdPair {
|
||||
index_a: usize,
|
||||
index_b: usize,
|
||||
key_a: KeyData,
|
||||
key_b: KeyData,
|
||||
is_system_a: bool,
|
||||
is_system_b: bool,
|
||||
}
|
||||
@ -353,37 +357,43 @@ impl fmt::Debug for CompactNodeIdPair {
|
||||
}
|
||||
|
||||
impl CompactNodeIdPair {
|
||||
const fn store(a: NodeId, b: NodeId) -> Self {
|
||||
let index_a = a.index();
|
||||
fn store(a: NodeId, b: NodeId) -> Self {
|
||||
let key_a = match a {
|
||||
NodeId::System(index) => index.data(),
|
||||
NodeId::Set(index) => index.data(),
|
||||
};
|
||||
let is_system_a = a.is_system();
|
||||
|
||||
let index_b = b.index();
|
||||
let key_b = match b {
|
||||
NodeId::System(index) => index.data(),
|
||||
NodeId::Set(index) => index.data(),
|
||||
};
|
||||
let is_system_b = b.is_system();
|
||||
|
||||
Self {
|
||||
index_a,
|
||||
index_b,
|
||||
key_a,
|
||||
key_b,
|
||||
is_system_a,
|
||||
is_system_b,
|
||||
}
|
||||
}
|
||||
|
||||
const fn load(self) -> (NodeId, NodeId) {
|
||||
fn load(self) -> (NodeId, NodeId) {
|
||||
let Self {
|
||||
index_a,
|
||||
index_b,
|
||||
key_a,
|
||||
key_b,
|
||||
is_system_a,
|
||||
is_system_b,
|
||||
} = self;
|
||||
|
||||
let a = match is_system_a {
|
||||
true => NodeId::System(index_a),
|
||||
false => NodeId::Set(index_a),
|
||||
true => NodeId::System(key_a.into()),
|
||||
false => NodeId::Set(key_a.into()),
|
||||
};
|
||||
|
||||
let b = match is_system_b {
|
||||
true => NodeId::System(index_b),
|
||||
false => NodeId::Set(index_b),
|
||||
true => NodeId::System(key_b.into()),
|
||||
false => NodeId::Set(key_b.into()),
|
||||
};
|
||||
|
||||
(a, b)
|
||||
@ -392,8 +402,11 @@ impl CompactNodeIdPair {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::schedule::SystemKey;
|
||||
|
||||
use super::*;
|
||||
use alloc::vec;
|
||||
use slotmap::SlotMap;
|
||||
|
||||
/// The `Graph` type _must_ preserve the order that nodes are inserted in if
|
||||
/// no removals occur. Removals are permitted to swap the latest node into the
|
||||
@ -402,37 +415,43 @@ mod tests {
|
||||
fn node_order_preservation() {
|
||||
use NodeId::System;
|
||||
|
||||
let mut slotmap = SlotMap::<SystemKey, ()>::with_key();
|
||||
let mut graph = <DiGraph>::default();
|
||||
|
||||
graph.add_node(System(1));
|
||||
graph.add_node(System(2));
|
||||
graph.add_node(System(3));
|
||||
graph.add_node(System(4));
|
||||
let sys1 = slotmap.insert(());
|
||||
let sys2 = slotmap.insert(());
|
||||
let sys3 = slotmap.insert(());
|
||||
let sys4 = slotmap.insert(());
|
||||
|
||||
graph.add_node(System(sys1));
|
||||
graph.add_node(System(sys2));
|
||||
graph.add_node(System(sys3));
|
||||
graph.add_node(System(sys4));
|
||||
|
||||
assert_eq!(
|
||||
graph.nodes().collect::<Vec<_>>(),
|
||||
vec![System(1), System(2), System(3), System(4)]
|
||||
vec![System(sys1), System(sys2), System(sys3), System(sys4)]
|
||||
);
|
||||
|
||||
graph.remove_node(System(1));
|
||||
graph.remove_node(System(sys1));
|
||||
|
||||
assert_eq!(
|
||||
graph.nodes().collect::<Vec<_>>(),
|
||||
vec![System(4), System(2), System(3)]
|
||||
vec![System(sys4), System(sys2), System(sys3)]
|
||||
);
|
||||
|
||||
graph.remove_node(System(4));
|
||||
graph.remove_node(System(sys4));
|
||||
|
||||
assert_eq!(
|
||||
graph.nodes().collect::<Vec<_>>(),
|
||||
vec![System(3), System(2)]
|
||||
vec![System(sys3), System(sys2)]
|
||||
);
|
||||
|
||||
graph.remove_node(System(2));
|
||||
graph.remove_node(System(sys2));
|
||||
|
||||
assert_eq!(graph.nodes().collect::<Vec<_>>(), vec![System(3)]);
|
||||
assert_eq!(graph.nodes().collect::<Vec<_>>(), vec![System(sys3)]);
|
||||
|
||||
graph.remove_node(System(3));
|
||||
graph.remove_node(System(sys3));
|
||||
|
||||
assert_eq!(graph.nodes().collect::<Vec<_>>(), vec![]);
|
||||
}
|
||||
@ -444,18 +463,26 @@ mod tests {
|
||||
fn strongly_connected_components() {
|
||||
use NodeId::System;
|
||||
|
||||
let mut slotmap = SlotMap::<SystemKey, ()>::with_key();
|
||||
let mut graph = <DiGraph>::default();
|
||||
|
||||
graph.add_edge(System(1), System(2));
|
||||
graph.add_edge(System(2), System(1));
|
||||
let sys1 = slotmap.insert(());
|
||||
let sys2 = slotmap.insert(());
|
||||
let sys3 = slotmap.insert(());
|
||||
let sys4 = slotmap.insert(());
|
||||
let sys5 = slotmap.insert(());
|
||||
let sys6 = slotmap.insert(());
|
||||
|
||||
graph.add_edge(System(2), System(3));
|
||||
graph.add_edge(System(3), System(2));
|
||||
graph.add_edge(System(sys1), System(sys2));
|
||||
graph.add_edge(System(sys2), System(sys1));
|
||||
|
||||
graph.add_edge(System(4), System(5));
|
||||
graph.add_edge(System(5), System(4));
|
||||
graph.add_edge(System(sys2), System(sys3));
|
||||
graph.add_edge(System(sys3), System(sys2));
|
||||
|
||||
graph.add_edge(System(6), System(2));
|
||||
graph.add_edge(System(sys4), System(sys5));
|
||||
graph.add_edge(System(sys5), System(sys4));
|
||||
|
||||
graph.add_edge(System(sys6), System(sys2));
|
||||
|
||||
let sccs = graph
|
||||
.iter_sccs()
|
||||
@ -465,9 +492,9 @@ mod tests {
|
||||
assert_eq!(
|
||||
sccs,
|
||||
vec![
|
||||
vec![System(3), System(2), System(1)],
|
||||
vec![System(5), System(4)],
|
||||
vec![System(6)]
|
||||
vec![System(sys3), System(sys2), System(sys1)],
|
||||
vec![System(sys5), System(sys4)],
|
||||
vec![System(sys6)]
|
||||
]
|
||||
);
|
||||
}
|
||||
|
@ -1,24 +1,19 @@
|
||||
use core::fmt::Debug;
|
||||
|
||||
use crate::schedule::{SystemKey, SystemSetKey};
|
||||
|
||||
/// Unique identifier for a system or system set stored in a [`ScheduleGraph`].
|
||||
///
|
||||
/// [`ScheduleGraph`]: crate::schedule::ScheduleGraph
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub enum NodeId {
|
||||
/// Identifier for a system.
|
||||
System(usize),
|
||||
System(SystemKey),
|
||||
/// Identifier for a system set.
|
||||
Set(usize),
|
||||
Set(SystemSetKey),
|
||||
}
|
||||
|
||||
impl NodeId {
|
||||
/// Returns the internal integer value.
|
||||
pub const fn index(&self) -> usize {
|
||||
match self {
|
||||
NodeId::System(index) | NodeId::Set(index) => *index,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if the identified node is a system.
|
||||
pub const fn is_system(&self) -> bool {
|
||||
matches!(self, NodeId::System(_))
|
||||
@ -29,19 +24,19 @@ impl NodeId {
|
||||
matches!(self, NodeId::Set(_))
|
||||
}
|
||||
|
||||
/// Compare this [`NodeId`] with another.
|
||||
pub const fn cmp(&self, other: &Self) -> core::cmp::Ordering {
|
||||
use core::cmp::Ordering::{Equal, Greater, Less};
|
||||
use NodeId::{Set, System};
|
||||
/// Returns the system key if the node is a system, otherwise `None`.
|
||||
pub const fn as_system(&self) -> Option<SystemKey> {
|
||||
match self {
|
||||
NodeId::System(system) => Some(*system),
|
||||
NodeId::Set(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
match (self, other) {
|
||||
(System(a), System(b)) | (Set(a), Set(b)) => match a.checked_sub(*b) {
|
||||
None => Less,
|
||||
Some(0) => Equal,
|
||||
Some(_) => Greater,
|
||||
},
|
||||
(System(_), Set(_)) => Less,
|
||||
(Set(_), System(_)) => Greater,
|
||||
/// Returns the system set key if the node is a system set, otherwise `None`.
|
||||
pub const fn as_set(&self) -> Option<SystemSetKey> {
|
||||
match self {
|
||||
NodeId::System(_) => None,
|
||||
NodeId::Set(set) => Some(*set),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,10 @@ use alloc::{boxed::Box, vec::Vec};
|
||||
use core::any::{Any, TypeId};
|
||||
|
||||
use super::{DiGraph, NodeId, ScheduleBuildError, ScheduleGraph};
|
||||
use crate::world::World;
|
||||
use crate::{
|
||||
schedule::{SystemKey, SystemSetKey},
|
||||
world::World,
|
||||
};
|
||||
use bevy_utils::TypeIdMap;
|
||||
use core::fmt::Debug;
|
||||
|
||||
@ -19,8 +22,8 @@ pub trait ScheduleBuildPass: Send + Sync + Debug + 'static {
|
||||
/// Instead of modifying the graph directly, this method should return an iterator of edges to add to the graph.
|
||||
fn collapse_set(
|
||||
&mut self,
|
||||
set: NodeId,
|
||||
systems: &[NodeId],
|
||||
set: SystemSetKey,
|
||||
systems: &[SystemKey],
|
||||
dependency_flattened: &DiGraph,
|
||||
) -> impl Iterator<Item = (NodeId, NodeId)>;
|
||||
|
||||
@ -44,8 +47,8 @@ pub(super) trait ScheduleBuildPassObj: Send + Sync + Debug {
|
||||
|
||||
fn collapse_set(
|
||||
&mut self,
|
||||
set: NodeId,
|
||||
systems: &[NodeId],
|
||||
set: SystemSetKey,
|
||||
systems: &[SystemKey],
|
||||
dependency_flattened: &DiGraph,
|
||||
dependencies_to_add: &mut Vec<(NodeId, NodeId)>,
|
||||
);
|
||||
@ -63,8 +66,8 @@ impl<T: ScheduleBuildPass> ScheduleBuildPassObj for T {
|
||||
}
|
||||
fn collapse_set(
|
||||
&mut self,
|
||||
set: NodeId,
|
||||
systems: &[NodeId],
|
||||
set: SystemSetKey,
|
||||
systems: &[SystemKey],
|
||||
dependency_flattened: &DiGraph,
|
||||
dependencies_to_add: &mut Vec<(NodeId, NodeId)>,
|
||||
) {
|
||||
|
@ -19,6 +19,7 @@ use core::{
|
||||
use fixedbitset::FixedBitSet;
|
||||
use log::{error, info, warn};
|
||||
use pass::ScheduleBuildPassObj;
|
||||
use slotmap::{new_key_type, SecondaryMap, SlotMap};
|
||||
use thiserror::Error;
|
||||
#[cfg(feature = "trace")]
|
||||
use tracing::info_span;
|
||||
@ -404,7 +405,9 @@ impl Schedule {
|
||||
);
|
||||
};
|
||||
|
||||
self.graph.ambiguous_with.add_edge(a_id, b_id);
|
||||
self.graph
|
||||
.ambiguous_with
|
||||
.add_edge(NodeId::Set(a_id), NodeId::Set(b_id));
|
||||
|
||||
self
|
||||
}
|
||||
@ -599,7 +602,7 @@ impl Schedule {
|
||||
/// schedule has never been initialized or run.
|
||||
pub fn systems(
|
||||
&self,
|
||||
) -> Result<impl Iterator<Item = (NodeId, &ScheduleSystem)> + Sized, ScheduleNotInitialized>
|
||||
) -> Result<impl Iterator<Item = (SystemKey, &ScheduleSystem)> + Sized, ScheduleNotInitialized>
|
||||
{
|
||||
if !self.executor_initialized {
|
||||
return Err(ScheduleNotInitialized);
|
||||
@ -610,7 +613,7 @@ impl Schedule {
|
||||
.system_ids
|
||||
.iter()
|
||||
.zip(&self.executable.systems)
|
||||
.map(|(node_id, system)| (*node_id, &system.system));
|
||||
.map(|(&node_id, system)| (node_id, &system.system));
|
||||
|
||||
Ok(iter)
|
||||
}
|
||||
@ -742,6 +745,21 @@ impl SystemNode {
|
||||
}
|
||||
}
|
||||
|
||||
new_key_type! {
|
||||
/// A unique identifier for a system in a [`ScheduleGraph`].
|
||||
pub struct SystemKey;
|
||||
/// A unique identifier for a system set in a [`ScheduleGraph`].
|
||||
pub struct SystemSetKey;
|
||||
}
|
||||
|
||||
enum UninitializedId {
|
||||
System(SystemKey),
|
||||
Set {
|
||||
key: SystemSetKey,
|
||||
first_uninit_condition: usize,
|
||||
},
|
||||
}
|
||||
|
||||
/// Metadata for a [`Schedule`].
|
||||
///
|
||||
/// The order isn't optimized; calling `ScheduleGraph::build_schedule` will return a
|
||||
@ -749,18 +767,18 @@ impl SystemNode {
|
||||
#[derive(Default)]
|
||||
pub struct ScheduleGraph {
|
||||
/// List of systems in the schedule
|
||||
pub systems: Vec<SystemNode>,
|
||||
pub systems: SlotMap<SystemKey, SystemNode>,
|
||||
/// List of conditions for each system, in the same order as `systems`
|
||||
pub system_conditions: Vec<Vec<ConditionWithAccess>>,
|
||||
pub system_conditions: SecondaryMap<SystemKey, Vec<ConditionWithAccess>>,
|
||||
/// List of system sets in the schedule
|
||||
system_sets: Vec<SystemSetNode>,
|
||||
system_sets: SlotMap<SystemSetKey, SystemSetNode>,
|
||||
/// List of conditions for each system set, in the same order as `system_sets`
|
||||
system_set_conditions: Vec<Vec<ConditionWithAccess>>,
|
||||
system_set_conditions: SecondaryMap<SystemSetKey, Vec<ConditionWithAccess>>,
|
||||
/// Map from system set to node id
|
||||
system_set_ids: HashMap<InternedSystemSet, NodeId>,
|
||||
system_set_ids: HashMap<InternedSystemSet, SystemSetKey>,
|
||||
/// Systems that have not been initialized yet; for system sets, we store the index of the first uninitialized condition
|
||||
/// (all the conditions after that index still need to be initialized)
|
||||
uninit: Vec<(NodeId, usize)>,
|
||||
uninit: Vec<UninitializedId>,
|
||||
/// Directed acyclic graph of the hierarchy (which systems/sets are children of which sets)
|
||||
hierarchy: Dag,
|
||||
/// Directed acyclic graph of the dependency (which systems/sets have to run before which other systems/sets)
|
||||
@ -768,7 +786,7 @@ pub struct ScheduleGraph {
|
||||
ambiguous_with: UnGraph,
|
||||
/// Nodes that are allowed to have ambiguous ordering relationship with any other systems.
|
||||
pub ambiguous_with_all: HashSet<NodeId>,
|
||||
conflicting_systems: Vec<(NodeId, NodeId, Vec<ComponentId>)>,
|
||||
conflicting_systems: Vec<(SystemKey, SystemKey, Vec<ComponentId>)>,
|
||||
anonymous_sets: usize,
|
||||
changed: bool,
|
||||
settings: ScheduleBuildSettings,
|
||||
@ -780,10 +798,10 @@ impl ScheduleGraph {
|
||||
/// Creates an empty [`ScheduleGraph`] with default settings.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
systems: Vec::new(),
|
||||
system_conditions: Vec::new(),
|
||||
system_sets: Vec::new(),
|
||||
system_set_conditions: Vec::new(),
|
||||
systems: SlotMap::with_key(),
|
||||
system_conditions: SecondaryMap::new(),
|
||||
system_sets: SlotMap::with_key(),
|
||||
system_set_conditions: SecondaryMap::new(),
|
||||
system_set_ids: HashMap::default(),
|
||||
uninit: Vec::new(),
|
||||
hierarchy: Dag::new(),
|
||||
@ -798,14 +816,11 @@ impl ScheduleGraph {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the system at the given [`NodeId`], if it exists.
|
||||
pub fn get_system_at(&self, id: NodeId) -> Option<&ScheduleSystem> {
|
||||
if !id.is_system() {
|
||||
return None;
|
||||
}
|
||||
/// Returns the system at the given [`SystemKey`], if it exists.
|
||||
pub fn get_system_at(&self, key: SystemKey) -> Option<&ScheduleSystem> {
|
||||
self.systems
|
||||
.get(id.index())
|
||||
.and_then(|system| system.inner.as_ref())
|
||||
.get(key)
|
||||
.and_then(|system| system.get())
|
||||
.map(|system| &system.system)
|
||||
}
|
||||
|
||||
@ -818,74 +833,59 @@ impl ScheduleGraph {
|
||||
///
|
||||
/// Panics if it doesn't exist.
|
||||
#[track_caller]
|
||||
pub fn system_at(&self, id: NodeId) -> &ScheduleSystem {
|
||||
self.get_system_at(id)
|
||||
.ok_or_else(|| format!("system with id {id:?} does not exist in this Schedule"))
|
||||
.unwrap()
|
||||
pub fn system_at(&self, key: SystemKey) -> &ScheduleSystem {
|
||||
self.get_system_at(key)
|
||||
.unwrap_or_else(|| panic!("system with key {key:?} does not exist in this Schedule"))
|
||||
}
|
||||
|
||||
/// Returns the set at the given [`NodeId`], if it exists.
|
||||
pub fn get_set_at(&self, id: NodeId) -> Option<&dyn SystemSet> {
|
||||
if !id.is_set() {
|
||||
return None;
|
||||
}
|
||||
self.system_sets.get(id.index()).map(|set| &*set.inner)
|
||||
pub fn get_set_at(&self, key: SystemSetKey) -> Option<&dyn SystemSet> {
|
||||
self.system_sets.get(key).map(|set| &*set.inner)
|
||||
}
|
||||
|
||||
/// Returns the set at the given [`NodeId`].
|
||||
///
|
||||
/// Panics if it doesn't exist.
|
||||
#[track_caller]
|
||||
pub fn set_at(&self, id: NodeId) -> &dyn SystemSet {
|
||||
pub fn set_at(&self, id: SystemSetKey) -> &dyn SystemSet {
|
||||
self.get_set_at(id)
|
||||
.ok_or_else(|| format!("set with id {id:?} does not exist in this Schedule"))
|
||||
.unwrap()
|
||||
.unwrap_or_else(|| panic!("set with id {id:?} does not exist in this Schedule"))
|
||||
}
|
||||
|
||||
/// Returns the conditions for the set at the given [`NodeId`], if it exists.
|
||||
pub fn get_set_conditions_at(&self, id: NodeId) -> Option<&[ConditionWithAccess]> {
|
||||
if !id.is_set() {
|
||||
return None;
|
||||
}
|
||||
self.system_set_conditions
|
||||
.get(id.index())
|
||||
.map(Vec::as_slice)
|
||||
/// Returns the conditions for the set at the given [`SystemSetKey`], if it exists.
|
||||
pub fn get_set_conditions_at(&self, key: SystemSetKey) -> Option<&[ConditionWithAccess]> {
|
||||
self.system_set_conditions.get(key).map(Vec::as_slice)
|
||||
}
|
||||
|
||||
/// Returns the conditions for the set at the given [`NodeId`].
|
||||
/// Returns the conditions for the set at the given [`SystemSetKey`].
|
||||
///
|
||||
/// Panics if it doesn't exist.
|
||||
#[track_caller]
|
||||
pub fn set_conditions_at(&self, id: NodeId) -> &[ConditionWithAccess] {
|
||||
self.get_set_conditions_at(id)
|
||||
.ok_or_else(|| format!("set with id {id:?} does not exist in this Schedule"))
|
||||
.unwrap()
|
||||
pub fn set_conditions_at(&self, key: SystemSetKey) -> &[ConditionWithAccess] {
|
||||
self.get_set_conditions_at(key)
|
||||
.unwrap_or_else(|| panic!("set with key {key:?} does not exist in this Schedule"))
|
||||
}
|
||||
|
||||
/// Returns an iterator over all systems in this schedule, along with the conditions for each system.
|
||||
pub fn systems(
|
||||
&self,
|
||||
) -> impl Iterator<Item = (NodeId, &ScheduleSystem, &[ConditionWithAccess])> {
|
||||
self.systems
|
||||
.iter()
|
||||
.zip(self.system_conditions.iter())
|
||||
.enumerate()
|
||||
.filter_map(|(i, (system_node, condition))| {
|
||||
let system = &system_node.inner.as_ref()?.system;
|
||||
Some((NodeId::System(i), system, condition.as_slice()))
|
||||
})
|
||||
) -> impl Iterator<Item = (SystemKey, &ScheduleSystem, &[ConditionWithAccess])> {
|
||||
self.systems.iter().filter_map(|(key, system_node)| {
|
||||
let system = &system_node.inner.as_ref()?.system;
|
||||
let conditions = self.system_conditions.get(key)?;
|
||||
Some((key, system, conditions.as_slice()))
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns an iterator over all system sets in this schedule, along with the conditions for each
|
||||
/// system set.
|
||||
pub fn system_sets(
|
||||
&self,
|
||||
) -> impl Iterator<Item = (NodeId, &dyn SystemSet, &[ConditionWithAccess])> {
|
||||
self.system_set_ids.iter().map(|(_, &node_id)| {
|
||||
let set_node = &self.system_sets[node_id.index()];
|
||||
) -> impl Iterator<Item = (SystemSetKey, &dyn SystemSet, &[ConditionWithAccess])> {
|
||||
self.system_sets.iter().filter_map(|(key, set_node)| {
|
||||
let set = &*set_node.inner;
|
||||
let conditions = self.system_set_conditions[node_id.index()].as_slice();
|
||||
(node_id, set, conditions)
|
||||
let conditions = self.system_set_conditions.get(key)?.as_slice();
|
||||
Some((key, set, conditions))
|
||||
})
|
||||
}
|
||||
|
||||
@ -909,7 +909,7 @@ impl ScheduleGraph {
|
||||
///
|
||||
/// If the `Vec<ComponentId>` is empty, the systems conflict on [`World`] access.
|
||||
/// Must be called after [`ScheduleGraph::build_schedule`] to be non-empty.
|
||||
pub fn conflicting_systems(&self) -> &[(NodeId, NodeId, Vec<ComponentId>)] {
|
||||
pub fn conflicting_systems(&self) -> &[(SystemKey, SystemKey, Vec<ComponentId>)] {
|
||||
&self.conflicting_systems
|
||||
}
|
||||
|
||||
@ -1051,23 +1051,22 @@ impl ScheduleGraph {
|
||||
&mut self,
|
||||
config: ScheduleConfig<ScheduleSystem>,
|
||||
) -> Result<NodeId, ScheduleBuildError> {
|
||||
let id = NodeId::System(self.systems.len());
|
||||
|
||||
// graph updates are immediate
|
||||
self.update_graphs(id, config.metadata)?;
|
||||
|
||||
// system init has to be deferred (need `&mut World`)
|
||||
self.uninit.push((id, 0));
|
||||
self.systems.push(SystemNode::new(config.node));
|
||||
self.system_conditions.push(
|
||||
let key = self.systems.insert(SystemNode::new(config.node));
|
||||
self.system_conditions.insert(
|
||||
key,
|
||||
config
|
||||
.conditions
|
||||
.into_iter()
|
||||
.map(ConditionWithAccess::new)
|
||||
.collect(),
|
||||
);
|
||||
// system init has to be deferred (need `&mut World`)
|
||||
self.uninit.push(UninitializedId::System(key));
|
||||
|
||||
Ok(id)
|
||||
// graph updates are immediate
|
||||
self.update_graphs(NodeId::System(key), config.metadata)?;
|
||||
|
||||
Ok(NodeId::System(key))
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
@ -1086,49 +1085,30 @@ impl ScheduleGraph {
|
||||
conditions,
|
||||
} = set;
|
||||
|
||||
let id = match self.system_set_ids.get(&set) {
|
||||
let key = match self.system_set_ids.get(&set) {
|
||||
Some(&id) => id,
|
||||
None => self.add_set(set),
|
||||
};
|
||||
|
||||
// graph updates are immediate
|
||||
self.update_graphs(id, metadata)?;
|
||||
self.update_graphs(NodeId::Set(key), metadata)?;
|
||||
|
||||
// system init has to be deferred (need `&mut World`)
|
||||
let system_set_conditions = &mut self.system_set_conditions[id.index()];
|
||||
self.uninit.push((id, system_set_conditions.len()));
|
||||
let system_set_conditions = self.system_set_conditions.entry(key).unwrap().or_default();
|
||||
self.uninit.push(UninitializedId::Set {
|
||||
key,
|
||||
first_uninit_condition: system_set_conditions.len(),
|
||||
});
|
||||
system_set_conditions.extend(conditions.into_iter().map(ConditionWithAccess::new));
|
||||
|
||||
Ok(id)
|
||||
Ok(NodeId::Set(key))
|
||||
}
|
||||
|
||||
fn add_set(&mut self, set: InternedSystemSet) -> NodeId {
|
||||
let id = NodeId::Set(self.system_sets.len());
|
||||
self.system_sets.push(SystemSetNode::new(set));
|
||||
self.system_set_conditions.push(Vec::new());
|
||||
self.system_set_ids.insert(set, id);
|
||||
id
|
||||
}
|
||||
|
||||
/// Checks that a system set isn't included in itself.
|
||||
/// If not present, add the set to the graph.
|
||||
fn check_hierarchy_set(
|
||||
&mut self,
|
||||
id: &NodeId,
|
||||
set: InternedSystemSet,
|
||||
) -> Result<(), ScheduleBuildError> {
|
||||
match self.system_set_ids.get(&set) {
|
||||
Some(set_id) => {
|
||||
if id == set_id {
|
||||
return Err(ScheduleBuildError::HierarchyLoop(self.get_node_name(id)));
|
||||
}
|
||||
}
|
||||
None => {
|
||||
self.add_set(set);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
fn add_set(&mut self, set: InternedSystemSet) -> SystemSetKey {
|
||||
let key = self.system_sets.insert(SystemSetNode::new(set));
|
||||
self.system_set_conditions.insert(key, Vec::new());
|
||||
self.system_set_ids.insert(set, key);
|
||||
key
|
||||
}
|
||||
|
||||
fn create_anonymous_set(&mut self) -> AnonymousSet {
|
||||
@ -1141,11 +1121,24 @@ impl ScheduleGraph {
|
||||
/// Add all the sets from the [`GraphInfo`]'s hierarchy to the graph.
|
||||
fn check_hierarchy_sets(
|
||||
&mut self,
|
||||
id: &NodeId,
|
||||
id: NodeId,
|
||||
graph_info: &GraphInfo,
|
||||
) -> Result<(), ScheduleBuildError> {
|
||||
for &set in &graph_info.hierarchy {
|
||||
self.check_hierarchy_set(id, set)?;
|
||||
if let Some(&set_id) = self.system_set_ids.get(&set) {
|
||||
if let NodeId::Set(key) = id
|
||||
&& set_id == key
|
||||
{
|
||||
{
|
||||
return Err(ScheduleBuildError::HierarchyLoop(
|
||||
self.get_node_name(&NodeId::Set(key)),
|
||||
));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If the set is not in the graph, we add it
|
||||
self.add_set(set);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -1155,22 +1148,29 @@ impl ScheduleGraph {
|
||||
/// Add all the sets from the [`GraphInfo`]'s dependencies to the graph.
|
||||
fn check_edges(
|
||||
&mut self,
|
||||
id: &NodeId,
|
||||
id: NodeId,
|
||||
graph_info: &GraphInfo,
|
||||
) -> Result<(), ScheduleBuildError> {
|
||||
for Dependency { set, .. } in &graph_info.dependencies {
|
||||
match self.system_set_ids.get(set) {
|
||||
Some(set_id) => {
|
||||
if id == set_id {
|
||||
return Err(ScheduleBuildError::DependencyLoop(self.get_node_name(id)));
|
||||
}
|
||||
}
|
||||
None => {
|
||||
self.add_set(*set);
|
||||
if let Some(&set_id) = self.system_set_ids.get(set) {
|
||||
if let NodeId::Set(key) = id
|
||||
&& set_id == key
|
||||
{
|
||||
return Err(ScheduleBuildError::DependencyLoop(
|
||||
self.get_node_name(&NodeId::Set(key)),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
// If the set is not in the graph, we add it
|
||||
self.add_set(*set);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add all the sets from the [`GraphInfo`]'s ambiguity to the graph.
|
||||
fn add_ambiguities(&mut self, graph_info: &GraphInfo) {
|
||||
if let Ambiguity::IgnoreWithSet(ambiguous_with) = &graph_info.ambiguous_with {
|
||||
for set in ambiguous_with {
|
||||
if !self.system_set_ids.contains_key(set) {
|
||||
@ -1178,8 +1178,6 @@ impl ScheduleGraph {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Update the internal graphs (hierarchy, dependency, ambiguity) by adding a single [`GraphInfo`]
|
||||
@ -1188,8 +1186,9 @@ impl ScheduleGraph {
|
||||
id: NodeId,
|
||||
graph_info: GraphInfo,
|
||||
) -> Result<(), ScheduleBuildError> {
|
||||
self.check_hierarchy_sets(&id, &graph_info)?;
|
||||
self.check_edges(&id, &graph_info)?;
|
||||
self.check_hierarchy_sets(id, &graph_info)?;
|
||||
self.check_edges(id, &graph_info)?;
|
||||
self.add_ambiguities(&graph_info);
|
||||
self.changed = true;
|
||||
|
||||
let GraphInfo {
|
||||
@ -1202,20 +1201,20 @@ impl ScheduleGraph {
|
||||
self.hierarchy.graph.add_node(id);
|
||||
self.dependency.graph.add_node(id);
|
||||
|
||||
for set in sets.into_iter().map(|set| self.system_set_ids[&set]) {
|
||||
self.hierarchy.graph.add_edge(set, id);
|
||||
for key in sets.into_iter().map(|set| self.system_set_ids[&set]) {
|
||||
self.hierarchy.graph.add_edge(NodeId::Set(key), id);
|
||||
|
||||
// ensure set also appears in dependency graph
|
||||
self.dependency.graph.add_node(set);
|
||||
self.dependency.graph.add_node(NodeId::Set(key));
|
||||
}
|
||||
|
||||
for (kind, set, options) in dependencies
|
||||
for (kind, key, options) in dependencies
|
||||
.into_iter()
|
||||
.map(|Dependency { kind, set, options }| (kind, self.system_set_ids[&set], options))
|
||||
{
|
||||
let (lhs, rhs) = match kind {
|
||||
DependencyKind::Before => (id, set),
|
||||
DependencyKind::After => (set, id),
|
||||
DependencyKind::Before => (id, NodeId::Set(key)),
|
||||
DependencyKind::After => (NodeId::Set(key), id),
|
||||
};
|
||||
self.dependency.graph.add_edge(lhs, rhs);
|
||||
for pass in self.passes.values_mut() {
|
||||
@ -1223,17 +1222,17 @@ impl ScheduleGraph {
|
||||
}
|
||||
|
||||
// ensure set also appears in hierarchy graph
|
||||
self.hierarchy.graph.add_node(set);
|
||||
self.hierarchy.graph.add_node(NodeId::Set(key));
|
||||
}
|
||||
|
||||
match ambiguous_with {
|
||||
Ambiguity::Check => (),
|
||||
Ambiguity::IgnoreWithSet(ambiguous_with) => {
|
||||
for set in ambiguous_with
|
||||
for key in ambiguous_with
|
||||
.into_iter()
|
||||
.map(|set| self.system_set_ids[&set])
|
||||
{
|
||||
self.ambiguous_with.add_edge(id, set);
|
||||
self.ambiguous_with.add_edge(id, NodeId::Set(key));
|
||||
}
|
||||
}
|
||||
Ambiguity::IgnoreAll => {
|
||||
@ -1246,17 +1245,23 @@ impl ScheduleGraph {
|
||||
|
||||
/// Initializes any newly-added systems and conditions by calling [`System::initialize`](crate::system::System)
|
||||
pub fn initialize(&mut self, world: &mut World) {
|
||||
for (id, i) in self.uninit.drain(..) {
|
||||
for id in self.uninit.drain(..) {
|
||||
match id {
|
||||
NodeId::System(index) => {
|
||||
let system = self.systems[index].get_mut().unwrap();
|
||||
UninitializedId::System(key) => {
|
||||
let system = self.systems[key].get_mut().unwrap();
|
||||
system.access = system.system.initialize(world);
|
||||
for condition in &mut self.system_conditions[index] {
|
||||
for condition in &mut self.system_conditions[key] {
|
||||
condition.access = condition.condition.initialize(world);
|
||||
}
|
||||
}
|
||||
NodeId::Set(index) => {
|
||||
for condition in self.system_set_conditions[index].iter_mut().skip(i) {
|
||||
UninitializedId::Set {
|
||||
key,
|
||||
first_uninit_condition,
|
||||
} => {
|
||||
for condition in self.system_set_conditions[key]
|
||||
.iter_mut()
|
||||
.skip(first_uninit_condition)
|
||||
{
|
||||
condition.access = condition.condition.initialize(world);
|
||||
}
|
||||
}
|
||||
@ -1348,41 +1353,47 @@ impl ScheduleGraph {
|
||||
&self,
|
||||
hierarchy_topsort: &[NodeId],
|
||||
hierarchy_graph: &DiGraph,
|
||||
) -> (HashMap<NodeId, Vec<NodeId>>, HashMap<NodeId, FixedBitSet>) {
|
||||
let mut set_systems: HashMap<NodeId, Vec<NodeId>> =
|
||||
) -> (
|
||||
HashMap<SystemSetKey, Vec<SystemKey>>,
|
||||
HashMap<SystemSetKey, HashSet<SystemKey>>,
|
||||
) {
|
||||
let mut set_systems: HashMap<SystemSetKey, Vec<SystemKey>> =
|
||||
HashMap::with_capacity_and_hasher(self.system_sets.len(), Default::default());
|
||||
let mut set_system_bitsets =
|
||||
let mut set_system_sets: HashMap<SystemSetKey, HashSet<SystemKey>> =
|
||||
HashMap::with_capacity_and_hasher(self.system_sets.len(), Default::default());
|
||||
for &id in hierarchy_topsort.iter().rev() {
|
||||
if id.is_system() {
|
||||
let NodeId::Set(set_key) = id else {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let mut systems = Vec::new();
|
||||
let mut system_bitset = FixedBitSet::with_capacity(self.systems.len());
|
||||
let mut system_set = HashSet::with_capacity(self.systems.len());
|
||||
|
||||
for child in hierarchy_graph.neighbors_directed(id, Outgoing) {
|
||||
match child {
|
||||
NodeId::System(_) => {
|
||||
systems.push(child);
|
||||
system_bitset.insert(child.index());
|
||||
NodeId::System(key) => {
|
||||
systems.push(key);
|
||||
system_set.insert(key);
|
||||
}
|
||||
NodeId::Set(_) => {
|
||||
let child_systems = set_systems.get(&child).unwrap();
|
||||
let child_system_bitset = set_system_bitsets.get(&child).unwrap();
|
||||
NodeId::Set(key) => {
|
||||
let child_systems = set_systems.get(&key).unwrap();
|
||||
let child_system_set = set_system_sets.get(&key).unwrap();
|
||||
systems.extend_from_slice(child_systems);
|
||||
system_bitset.union_with(child_system_bitset);
|
||||
system_set.extend(child_system_set.iter());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set_systems.insert(id, systems);
|
||||
set_system_bitsets.insert(id, system_bitset);
|
||||
set_systems.insert(set_key, systems);
|
||||
set_system_sets.insert(set_key, system_set);
|
||||
}
|
||||
(set_systems, set_system_bitsets)
|
||||
(set_systems, set_system_sets)
|
||||
}
|
||||
|
||||
fn get_dependency_flattened(&mut self, set_systems: &HashMap<NodeId, Vec<NodeId>>) -> DiGraph {
|
||||
fn get_dependency_flattened(
|
||||
&mut self,
|
||||
set_systems: &HashMap<SystemSetKey, Vec<SystemKey>>,
|
||||
) -> DiGraph {
|
||||
// flatten: combine `in_set` with `before` and `after` information
|
||||
// have to do it like this to preserve transitivity
|
||||
let mut dependency_flattened = self.dependency.graph.clone();
|
||||
@ -1393,26 +1404,26 @@ impl ScheduleGraph {
|
||||
}
|
||||
if systems.is_empty() {
|
||||
// collapse dependencies for empty sets
|
||||
for a in dependency_flattened.neighbors_directed(set, Incoming) {
|
||||
for b in dependency_flattened.neighbors_directed(set, Outgoing) {
|
||||
for a in dependency_flattened.neighbors_directed(NodeId::Set(set), Incoming) {
|
||||
for b in dependency_flattened.neighbors_directed(NodeId::Set(set), Outgoing) {
|
||||
temp.push((a, b));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for a in dependency_flattened.neighbors_directed(set, Incoming) {
|
||||
for a in dependency_flattened.neighbors_directed(NodeId::Set(set), Incoming) {
|
||||
for &sys in systems {
|
||||
temp.push((a, sys));
|
||||
temp.push((a, NodeId::System(sys)));
|
||||
}
|
||||
}
|
||||
|
||||
for b in dependency_flattened.neighbors_directed(set, Outgoing) {
|
||||
for b in dependency_flattened.neighbors_directed(NodeId::Set(set), Outgoing) {
|
||||
for &sys in systems {
|
||||
temp.push((sys, b));
|
||||
temp.push((NodeId::System(sys), b));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependency_flattened.remove_node(set);
|
||||
dependency_flattened.remove_node(NodeId::Set(set));
|
||||
for (a, b) in temp.drain(..) {
|
||||
dependency_flattened.add_edge(a, b);
|
||||
}
|
||||
@ -1421,27 +1432,31 @@ impl ScheduleGraph {
|
||||
dependency_flattened
|
||||
}
|
||||
|
||||
fn get_ambiguous_with_flattened(&self, set_systems: &HashMap<NodeId, Vec<NodeId>>) -> UnGraph {
|
||||
fn get_ambiguous_with_flattened(
|
||||
&self,
|
||||
set_systems: &HashMap<SystemSetKey, Vec<SystemKey>>,
|
||||
) -> UnGraph {
|
||||
let mut ambiguous_with_flattened = UnGraph::default();
|
||||
for (lhs, rhs) in self.ambiguous_with.all_edges() {
|
||||
match (lhs, rhs) {
|
||||
(NodeId::System(_), NodeId::System(_)) => {
|
||||
ambiguous_with_flattened.add_edge(lhs, rhs);
|
||||
}
|
||||
(NodeId::Set(_), NodeId::System(_)) => {
|
||||
(NodeId::Set(lhs), NodeId::System(_)) => {
|
||||
for &lhs_ in set_systems.get(&lhs).unwrap_or(&Vec::new()) {
|
||||
ambiguous_with_flattened.add_edge(lhs_, rhs);
|
||||
ambiguous_with_flattened.add_edge(NodeId::System(lhs_), rhs);
|
||||
}
|
||||
}
|
||||
(NodeId::System(_), NodeId::Set(_)) => {
|
||||
(NodeId::System(_), NodeId::Set(rhs)) => {
|
||||
for &rhs_ in set_systems.get(&rhs).unwrap_or(&Vec::new()) {
|
||||
ambiguous_with_flattened.add_edge(lhs, rhs_);
|
||||
ambiguous_with_flattened.add_edge(lhs, NodeId::System(rhs_));
|
||||
}
|
||||
}
|
||||
(NodeId::Set(_), NodeId::Set(_)) => {
|
||||
(NodeId::Set(lhs), NodeId::Set(rhs)) => {
|
||||
for &lhs_ in set_systems.get(&lhs).unwrap_or(&Vec::new()) {
|
||||
for &rhs_ in set_systems.get(&rhs).unwrap_or(&vec![]) {
|
||||
ambiguous_with_flattened.add_edge(lhs_, rhs_);
|
||||
ambiguous_with_flattened
|
||||
.add_edge(NodeId::System(lhs_), NodeId::System(rhs_));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1456,7 +1471,7 @@ impl ScheduleGraph {
|
||||
flat_results_disconnected: &Vec<(NodeId, NodeId)>,
|
||||
ambiguous_with_flattened: &UnGraph,
|
||||
ignored_ambiguities: &BTreeSet<ComponentId>,
|
||||
) -> Vec<(NodeId, NodeId, Vec<ComponentId>)> {
|
||||
) -> Vec<(SystemKey, SystemKey, Vec<ComponentId>)> {
|
||||
let mut conflicting_systems = Vec::new();
|
||||
for &(a, b) in flat_results_disconnected {
|
||||
if ambiguous_with_flattened.contains_edge(a, b)
|
||||
@ -1466,8 +1481,18 @@ impl ScheduleGraph {
|
||||
continue;
|
||||
}
|
||||
|
||||
let system_a = self.systems[a.index()].get().unwrap();
|
||||
let system_b = self.systems[b.index()].get().unwrap();
|
||||
let NodeId::System(a) = a else {
|
||||
panic!(
|
||||
"Encountered a non-system node in the flattened disconnected results: {a:?}"
|
||||
);
|
||||
};
|
||||
let NodeId::System(b) = b else {
|
||||
panic!(
|
||||
"Encountered a non-system node in the flattened disconnected results: {b:?}"
|
||||
);
|
||||
};
|
||||
let system_a = self.systems[a].get().unwrap();
|
||||
let system_b = self.systems[b].get().unwrap();
|
||||
if system_a.system.is_exclusive() || system_b.system.is_exclusive() {
|
||||
conflicting_systems.push((a, b, Vec::new()));
|
||||
} else {
|
||||
@ -1503,7 +1528,11 @@ impl ScheduleGraph {
|
||||
dependency_flattened_dag: Dag,
|
||||
hier_results_reachable: FixedBitSet,
|
||||
) -> SystemSchedule {
|
||||
let dg_system_ids = dependency_flattened_dag.topsort.clone();
|
||||
let dg_system_ids = dependency_flattened_dag
|
||||
.topsort
|
||||
.iter()
|
||||
.filter_map(NodeId::as_system)
|
||||
.collect::<Vec<_>>();
|
||||
let dg_system_idx_map = dg_system_ids
|
||||
.iter()
|
||||
.cloned()
|
||||
@ -1517,7 +1546,7 @@ impl ScheduleGraph {
|
||||
.iter()
|
||||
.cloned()
|
||||
.enumerate()
|
||||
.filter(|&(_i, id)| id.is_system())
|
||||
.filter_map(|(i, id)| Some((i, id.as_system()?)))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let (hg_set_with_conditions_idxs, hg_set_ids): (Vec<_>, Vec<_>) = self
|
||||
@ -1526,10 +1555,11 @@ impl ScheduleGraph {
|
||||
.iter()
|
||||
.cloned()
|
||||
.enumerate()
|
||||
.filter(|&(_i, id)| {
|
||||
.filter_map(|(i, id)| {
|
||||
// ignore system sets that have no conditions
|
||||
// ignore system type sets (already covered, they don't have conditions)
|
||||
id.is_set() && !self.system_set_conditions[id.index()].is_empty()
|
||||
let key = id.as_set()?;
|
||||
(!self.system_set_conditions[key].is_empty()).then_some((i, key))
|
||||
})
|
||||
.unzip();
|
||||
|
||||
@ -1541,16 +1571,19 @@ impl ScheduleGraph {
|
||||
// (needed by multi_threaded executor to run systems in the correct order)
|
||||
let mut system_dependencies = Vec::with_capacity(sys_count);
|
||||
let mut system_dependents = Vec::with_capacity(sys_count);
|
||||
for &sys_id in &dg_system_ids {
|
||||
for &sys_key in &dg_system_ids {
|
||||
let num_dependencies = dependency_flattened_dag
|
||||
.graph
|
||||
.neighbors_directed(sys_id, Incoming)
|
||||
.neighbors_directed(NodeId::System(sys_key), Incoming)
|
||||
.count();
|
||||
|
||||
let dependents = dependency_flattened_dag
|
||||
.graph
|
||||
.neighbors_directed(sys_id, Outgoing)
|
||||
.map(|dep_id| dg_system_idx_map[&dep_id])
|
||||
.neighbors_directed(NodeId::System(sys_key), Outgoing)
|
||||
.filter_map(|dep_id| {
|
||||
let dep_key = dep_id.as_system()?;
|
||||
Some(dg_system_idx_map[&dep_key])
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
system_dependencies.push(num_dependencies);
|
||||
@ -1563,8 +1596,8 @@ impl ScheduleGraph {
|
||||
vec![FixedBitSet::with_capacity(sys_count); set_with_conditions_count];
|
||||
for (i, &row) in hg_set_with_conditions_idxs.iter().enumerate() {
|
||||
let bitset = &mut systems_in_sets_with_conditions[i];
|
||||
for &(col, sys_id) in &hg_systems {
|
||||
let idx = dg_system_idx_map[&sys_id];
|
||||
for &(col, sys_key) in &hg_systems {
|
||||
let idx = dg_system_idx_map[&sys_key];
|
||||
let is_descendant = hier_results_reachable[index(row, col, hg_node_count)];
|
||||
bitset.set(idx, is_descendant);
|
||||
}
|
||||
@ -1572,8 +1605,8 @@ impl ScheduleGraph {
|
||||
|
||||
let mut sets_with_conditions_of_systems =
|
||||
vec![FixedBitSet::with_capacity(set_with_conditions_count); sys_count];
|
||||
for &(col, sys_id) in &hg_systems {
|
||||
let i = dg_system_idx_map[&sys_id];
|
||||
for &(col, sys_key) in &hg_systems {
|
||||
let i = dg_system_idx_map[&sys_key];
|
||||
let bitset = &mut sets_with_conditions_of_systems[i];
|
||||
for (idx, &row) in hg_set_with_conditions_idxs
|
||||
.iter()
|
||||
@ -1611,36 +1644,36 @@ impl ScheduleGraph {
|
||||
}
|
||||
|
||||
// move systems out of old schedule
|
||||
for ((id, system), conditions) in schedule
|
||||
for ((key, system), conditions) in schedule
|
||||
.system_ids
|
||||
.drain(..)
|
||||
.zip(schedule.systems.drain(..))
|
||||
.zip(schedule.system_conditions.drain(..))
|
||||
{
|
||||
self.systems[id.index()].inner = Some(system);
|
||||
self.system_conditions[id.index()] = conditions;
|
||||
self.systems[key].inner = Some(system);
|
||||
self.system_conditions[key] = conditions;
|
||||
}
|
||||
|
||||
for (id, conditions) in schedule
|
||||
for (key, conditions) in schedule
|
||||
.set_ids
|
||||
.drain(..)
|
||||
.zip(schedule.set_conditions.drain(..))
|
||||
{
|
||||
self.system_set_conditions[id.index()] = conditions;
|
||||
self.system_set_conditions[key] = conditions;
|
||||
}
|
||||
|
||||
*schedule = self.build_schedule(world, schedule_label, ignored_ambiguities)?;
|
||||
|
||||
// move systems into new schedule
|
||||
for &id in &schedule.system_ids {
|
||||
let system = self.systems[id.index()].inner.take().unwrap();
|
||||
let conditions = core::mem::take(&mut self.system_conditions[id.index()]);
|
||||
for &key in &schedule.system_ids {
|
||||
let system = self.systems[key].inner.take().unwrap();
|
||||
let conditions = core::mem::take(&mut self.system_conditions[key]);
|
||||
schedule.systems.push(system);
|
||||
schedule.system_conditions.push(conditions);
|
||||
}
|
||||
|
||||
for &id in &schedule.set_ids {
|
||||
let conditions = core::mem::take(&mut self.system_set_conditions[id.index()]);
|
||||
for &key in &schedule.set_ids {
|
||||
let conditions = core::mem::take(&mut self.system_set_conditions[key]);
|
||||
schedule.set_conditions.push(conditions);
|
||||
}
|
||||
|
||||
@ -1693,9 +1726,9 @@ impl ScheduleGraph {
|
||||
|
||||
#[inline]
|
||||
fn get_node_name_inner(&self, id: &NodeId, report_sets: bool) -> String {
|
||||
match id {
|
||||
NodeId::System(_) => {
|
||||
let name = self.systems[id.index()].get().unwrap().system.name();
|
||||
match *id {
|
||||
NodeId::System(key) => {
|
||||
let name = self.systems[key].get().unwrap().system.name();
|
||||
let name = if self.settings.use_shortnames {
|
||||
name.shortname().to_string()
|
||||
} else {
|
||||
@ -1714,8 +1747,8 @@ impl ScheduleGraph {
|
||||
name
|
||||
}
|
||||
}
|
||||
NodeId::Set(_) => {
|
||||
let set = &self.system_sets[id.index()];
|
||||
NodeId::Set(key) => {
|
||||
let set = &self.system_sets[key];
|
||||
if set.is_anonymous() {
|
||||
self.anonymous_set_name(id)
|
||||
} else {
|
||||
@ -1903,16 +1936,16 @@ impl ScheduleGraph {
|
||||
fn check_order_but_intersect(
|
||||
&self,
|
||||
dep_results_connected: &HashSet<(NodeId, NodeId)>,
|
||||
set_system_bitsets: &HashMap<NodeId, FixedBitSet>,
|
||||
set_system_sets: &HashMap<SystemSetKey, HashSet<SystemKey>>,
|
||||
) -> Result<(), ScheduleBuildError> {
|
||||
// check that there is no ordering between system sets that intersect
|
||||
for (a, b) in dep_results_connected {
|
||||
if !(a.is_set() && b.is_set()) {
|
||||
let (NodeId::Set(a_key), NodeId::Set(b_key)) = (a, b) else {
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let a_systems = set_system_bitsets.get(a).unwrap();
|
||||
let b_systems = set_system_bitsets.get(b).unwrap();
|
||||
let a_systems = set_system_sets.get(a_key).unwrap();
|
||||
let b_systems = set_system_sets.get(b_key).unwrap();
|
||||
|
||||
if !a_systems.is_disjoint(b_systems) {
|
||||
return Err(ScheduleBuildError::SetsHaveOrderButIntersect(
|
||||
@ -1927,19 +1960,25 @@ impl ScheduleGraph {
|
||||
|
||||
fn check_system_type_set_ambiguity(
|
||||
&self,
|
||||
set_systems: &HashMap<NodeId, Vec<NodeId>>,
|
||||
set_systems: &HashMap<SystemSetKey, Vec<SystemKey>>,
|
||||
) -> Result<(), ScheduleBuildError> {
|
||||
for (&id, systems) in set_systems {
|
||||
let set = &self.system_sets[id.index()];
|
||||
for (&key, systems) in set_systems {
|
||||
let set = &self.system_sets[key];
|
||||
if set.is_system_type() {
|
||||
let instances = systems.len();
|
||||
let ambiguous_with = self.ambiguous_with.edges(id);
|
||||
let before = self.dependency.graph.edges_directed(id, Incoming);
|
||||
let after = self.dependency.graph.edges_directed(id, Outgoing);
|
||||
let ambiguous_with = self.ambiguous_with.edges(NodeId::Set(key));
|
||||
let before = self
|
||||
.dependency
|
||||
.graph
|
||||
.edges_directed(NodeId::Set(key), Incoming);
|
||||
let after = self
|
||||
.dependency
|
||||
.graph
|
||||
.edges_directed(NodeId::Set(key), Outgoing);
|
||||
let relations = before.count() + after.count() + ambiguous_with.count();
|
||||
if instances > 1 && relations > 0 {
|
||||
return Err(ScheduleBuildError::SystemTypeSetAmbiguity(
|
||||
self.get_node_name(&id),
|
||||
self.get_node_name(&NodeId::Set(key)),
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -1950,7 +1989,7 @@ impl ScheduleGraph {
|
||||
/// if [`ScheduleBuildSettings::ambiguity_detection`] is [`LogLevel::Ignore`], this check is skipped
|
||||
fn optionally_check_conflicts(
|
||||
&self,
|
||||
conflicts: &[(NodeId, NodeId, Vec<ComponentId>)],
|
||||
conflicts: &[(SystemKey, SystemKey, Vec<ComponentId>)],
|
||||
components: &Components,
|
||||
schedule_label: InternedScheduleLabel,
|
||||
) -> Result<(), ScheduleBuildError> {
|
||||
@ -1971,7 +2010,7 @@ impl ScheduleGraph {
|
||||
|
||||
fn get_conflicts_error_message(
|
||||
&self,
|
||||
ambiguities: &[(NodeId, NodeId, Vec<ComponentId>)],
|
||||
ambiguities: &[(SystemKey, SystemKey, Vec<ComponentId>)],
|
||||
components: &Components,
|
||||
) -> String {
|
||||
let n_ambiguities = ambiguities.len();
|
||||
@ -1999,17 +2038,14 @@ impl ScheduleGraph {
|
||||
/// convert conflicts to human readable format
|
||||
pub fn conflicts_to_string<'a>(
|
||||
&'a self,
|
||||
ambiguities: &'a [(NodeId, NodeId, Vec<ComponentId>)],
|
||||
ambiguities: &'a [(SystemKey, SystemKey, Vec<ComponentId>)],
|
||||
components: &'a Components,
|
||||
) -> impl Iterator<Item = (String, String, Vec<DebugName>)> + 'a {
|
||||
ambiguities
|
||||
.iter()
|
||||
.map(move |(system_a, system_b, conflicts)| {
|
||||
let name_a = self.get_node_name(system_a);
|
||||
let name_b = self.get_node_name(system_b);
|
||||
|
||||
debug_assert!(system_a.is_system(), "{name_a} is not a system.");
|
||||
debug_assert!(system_b.is_system(), "{name_b} is not a system.");
|
||||
let name_a = self.get_node_name(&NodeId::System(*system_a));
|
||||
let name_b = self.get_node_name(&NodeId::System(*system_b));
|
||||
|
||||
let conflict_names: Vec<_> = conflicts
|
||||
.iter()
|
||||
@ -2020,22 +2056,25 @@ impl ScheduleGraph {
|
||||
})
|
||||
}
|
||||
|
||||
fn traverse_sets_containing_node(&self, id: NodeId, f: &mut impl FnMut(NodeId) -> bool) {
|
||||
fn traverse_sets_containing_node(&self, id: NodeId, f: &mut impl FnMut(SystemSetKey) -> bool) {
|
||||
for (set_id, _) in self.hierarchy.graph.edges_directed(id, Incoming) {
|
||||
if f(set_id) {
|
||||
self.traverse_sets_containing_node(set_id, f);
|
||||
let NodeId::Set(set_key) = set_id else {
|
||||
continue;
|
||||
};
|
||||
if f(set_key) {
|
||||
self.traverse_sets_containing_node(NodeId::Set(set_key), f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn names_of_sets_containing_node(&self, id: &NodeId) -> Vec<String> {
|
||||
let mut sets = <HashSet<_>>::default();
|
||||
self.traverse_sets_containing_node(*id, &mut |set_id| {
|
||||
!self.system_sets[set_id.index()].is_system_type() && sets.insert(set_id)
|
||||
self.traverse_sets_containing_node(*id, &mut |key| {
|
||||
!self.system_sets[key].is_system_type() && sets.insert(key)
|
||||
});
|
||||
let mut sets: Vec<_> = sets
|
||||
.into_iter()
|
||||
.map(|set_id| self.get_node_name(&set_id))
|
||||
.map(|key| self.get_node_name(&NodeId::Set(key)))
|
||||
.collect();
|
||||
sets.sort();
|
||||
sets
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
resource::Resource,
|
||||
schedule::{InternedScheduleLabel, NodeId, Schedule, ScheduleLabel},
|
||||
schedule::{InternedScheduleLabel, NodeId, Schedule, ScheduleLabel, SystemKey},
|
||||
system::{IntoSystem, ResMut},
|
||||
};
|
||||
use alloc::vec::Vec;
|
||||
@ -173,7 +173,7 @@ impl Stepping {
|
||||
state
|
||||
.node_ids
|
||||
.get(self.cursor.system)
|
||||
.map(|node_id| (*label, *node_id))
|
||||
.map(|node_id| (*label, NodeId::System(*node_id)))
|
||||
}
|
||||
|
||||
/// Enable stepping for the provided schedule
|
||||
@ -606,7 +606,7 @@ struct ScheduleState {
|
||||
/// This is a cached copy of `SystemExecutable::system_ids`. We need it
|
||||
/// available here to be accessed by [`Stepping::cursor()`] so we can return
|
||||
/// [`NodeId`]s to the caller.
|
||||
node_ids: Vec<NodeId>,
|
||||
node_ids: Vec<SystemKey>,
|
||||
|
||||
/// changes to system behavior that should be applied the next time
|
||||
/// [`ScheduleState::skipped_systems()`] is called
|
||||
@ -662,15 +662,15 @@ impl ScheduleState {
|
||||
// updates for the system TypeId.
|
||||
// PERF: If we add a way to efficiently query schedule systems by their TypeId, we could remove the full
|
||||
// system scan here
|
||||
for (node_id, system) in schedule.systems().unwrap() {
|
||||
for (key, system) in schedule.systems().unwrap() {
|
||||
let behavior = self.behavior_updates.get(&system.type_id());
|
||||
match behavior {
|
||||
None => continue,
|
||||
Some(None) => {
|
||||
self.behaviors.remove(&node_id);
|
||||
self.behaviors.remove(&NodeId::System(key));
|
||||
}
|
||||
Some(Some(behavior)) => {
|
||||
self.behaviors.insert(node_id, *behavior);
|
||||
self.behaviors.insert(NodeId::System(key), *behavior);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -703,8 +703,8 @@ impl ScheduleState {
|
||||
|
||||
// if we don't have a first system set, set it now
|
||||
if self.first.is_none() {
|
||||
for (i, (node_id, _)) in schedule.systems().unwrap().enumerate() {
|
||||
match self.behaviors.get(&node_id) {
|
||||
for (i, (key, _)) in schedule.systems().unwrap().enumerate() {
|
||||
match self.behaviors.get(&NodeId::System(key)) {
|
||||
Some(SystemBehavior::AlwaysRun | SystemBehavior::NeverRun) => continue,
|
||||
Some(_) | None => {
|
||||
self.first = Some(i);
|
||||
@ -717,10 +717,10 @@ impl ScheduleState {
|
||||
let mut skip = FixedBitSet::with_capacity(schedule.systems_len());
|
||||
let mut pos = start;
|
||||
|
||||
for (i, (node_id, _system)) in schedule.systems().unwrap().enumerate() {
|
||||
for (i, (key, _system)) in schedule.systems().unwrap().enumerate() {
|
||||
let behavior = self
|
||||
.behaviors
|
||||
.get(&node_id)
|
||||
.get(&NodeId::System(key))
|
||||
.unwrap_or(&SystemBehavior::Continue);
|
||||
|
||||
#[cfg(test)]
|
||||
@ -825,6 +825,7 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::{prelude::*, schedule::ScheduleLabel};
|
||||
use alloc::{format, vec};
|
||||
use slotmap::SlotMap;
|
||||
use std::println;
|
||||
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
@ -1467,10 +1468,11 @@ mod tests {
|
||||
// helper to build a cursor tuple for the supplied schedule
|
||||
fn cursor(schedule: &Schedule, index: usize) -> (InternedScheduleLabel, NodeId) {
|
||||
let node_id = schedule.executable().system_ids[index];
|
||||
(schedule.label(), node_id)
|
||||
(schedule.label(), NodeId::System(node_id))
|
||||
}
|
||||
|
||||
let mut world = World::new();
|
||||
let mut slotmap = SlotMap::<SystemKey, ()>::with_key();
|
||||
|
||||
// create two schedules with a number of systems in them
|
||||
let mut schedule_a = Schedule::new(TestScheduleA);
|
||||
@ -1517,6 +1519,11 @@ mod tests {
|
||||
]
|
||||
);
|
||||
|
||||
let sys0 = slotmap.insert(());
|
||||
let sys1 = slotmap.insert(());
|
||||
let _sys2 = slotmap.insert(());
|
||||
let sys3 = slotmap.insert(());
|
||||
|
||||
// reset our cursor (disable/enable), and update stepping to test if the
|
||||
// cursor properly skips over AlwaysRun & NeverRun systems. Also set
|
||||
// a Break system to ensure that shows properly in the cursor
|
||||
@ -1524,9 +1531,9 @@ mod tests {
|
||||
// disable/enable to reset cursor
|
||||
.disable()
|
||||
.enable()
|
||||
.set_breakpoint_node(TestScheduleA, NodeId::System(1))
|
||||
.always_run_node(TestScheduleA, NodeId::System(3))
|
||||
.never_run_node(TestScheduleB, NodeId::System(0));
|
||||
.set_breakpoint_node(TestScheduleA, NodeId::System(sys1))
|
||||
.always_run_node(TestScheduleA, NodeId::System(sys3))
|
||||
.never_run_node(TestScheduleB, NodeId::System(sys0));
|
||||
|
||||
let mut cursors = Vec::new();
|
||||
for _ in 0..9 {
|
||||
|
@ -132,18 +132,20 @@ fn build_ui(
|
||||
return;
|
||||
};
|
||||
|
||||
for (node_id, system) in systems {
|
||||
for (key, system) in systems {
|
||||
// skip bevy default systems; we don't want to step those
|
||||
#[cfg(feature = "debug")]
|
||||
if system.name().as_string().starts_with("bevy") {
|
||||
always_run.push((*label, node_id));
|
||||
always_run.push((*label, NodeId::System(key)));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add an entry to our systems list so we can find where to draw
|
||||
// the cursor when the stepping cursor is at this system
|
||||
// we add plus 1 to account for the empty root span
|
||||
state.systems.push((*label, node_id, text_spans.len() + 1));
|
||||
state
|
||||
.systems
|
||||
.push((*label, NodeId::System(key), text_spans.len() + 1));
|
||||
|
||||
// Add a text section for displaying the cursor for this system
|
||||
text_spans.push((
|
||||
|
34
release-content/migration-guides/schedule_slotmaps.md
Normal file
34
release-content/migration-guides/schedule_slotmaps.md
Normal file
@ -0,0 +1,34 @@
|
||||
---
|
||||
title: Schedule SlotMaps
|
||||
pull_requests: [19352]
|
||||
---
|
||||
|
||||
In order to support removing systems from schedules, `Vec`s storing `System`s and
|
||||
`SystemSet`s have been replaced with `SlotMap`s which allow safely removing and
|
||||
reusing indices. The maps are respectively keyed by `SystemKey`s and `SystemSetKey`s.
|
||||
|
||||
The following signatures were changed:
|
||||
|
||||
- `NodeId::System`: Now stores a `SystemKey` instead of a plain `usize`
|
||||
- `NodeId::Set`: Now stores a `SystemSetKey` instead of a plain `usize`
|
||||
- `ScheduleBuildPass::collapse_set`: Now takes the type-specific keys. Wrap them back into a `NodeId` if necessary.
|
||||
- The following functions now return the type-specific keys. Wrap them back into a `NodeId` if necessary.
|
||||
- `Schedule::systems`
|
||||
- `ScheduleGraph::systems`
|
||||
- `ScheduleGraph::system_sets`
|
||||
- `ScheduleGraph::conflicting_systems`
|
||||
- Use the appropriate key types to index these structures rather than bare `usize`s:
|
||||
- `ScheduleGraph::systems` field
|
||||
- `ScheduleGraph::system_conditions`
|
||||
- The following functions now take the type-specific keys. Use pattern matching to extract them from `NodeId`s, if necessary:
|
||||
- `ScheduleGraph::get_system_at`
|
||||
- `ScheduleGraph::system_at`
|
||||
- `ScheduleGraph::get_set_at`
|
||||
- `ScheduleGraph::set_at`
|
||||
- `ScheduleGraph::get_set_conditions_at`
|
||||
- `ScheduleGraph::set_conditions_at`
|
||||
|
||||
The following functions were removed:
|
||||
|
||||
- `NodeId::index`: You should match on and use the `SystemKey` and `SystemSetKey` instead.
|
||||
- `NodeId::cmp`: Use the `PartialOrd` and `Ord` traits instead.
|
Loading…
Reference in New Issue
Block a user