expose ScheduleGraph for third party dependencies (#7522)
# 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.
This commit is contained in:
parent
8853bef6df
commit
b35818c0a3
@ -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>>,
|
||||
|
||||
@ -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,
|
||||
}
|
||||
|
||||
@ -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::*;
|
||||
|
||||
@ -19,7 +19,7 @@ use crate::{
|
||||
self as bevy_ecs,
|
||||
component::{ComponentId, Components},
|
||||
schedule::*,
|
||||
system::{BoxedSystem, Resource},
|
||||
system::{BoxedSystem, Resource, System},
|
||||
world::World,
|
||||
};
|
||||
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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.
|
||||
@ -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),
|
||||
@ -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>,
|
||||
@ -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();
|
||||
@ -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> {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user