-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
System sets and run criteria v2 (#1675)
I'm opening this prematurely; consider this an RFC that predates RFCs and therefore not super-RFC-like. This PR does two "big" things: decouple run criteria from system sets, reimagine system sets as weapons of mass system description. ### What it lets us do: * Reuse run criteria within a stage. * Pipe output of one run criteria as input to another. * Assign labels, dependencies, run criteria, and ambiguity sets to many systems at the same time. ### Things already done: * Decoupled run criteria from system sets. * Mass system description superpowers to `SystemSet`. * Implemented `RunCriteriaDescriptor`. * Removed `VirtualSystemSet`. * Centralized all run criteria of `SystemStage`. * Extended system descriptors with per-system run criteria. * `.before()` and `.after()` for run criteria. * Explicit order between state driver and related run criteria. Fixes #1672. * Opt-in run criteria deduplication; default behavior is to panic. * Labels (not exposed) for state run criteria; state run criteria are deduplicated. ### API issues that need discussion: * [`FixedTimestep::step(1.0).label("my label")`](https://github.com/Ratysz/bevy/blob/eaccf857cdaeb5a5632b6e75feab5c1ad6267d1d/crates/bevy_ecs/src/schedule/run_criteria.rs#L120-L122) and [`FixedTimestep::step(1.0).with_label("my label")`](https://github.com/Ratysz/bevy/blob/eaccf857cdaeb5a5632b6e75feab5c1ad6267d1d/crates/bevy_core/src/time/fixed_timestep.rs#L86-L89) are both valid but do very different things. --- I will try to maintain this post up-to-date as things change. Do check the diffs in "edited" thingy from time to time. Co-authored-by: Carter Anderson <[email protected]>
- Loading branch information
Showing
16 changed files
with
1,429 additions
and
492 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
use bevy_utils::{tracing::warn, HashMap, HashSet}; | ||
use fixedbitset::FixedBitSet; | ||
use std::{borrow::Cow, fmt::Debug, hash::Hash}; | ||
|
||
pub enum DependencyGraphError<Labels> { | ||
GraphCycles(Vec<(usize, Labels)>), | ||
} | ||
|
||
pub trait GraphNode<Label> { | ||
fn name(&self) -> Cow<'static, str>; | ||
fn labels(&self) -> &[Label]; | ||
fn before(&self) -> &[Label]; | ||
fn after(&self) -> &[Label]; | ||
} | ||
|
||
/// Constructs a dependency graph of given nodes. | ||
pub fn build_dependency_graph<Node, Label>( | ||
nodes: &[Node], | ||
) -> HashMap<usize, HashMap<usize, HashSet<Label>>> | ||
where | ||
Node: GraphNode<Label>, | ||
Label: Debug + Clone + Eq + Hash, | ||
{ | ||
let mut labels = HashMap::<Label, FixedBitSet>::default(); | ||
for (label, index) in nodes.iter().enumerate().flat_map(|(index, container)| { | ||
container | ||
.labels() | ||
.iter() | ||
.cloned() | ||
.map(move |label| (label, index)) | ||
}) { | ||
labels | ||
.entry(label) | ||
.or_insert_with(|| FixedBitSet::with_capacity(nodes.len())) | ||
.insert(index); | ||
} | ||
let mut graph = HashMap::with_capacity_and_hasher(nodes.len(), Default::default()); | ||
for (index, node) in nodes.iter().enumerate() { | ||
let dependencies = graph.entry(index).or_insert_with(HashMap::default); | ||
for label in node.after() { | ||
match labels.get(label) { | ||
Some(new_dependencies) => { | ||
for dependency in new_dependencies.ones() { | ||
dependencies | ||
.entry(dependency) | ||
.or_insert_with(HashSet::default) | ||
.insert(label.clone()); | ||
} | ||
} | ||
None => warn!( | ||
// TODO: plumb this as proper output? | ||
"{} wants to be after unknown label: {:?}", | ||
nodes[index].name(), | ||
label | ||
), | ||
} | ||
} | ||
for label in node.before() { | ||
match labels.get(label) { | ||
Some(dependants) => { | ||
for dependant in dependants.ones() { | ||
graph | ||
.entry(dependant) | ||
.or_insert_with(HashMap::default) | ||
.entry(index) | ||
.or_insert_with(HashSet::default) | ||
.insert(label.clone()); | ||
} | ||
} | ||
None => warn!( | ||
"{} wants to be before unknown label: {:?}", | ||
nodes[index].name(), | ||
label | ||
), | ||
} | ||
} | ||
} | ||
graph | ||
} | ||
|
||
/// Generates a topological order for the given graph. | ||
pub fn topological_order<Labels: Clone>( | ||
graph: &HashMap<usize, HashMap<usize, Labels>>, | ||
) -> Result<Vec<usize>, DependencyGraphError<Labels>> { | ||
fn check_if_cycles_and_visit<L>( | ||
node: &usize, | ||
graph: &HashMap<usize, HashMap<usize, L>>, | ||
sorted: &mut Vec<usize>, | ||
unvisited: &mut HashSet<usize>, | ||
current: &mut Vec<usize>, | ||
) -> bool { | ||
if current.contains(node) { | ||
return true; | ||
} else if !unvisited.remove(node) { | ||
return false; | ||
} | ||
current.push(*node); | ||
for dependency in graph.get(node).unwrap().keys() { | ||
if check_if_cycles_and_visit(dependency, &graph, sorted, unvisited, current) { | ||
return true; | ||
} | ||
} | ||
sorted.push(*node); | ||
current.pop(); | ||
false | ||
} | ||
let mut sorted = Vec::with_capacity(graph.len()); | ||
let mut current = Vec::with_capacity(graph.len()); | ||
let mut unvisited = HashSet::with_capacity_and_hasher(graph.len(), Default::default()); | ||
unvisited.extend(graph.keys().cloned()); | ||
while let Some(node) = unvisited.iter().next().cloned() { | ||
if check_if_cycles_and_visit(&node, graph, &mut sorted, &mut unvisited, &mut current) { | ||
let mut cycle = Vec::new(); | ||
let last_window = [*current.last().unwrap(), current[0]]; | ||
let mut windows = current | ||
.windows(2) | ||
.chain(std::iter::once(&last_window as &[usize])); | ||
while let Some(&[dependant, dependency]) = windows.next() { | ||
cycle.push((dependant, graph[&dependant][&dependency].clone())); | ||
} | ||
return Err(DependencyGraphError::GraphCycles(cycle)); | ||
} | ||
} | ||
Ok(sorted) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.