Skip to content

Commit

Permalink
expose ScheduleGraph for third party dependencies (bevyengine#7522)
Browse files Browse the repository at this point in the history
# Objective

The `ScheduleGraph` should be expose so that crates like [bevy_mod_debugdump](https://github.com/jakobhellermann/bevy_mod_debugdump/blob/stageless/docs/README.md) can access useful information. 

## Solution

- expose `ScheduleGraph`, `NodeId`, `BaseSetMembership`, `Dag`
- add accessor functions for sets and systems

## Changelog

- expose `ScheduleGraph` for use in third party tools

This does expose our use of `petgraph` as a graph library, so we can only change that as a breaking change.
  • Loading branch information
jakobhellermann authored and myreprise1 committed Feb 16, 2023
1 parent 9e81a1d commit 238e62c
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 8 deletions.
2 changes: 1 addition & 1 deletion crates/bevy_ecs/src/schedule/executor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ pub enum ExecutorKind {
/// Since the arrays are sorted in the same order, elements are referenced by their index.
/// `FixedBitSet` is used as a smaller, more efficient substitute of `HashSet<usize>`.
#[derive(Default)]
pub(super) struct SystemSchedule {
pub struct SystemSchedule {
pub(super) systems: Vec<BoxedSystem>,
pub(super) system_conditions: Vec<Vec<BoxedCondition>>,
pub(super) set_conditions: Vec<Vec<BoxedCondition>>,
Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_ecs/src/schedule/graph_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ use crate::schedule::set::*;

/// Unique identifier for a system or system set.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(crate) enum NodeId {
pub enum NodeId {
System(usize),
Set(usize),
}

impl NodeId {
/// Returns the internal integer value.
pub fn index(&self) -> usize {
pub(crate) fn index(&self) -> usize {
match self {
NodeId::System(index) | NodeId::Set(index) => *index,
}
Expand Down
2 changes: 2 additions & 0 deletions crates/bevy_ecs/src/schedule/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ pub use self::schedule::*;
pub use self::set::*;
pub use self::state::*;

pub use self::graph_utils::NodeId;

#[cfg(test)]
mod tests {
use super::*;
Expand Down
146 changes: 141 additions & 5 deletions crates/bevy_ecs/src/schedule/schedule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use crate::{
self as bevy_ecs,
component::{ComponentId, Components},
schedule::*,
system::{BoxedSystem, Resource},
system::{BoxedSystem, Resource, System},
world::World,
};

Expand Down Expand Up @@ -83,6 +83,19 @@ impl Schedules {
self.inner.get_mut(label)
}

/// Returns an iterator over all schedules. Iteration order is undefined.
pub fn iter(&self) -> impl Iterator<Item = (&dyn ScheduleLabel, &Schedule)> {
self.inner
.iter()
.map(|(label, schedule)| (&**label, schedule))
}
/// Returns an iterator over mutable references to all schedules. Iteration order is undefined.
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&dyn ScheduleLabel, &mut Schedule)> {
self.inner
.iter_mut()
.map(|(label, schedule)| (&**label, schedule))
}

/// Iterates the change ticks of all systems in all stored schedules and clamps any older than
/// [`MAX_CHANGE_AGE`](crate::change_detection::MAX_CHANGE_AGE).
/// This prevents overflow and thus prevents false positives.
Expand Down Expand Up @@ -216,6 +229,11 @@ impl Schedule {
Ok(())
}

/// Returns the [`ScheduleGraph`].
pub fn graph(&self) -> &ScheduleGraph {
&self.graph
}

/// Iterates the change ticks of all systems in the schedule and clamps any older than
/// [`MAX_CHANGE_AGE`](crate::change_detection::MAX_CHANGE_AGE).
/// This prevents overflow and thus prevents false positives.
Expand Down Expand Up @@ -254,7 +272,7 @@ impl Schedule {

/// A directed acylic graph structure.
#[derive(Default)]
struct Dag {
pub struct Dag {
/// A directed graph.
graph: DiGraphMap<NodeId, ()>,
/// A cached topological ordering of the graph.
Expand All @@ -268,10 +286,26 @@ impl Dag {
topsort: Vec::new(),
}
}

/// The directed graph of the stored systems, connected by their ordering dependencies.
pub fn graph(&self) -> &DiGraphMap<NodeId, ()> {
&self.graph
}

/// A cached topological ordering of the graph.
///
/// The order is determined by the ordering dependencies between systems.
pub fn cached_topsort(&self) -> &[NodeId] {
&self.topsort
}
}

/// Describes which base set (i.e. [`SystemSet`] where [`SystemSet::is_base`] returns true)
/// a system belongs to.
///
/// Note that this is only populated once [`ScheduleGraph::build_schedule`] is called.
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
enum BaseSetMembership {
pub enum BaseSetMembership {
Uncalculated,
None,
Some(NodeId),
Expand Down Expand Up @@ -329,7 +363,7 @@ impl SystemNode {

/// Metadata for a [`Schedule`].
#[derive(Default)]
struct ScheduleGraph {
pub struct ScheduleGraph {
systems: Vec<SystemNode>,
system_conditions: Vec<Option<Vec<BoxedCondition>>>,
system_sets: Vec<SystemSetNode>,
Expand Down Expand Up @@ -370,6 +404,102 @@ impl ScheduleGraph {
}
}

/// Returns the system at the given [`NodeId`], if it exists.
pub fn get_system_at(&self, id: NodeId) -> Option<&dyn System<In = (), Out = ()>> {
if !id.is_system() {
return None;
}
self.systems
.get(id.index())
.and_then(|system| system.inner.as_deref())
}

/// Returns the system at the given [`NodeId`].
///
/// Panics if it doesn't exist.
#[track_caller]
pub fn system_at(&self, id: NodeId) -> &dyn System<In = (), Out = ()> {
self.get_system_at(id)
.ok_or_else(|| format!("system with id {id:?} does not exist in this Schedule"))
.unwrap()
}

/// 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)
}

/// Returns the set at the given [`NodeId`].
///
/// Panics if it doesn't exist.
#[track_caller]
pub fn set_at(&self, id: NodeId) -> &dyn SystemSet {
self.get_set_at(id)
.ok_or_else(|| format!("set with id {id:?} does not exist in this Schedule"))
.unwrap()
}

/// Returns an iterator over all systems in this schedule.
///
/// Note that the [`BaseSetMembership`] will only be initialized after [`ScheduleGraph::build_schedule`] is called.
pub fn systems(
&self,
) -> impl Iterator<
Item = (
NodeId,
&dyn System<In = (), Out = ()>,
BaseSetMembership,
&[BoxedCondition],
),
> {
self.systems
.iter()
.zip(self.system_conditions.iter())
.enumerate()
.filter_map(|(i, (system_node, condition))| {
let system = system_node.inner.as_deref()?;
let base_set_membership = system_node.base_set_membership;
let condition = condition.as_ref()?.as_slice();
Some((NodeId::System(i), system, base_set_membership, condition))
})
}

/// Returns an iterator over all system sets in this schedule.
///
/// Note that the [`BaseSetMembership`] will only be initialized after [`ScheduleGraph::build_schedule`] is called.
pub fn system_sets(
&self,
) -> impl Iterator<Item = (NodeId, &dyn SystemSet, BaseSetMembership, &[BoxedCondition])> {
self.system_set_ids.iter().map(|(_, node_id)| {
let set_node = &self.system_sets[node_id.index()];
let set = &*set_node.inner;
let base_set_membership = set_node.base_set_membership;
let conditions = self.system_set_conditions[node_id.index()]
.as_deref()
.unwrap_or(&[]);
(*node_id, set, base_set_membership, conditions)
})
}

/// Returns the [`Dag`] of the hierarchy.
///
/// The hierarchy is a directed acyclic graph of the systems and sets,
/// where an edge denotes that a system or set is the child of another set.
pub fn hierarchy(&self) -> &Dag {
&self.hierarchy
}

/// Returns the [`Dag`] of the dependencies in the schedule.
///
/// Nodes in this graph are systems and sets, and edges denote that
/// a system or set has to run before another system or set.
pub fn dependency(&self) -> &Dag {
&self.dependency
}

fn add_systems<P>(&mut self, systems: impl IntoSystemConfigs<P>) {
let SystemConfigs { systems, chained } = systems.into_configs();
let mut system_iter = systems.into_iter();
Expand Down Expand Up @@ -751,7 +881,13 @@ impl ScheduleGraph {
Ok(base_set)
}

fn build_schedule(
/// Build a [`SystemSchedule`] optimized for scheduler access from the [`ScheduleGraph`].
///
/// This method also
/// - calculates [`BaseSetMembership`]
/// - checks for dependency or hierarchy cycles
/// - checks for system access conflicts and reports ambiguities
pub fn build_schedule(
&mut self,
components: &Components,
) -> Result<SystemSchedule, ScheduleBuildError> {
Expand Down

0 comments on commit 238e62c

Please sign in to comment.