Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - expose ScheduleGraph for third party dependencies #7522

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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