1use std::{any::TypeId, borrow::Cow, ops::Deref};
3
4use ::{
5 bevy_ecs::{
6 schedule::{
7 InternedScheduleLabel, InternedSystemSet, NodeId, Schedule, ScheduleLabel, SystemSet,
8 },
9 system::{System, SystemInput},
10 },
11 bevy_platform::collections::{HashMap, HashSet},
12 bevy_reflect::Reflect,
13};
14use bevy_log::warn;
15use dot_writer::{Attributes, DotWriter};
16
17#[derive(Reflect, Debug, Clone)]
18#[reflect(opaque)]
19pub struct ReflectSystem {
21 pub(crate) name: Cow<'static, str>,
22 pub(crate) type_id: TypeId,
23 pub(crate) node_id: ReflectNodeId,
24 pub(crate) default_system_sets: Vec<InternedSystemSet>,
25}
26
27impl ReflectSystem {
28 pub fn name(&self) -> &str {
30 self.name.as_ref()
31 }
32
33 pub fn type_id(&self) -> TypeId {
35 self.type_id
36 }
37
38 pub fn node_id(&self) -> NodeId {
40 self.node_id.0
41 }
42
43 pub fn default_system_sets(&self) -> &[InternedSystemSet] {
45 &self.default_system_sets
46 }
47 pub fn from_system<In: SystemInput + 'static, Out: 'static>(
49 system: &dyn System<In = In, Out = Out>,
50 node_id: NodeId,
51 ) -> Self {
52 ReflectSystem {
53 name: system.name().clone(),
54 type_id: system.type_id(),
55 node_id: ReflectNodeId(node_id),
56 default_system_sets: system.default_system_sets(),
57 }
58 }
59
60 pub fn identifier(&self) -> &str {
62 if self.name.contains("<") {
64 self.name
65 .split("<")
66 .next()
67 .unwrap_or_default()
68 .split("::")
69 .last()
70 .unwrap_or_default()
71 } else {
72 self.name.split("::").last().unwrap_or_default()
73 }
74 }
75
76 pub fn path(&self) -> &str {
78 self.name.as_ref()
79 }
80}
81
82#[derive(Reflect, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
83#[reflect(opaque)]
84pub(crate) struct ReflectNodeId(pub NodeId);
85
86#[derive(Reflect, Clone, Debug)]
88pub struct ReflectSchedule {
89 type_path: &'static str,
91 label: ReflectableScheduleLabel,
92}
93
94#[derive(Reflect, Clone, Debug)]
95#[reflect(opaque)]
96struct ReflectableScheduleLabel(InternedScheduleLabel);
97
98impl Deref for ReflectableScheduleLabel {
99 type Target = InternedScheduleLabel;
100
101 fn deref(&self) -> &Self::Target {
102 &self.0
103 }
104}
105
106impl From<InternedScheduleLabel> for ReflectableScheduleLabel {
107 fn from(label: InternedScheduleLabel) -> Self {
108 Self(label)
109 }
110}
111
112impl ReflectSchedule {
113 pub fn type_path(&self) -> &'static str {
115 self.type_path
116 }
117
118 pub fn identifier(&self) -> &'static str {
120 self.type_path.split("::").last().unwrap_or_default()
121 }
122
123 pub fn label(&self) -> &InternedScheduleLabel {
125 &self.label
126 }
127
128 pub fn from_label<T: ScheduleLabel + 'static>(label: T) -> Self {
130 ReflectSchedule {
131 type_path: std::any::type_name::<T>(),
132 label: label.intern().into(),
133 }
134 }
135}
136
137#[derive(Reflect)]
138pub struct ReflectSystemSet {
140 node_id: ReflectNodeId,
142
143 debug: String,
145
146 type_id: Option<TypeId>,
148}
149
150impl ReflectSystemSet {
151 pub fn from_set(set: &dyn SystemSet, node_id: NodeId) -> Self {
153 ReflectSystemSet {
154 node_id: ReflectNodeId(node_id),
155 debug: format!("{set:?}"),
156 type_id: set.system_type(),
157 }
158 }
159}
160
161pub fn schedule_to_dot_graph(schedule: &Schedule) -> String {
163 let graph = schedule_to_reflect_graph(schedule);
164 reflect_graph_to_dot(graph)
165}
166
167pub fn reflect_graph_to_dot(graph: ReflectSystemGraph) -> String {
169 let mut output_bytes = Vec::new();
173 let mut writer = DotWriter::from(&mut output_bytes);
174 {
175 let mut writer = writer.digraph();
176
177 let mut node_id_map = HashMap::new();
178 for node in graph.nodes {
179 match node {
180 ReflectSystemGraphNode::System(reflect_system) => {
181 let mut node = writer.node_auto();
182
183 node.set_label(&reflect_system.name);
184 node_id_map.insert(reflect_system.node_id, node.id());
185 }
186 ReflectSystemGraphNode::SystemSet(reflect_system_set) => {
187 let name = if reflect_system_set.type_id.is_some() {
188 "SystemTypeSet".to_owned()
190 } else {
191 format!("SystemSet {}", reflect_system_set.debug)
192 };
193
194 let mut node = writer.node_auto();
195 node.set_label(&name);
196 node_id_map.insert(reflect_system_set.node_id, node.id());
197 }
198 }
199 }
200
201 for edge in graph.hierarchy {
203 let from = node_id_map.get(&edge.from).cloned().unwrap_or_else(|| {
204 let mut unknown = writer.node_auto();
205 unknown.set_label(&format!("unknown_parent {:?}", edge.from.0));
206 let id = unknown.id();
207 node_id_map.insert(edge.from, id.clone());
208 id
209 });
210 let to = node_id_map.get(&edge.to).cloned().unwrap_or_else(|| {
211 let mut unknown = writer.node_auto();
212 unknown.set_label(&format!("unknown_child {:?}", edge.to.0));
213 let id = unknown.id();
214 node_id_map.insert(edge.to, id.clone());
215 id
216 });
217 writer
218 .edge(to, from)
219 .attributes()
220 .set_color(dot_writer::Color::Red)
221 .set_label("child of")
222 .set_arrow_head(dot_writer::ArrowType::Diamond);
223 }
224 for edge in graph.dependencies {
226 let from = node_id_map.get(&edge.from).cloned().unwrap_or_else(|| {
227 let mut unknown = writer.node_auto();
228 unknown.set_label(&format!("unknown_dependant {:?}", edge.from.0));
229 let id = unknown.id();
230 node_id_map.insert(edge.from, id.clone());
231 id
232 });
233 let to = node_id_map.get(&edge.to).cloned().unwrap_or_else(|| {
234 let mut unknown = writer.node_auto();
235 unknown.set_label(&format!("unknown_dependency {:?}", edge.to.0));
236 let id = unknown.id();
237 node_id_map.insert(edge.to, id.clone());
238 id
239 });
240 writer
241 .edge(from, to)
242 .attributes()
243 .set_color(dot_writer::Color::Blue)
244 .set_label("runs before")
245 .set_arrow_head(dot_writer::ArrowType::Normal);
246 }
247 }
248
249 String::from_utf8(output_bytes).unwrap_or_default()
250}
251
252pub fn schedule_to_reflect_graph(schedule: &Schedule) -> ReflectSystemGraph {
254 let graph = schedule.graph();
255 let hierarchy = graph.hierarchy().graph();
256 let dependency = graph.dependency().graph();
257
258 let mut nodes = Vec::new();
259 let mut covered_nodes = HashSet::new();
260 for (node_id, system_set, _) in graph.system_sets() {
261 covered_nodes.insert(node_id);
262 nodes.push(ReflectSystemGraphNode::SystemSet(
263 ReflectSystemSet::from_set(system_set, node_id),
264 ));
265 }
266
267 if let Ok(systems) = schedule.systems() {
269 for (node_id, system) in systems {
270 covered_nodes.insert(node_id);
271 nodes.push(ReflectSystemGraphNode::System(ReflectSystem::from_system(
272 system.as_ref(),
273 node_id,
274 )));
275 }
276 }
277
278 for node_id in hierarchy.nodes() {
281 if covered_nodes.contains(&node_id) {
282 continue;
283 }
284
285 warn!("Found uncovered node {node_id:?}");
286 }
287
288 let dependencies = dependency
289 .all_edges()
290 .map(|(from, to)| Edge {
291 from: ReflectNodeId(from),
292 to: ReflectNodeId(to),
293 })
294 .collect();
295
296 let hierarchy = hierarchy
297 .all_edges()
298 .map(|(from, to)| Edge {
299 from: ReflectNodeId(from),
300 to: ReflectNodeId(to),
301 })
302 .collect();
303
304 ReflectSystemGraph {
305 schedule: ReflectSchedule::from_label(schedule.label()),
306 nodes,
307 dependencies,
308 hierarchy,
309 }
310}
311
312#[derive(Reflect)]
314pub struct ReflectSystemGraph {
315 schedule: ReflectSchedule,
317 nodes: Vec<ReflectSystemGraphNode>,
319
320 dependencies: Vec<Edge>,
324
325 hierarchy: Vec<Edge>,
328}
329
330impl ReflectSystemGraph {
331 pub fn sort(&mut self) {
335 self.nodes.sort_by_key(|node| match node {
337 ReflectSystemGraphNode::System(system) => system.node_id.0,
338 ReflectSystemGraphNode::SystemSet(system_set) => system_set.node_id.0,
339 });
340
341 self.dependencies.sort();
342
343 self.hierarchy.sort();
344 }
345
346 fn absorb_set(&mut self, node_id: NodeId) {
348 let mut hierarchy_parents = Vec::new();
350 let mut hierarchy_children = Vec::new();
351 for edge in &self.hierarchy {
353 if edge.to.0 == node_id {
355 hierarchy_children.push(edge.from.clone());
356 }
357 if edge.from.0 == node_id {
359 hierarchy_parents.push(edge.to.clone());
360 }
361 }
362
363 let mut dependencies = Vec::new();
365 let mut dependents = Vec::new();
366 for edge in &self.dependencies {
368 if edge.to.0 == node_id {
369 dependencies.push(edge.from.clone());
370 }
371 if edge.from.0 == node_id {
372 dependents.push(edge.to.clone());
373 }
374 }
375
376 let mut new_hierarchy_edges =
377 HashSet::with_capacity(hierarchy_parents.len() * hierarchy_children.len());
378 let mut new_dependency_edges =
379 HashSet::with_capacity(dependencies.len() * dependents.len());
380
381 for parent in hierarchy_parents.iter() {
383 for child in hierarchy_children.iter() {
384 new_hierarchy_edges.insert(Edge {
385 from: child.clone(),
386 to: parent.clone(),
387 });
388 }
389 }
390
391 for child in hierarchy_parents.iter() {
392 for dependency in dependencies.iter() {
394 new_dependency_edges.insert(Edge {
395 from: dependency.clone(),
396 to: child.clone(),
397 });
398 }
399
400 for dependent in dependents.iter() {
401 new_dependency_edges.insert(Edge {
402 from: child.clone(),
403 to: dependent.clone(),
404 });
405 }
406 }
407
408 self.hierarchy
410 .retain(|edge| edge.from.0 != node_id && edge.to.0 != node_id);
411 self.dependencies
412 .retain(|edge| edge.from.0 != node_id && edge.to.0 != node_id);
413
414 self.nodes.retain(|node| match node {
416 ReflectSystemGraphNode::SystemSet(system_set) => system_set.node_id.0 != node_id,
417 _ => true,
418 });
419
420 self.hierarchy.extend(new_hierarchy_edges);
422 self.dependencies.extend(new_dependency_edges);
423 }
424
425 pub fn absorb_type_system_sets(&mut self) {
431 let type_sets = self
432 .nodes
433 .iter()
434 .filter_map(|node| match node {
435 ReflectSystemGraphNode::SystemSet(system_set) => {
436 if system_set.type_id.is_some() {
437 Some(system_set.node_id.0)
438 } else {
439 None
440 }
441 }
442 _ => None,
443 })
444 .collect::<Vec<_>>();
445
446 for node_id in type_sets {
448 self.absorb_set(node_id);
449 }
450 }
451}
452
453#[derive(Reflect)]
455pub enum ReflectSystemGraphNode {
456 System(ReflectSystem),
458 SystemSet(ReflectSystemSet),
460}
461
462#[derive(Reflect, PartialEq, Eq, PartialOrd, Ord, Hash)]
464pub struct Edge {
465 from: ReflectNodeId,
467 to: ReflectNodeId,
469}
470
471#[cfg(test)]
472mod test {
473 use ::{
474 bevy_app::Update,
475 bevy_ecs::{schedule::IntoScheduleConfigs, world::World},
476 };
477
478 use super::*;
479
480 fn system_a() {}
481
482 fn system_b() {}
483
484 fn system_c() {}
485
486 fn system_d() {}
487
488 fn system_e() {}
489
490 const BLESS_MODE: bool = false;
491
492 #[test]
493 fn test_graph_is_as_expected() {
494 let mut schedule = Schedule::new(Update);
497
498 #[derive(SystemSet, Hash, PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Copy)]
499 enum SystemSet {
500 SystemSetG,
501 SystemSetH,
502 }
503
504 schedule
507 .add_systems(system_a)
508 .add_systems(system_b.before(system_a))
509 .add_systems(system_c.after(system_b).before(SystemSet::SystemSetH))
510 .add_systems(system_d.in_set(SystemSet::SystemSetG))
511 .add_systems(system_e.in_set(SystemSet::SystemSetH))
512 .configure_sets(SystemSet::SystemSetG.after(SystemSet::SystemSetH));
513 let mut world = World::new();
514 schedule.initialize(&mut world).unwrap();
515
516 let mut graph = schedule_to_reflect_graph(&schedule);
517 graph.absorb_type_system_sets();
518 graph.sort();
519 let dot = reflect_graph_to_dot(graph);
520
521 let normalize = |s: &str| {
522 let lines: Vec<&str> = s.lines().map(|line| line.trim_start()).collect();
524 lines
525 .join("\n")
526 .replace(" = ", "")
527 .replace(";", "")
528 .replace(",", "")
529 .trim()
530 .to_string()
531 };
532
533 let expected = include_str!("../test_graph.dot");
536 let expected_path = manifest_dir_macros::file_path!("test_graph.dot");
537
538 if BLESS_MODE {
539 std::fs::write(expected_path, normalize(&dot)).unwrap();
540 panic!("Bless mode is active");
541 } else {
542 pretty_assertions::assert_eq!(normalize(&dot), normalize(expected));
543 }
544 }
545}