Skip to main content

bevy_ecs/schedule/
pass.rs

1use alloc::{boxed::Box, vec::Vec};
2use core::{
3    any::{Any, TypeId},
4    fmt::Debug,
5    ops::Deref,
6};
7
8use bevy_platform::{collections::HashSet, hash::FixedHasher};
9use bevy_utils::TypeIdMap;
10use indexmap::IndexSet;
11
12use super::{DiGraph, NodeId, ScheduleBuildError, ScheduleGraph};
13use crate::{
14    schedule::{
15        graph::{Dag, DagAnalysis, DiGraphToposortError},
16        SystemKey, SystemSetKey,
17    },
18    world::World,
19};
20
21/// A pass for modular modification of the dependency graph.
22pub trait ScheduleBuildPass: Send + Sync + Debug + 'static {
23    /// Custom options for dependencies between sets or systems.
24    type EdgeOptions: 'static;
25
26    /// Called when a dependency between sets or systems was explicitly added to the graph.
27    fn add_dependency(&mut self, from: NodeId, to: NodeId, options: Option<&Self::EdgeOptions>);
28
29    /// Called while flattening the dependency graph. For each `set`, this method is called
30    /// with the `systems` associated with the set as well as an immutable reference to the current graph.
31    /// Instead of modifying the graph directly, this method should return an iterator of edges to add to the graph.
32    fn collapse_set(
33        &mut self,
34        set: SystemSetKey,
35        systems: &IndexSet<SystemKey, FixedHasher>,
36        dependency_flattening: &DiGraph<NodeId>,
37    ) -> impl Iterator<Item = (NodeId, NodeId)>;
38
39    /// The implementation will be able to modify the `ScheduleGraph` here.
40    fn build(
41        &mut self,
42        world: &mut World,
43        graph: &mut ScheduleGraph,
44        dependency_flattened: FlattenedDependencies<'_>,
45    ) -> Result<(), ScheduleBuildError>;
46}
47
48/// A wrapper around the directed, acyclic graph of system edges.
49///
50/// This allows tracking mutations to the graph for recording build pass changes.
51pub struct FlattenedDependencies<'a> {
52    /// The graph of dependency edges.
53    pub(crate) dag: &'a mut Dag<SystemKey>,
54    /// The edges that have been added by build passes.
55    pub(crate) added_edges: &'a mut HashSet<(SystemKey, SystemKey)>,
56}
57
58impl Deref for FlattenedDependencies<'_> {
59    type Target = Dag<SystemKey>;
60
61    fn deref(&self) -> &Self::Target {
62        self.dag
63    }
64}
65
66impl FlattenedDependencies<'_> {
67    /// Adds an edge to the dependencies such that `system_1` runs before `system_2`.
68    pub fn add_edge(&mut self, system_1: SystemKey, system_2: SystemKey) {
69        self.dag.add_edge(system_1, system_2);
70        self.added_edges.insert((system_1, system_2));
71    }
72
73    /// Removes an edge going from `system_1` to `system_2` in the dependencies.
74    ///
75    /// This should be used with caution - removing edges this way can lead to **very** surprising
76    /// behavior. However, this function can be used to remove dependencies that are made redundant
77    /// by added edges.
78    ///
79    /// Note: these edges are **not** reported like the added edges are.
80    pub fn remove_edge(&mut self, system_1: SystemKey, system_2: SystemKey) {
81        self.dag.remove_edge(system_1, system_2);
82        // We intentionally don't record edges (like `self.added_edges`) because it's unlikely that
83        // users call this for anything other than redundant edges, and because these redundant
84        // edges are actually important. It would be confusing if a visualizer omitted the removed
85        // edges, since an edge you add in your plugin may not appear in the visualizer due to being
86        // removed!
87    }
88
89    /// Returns a topological ordering of the graph, computing it if the graph is dirty.
90    ///
91    /// This function matches [`Dag::toposort`].
92    pub fn toposort(&mut self) -> Result<&[SystemKey], DiGraphToposortError<SystemKey>> {
93        self.dag.toposort()
94    }
95
96    /// Returns both the topological ordering and the underlying graph, computing the toposort if
97    /// the graph is dirty.
98    ///
99    /// This function matches [`Dag::toposort_and_graph`].
100    pub fn toposort_and_graph(
101        &mut self,
102    ) -> Result<(&[SystemKey], &DiGraph<SystemKey>), DiGraphToposortError<SystemKey>> {
103        self.dag.toposort_and_graph()
104    }
105
106    /// Processes the DAG and computes various properties about it.
107    ///
108    /// This function matches [`Dag::analyze`].
109    pub fn analyze(&mut self) -> Result<DagAnalysis<SystemKey>, DiGraphToposortError<SystemKey>> {
110        self.dag.analyze()
111    }
112}
113
114/// Object safe version of [`ScheduleBuildPass`].
115pub(super) trait ScheduleBuildPassObj: Send + Sync + Debug {
116    fn build(
117        &mut self,
118        world: &mut World,
119        graph: &mut ScheduleGraph,
120        dependency_flattened: FlattenedDependencies<'_>,
121    ) -> Result<(), ScheduleBuildError>;
122
123    fn collapse_set(
124        &mut self,
125        set: SystemSetKey,
126        systems: &IndexSet<SystemKey, FixedHasher>,
127        dependency_flattening: &DiGraph<NodeId>,
128        dependencies_to_add: &mut Vec<(NodeId, NodeId)>,
129    );
130    fn add_dependency(&mut self, from: NodeId, to: NodeId, all_options: &TypeIdMap<Box<dyn Any>>);
131}
132
133impl<T: ScheduleBuildPass> ScheduleBuildPassObj for T {
134    fn build(
135        &mut self,
136        world: &mut World,
137        graph: &mut ScheduleGraph,
138        dependency_flattened: FlattenedDependencies<'_>,
139    ) -> Result<(), ScheduleBuildError> {
140        self.build(world, graph, dependency_flattened)
141    }
142    fn collapse_set(
143        &mut self,
144        set: SystemSetKey,
145        systems: &IndexSet<SystemKey, FixedHasher>,
146        dependency_flattening: &DiGraph<NodeId>,
147        dependencies_to_add: &mut Vec<(NodeId, NodeId)>,
148    ) {
149        let iter = self.collapse_set(set, systems, dependency_flattening);
150        dependencies_to_add.extend(iter);
151    }
152    fn add_dependency(&mut self, from: NodeId, to: NodeId, all_options: &TypeIdMap<Box<dyn Any>>) {
153        let option = all_options
154            .get(&TypeId::of::<T::EdgeOptions>())
155            .and_then(|x| x.downcast_ref::<T::EdgeOptions>());
156        self.add_dependency(from, to, option);
157    }
158}