From 2733ad83cbd90e151c94adec08aef1d42d4330f9 Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Sat, 23 Mar 2024 06:31:11 +0100 Subject: [PATCH 01/11] events graph --- Cargo.toml | 3 + examples/print_events_graph.rs | 8 ++ src/event_graph/mod.rs | 154 +++++++++++++++++++++++++++++++++ src/lib.rs | 43 +++++++++ 4 files changed, 208 insertions(+) create mode 100644 examples/print_events_graph.rs create mode 100644 src/event_graph/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 43d49df..6e696d7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,9 @@ bevy = { version = "0.13" } name = "print_render_graph" required-features = ["render_graph"] +[[example]] +name = "print_events_graph" + # [patch.crates-io] # bevy_ecs = { path = "/home/jakob/dev/rust/bevy/crates/bevy_ecs" } # bevy_app = { path = "/home/jakob/dev/rust/bevy/crates/bevy_app" } diff --git a/examples/print_events_graph.rs b/examples/print_events_graph.rs new file mode 100644 index 0000000..d8d21be --- /dev/null +++ b/examples/print_events_graph.rs @@ -0,0 +1,8 @@ +use bevy::log::LogPlugin; +use bevy::prelude::*; + +fn main() { + let mut app = App::new(); + app.add_plugins(DefaultPlugins.build().disable::()); + bevy_mod_debugdump::print_events_graph(&mut app, PostUpdate); +} diff --git a/src/event_graph/mod.rs b/src/event_graph/mod.rs new file mode 100644 index 0000000..f668ec7 --- /dev/null +++ b/src/event_graph/mod.rs @@ -0,0 +1,154 @@ +use bevy_ecs::{ + schedule::{NodeId, Schedule}, + world::World, +}; +use bevy_utils::hashbrown::{HashMap, HashSet}; + +use crate::dot::DotGraph; + +/// Formats the events into a dot graph. +pub fn events_graph_dot( + schedule: &Schedule, + world: &World, + settings: &crate::schedule_graph::Settings, +) -> String { + let graph = schedule.graph(); + + /* + let hierarchy = graph.hierarchy().graph(); + let dependency = graph.dependency().graph(); + */ + + let mut events_tracked = HashSet::new(); + let mut event_readers = HashMap::<&str, Vec>::new(); + let mut event_writers = HashMap::<&str, Vec>::new(); + for (system_id, system, _condition) in graph.systems() { + let accesses = system.component_access(); + for access in accesses.reads() { + let component = world.components().get_info(access).unwrap(); + let name = component.name(); + if name.starts_with("bevy_ecs::event::Events") { + // TODO: avoid relying on name, use TypeId ? + events_tracked.insert(name); + match event_readers.entry(name) { + bevy_utils::hashbrown::hash_map::Entry::Occupied(mut entry) => { + entry.get_mut().push(system_id) + } + bevy_utils::hashbrown::hash_map::Entry::Vacant(vacant) => { + vacant.insert([system_id].into()); + } + } + } + } + for access in accesses.writes() { + // TODO: avoid copying code? + let component = world.components().get_info(access).unwrap(); + let name = component.name(); + if name.starts_with("bevy_ecs::event::Events") { + events_tracked.insert(name); + + match event_writers.entry(name) { + bevy_utils::hashbrown::hash_map::Entry::Occupied(mut entry) => { + entry.get_mut().push(system_id) + } + bevy_utils::hashbrown::hash_map::Entry::Vacant(vacant) => { + vacant.insert([system_id].into()); + } + } + } + } + } + let mut dot = DotGraph::new( + "", + "digraph", + &[ + ("compound", "true"), // enable ltail/lhead + ("splines", settings.style.edge_style.as_dot()), + ("rankdir", settings.style.schedule_rankdir.as_dot()), + ("bgcolor", &settings.style.color_background), + ("fontname", &settings.style.fontname), + ("nodesep", "0.15"), + ], + ) + .edge_attributes(&[("penwidth", &format!("{}", settings.style.penwidth_edge))]) + .node_attributes(&[("shape", "box"), ("style", "filled")]); + + let all_writers = event_writers + .values() + .flatten() + .copied() + .collect::>(); + let all_readers = event_readers + .values() + .flatten() + .copied() + .collect::>(); + + let all_systems = all_writers + .iter() + .chain(all_readers.iter()) + .collect::>() + .into_iter() + .collect::>(); + + for s in all_systems { + let name = &graph.get_system_at(*s).unwrap().name(); + let color = match (all_writers.contains(s), all_readers.contains(s)) { + (true, false) => "yellow", + (false, true) => "red", + (true, true) => "orange", + _ => panic!("Unexpected event handled."), + }; + dot.add_node( + &node_string(s), + &[ + ("color", color), + ( + "label", + name.split('<').next().unwrap().split("::").last().unwrap(), + ), + ("tooltip", name), + ("shape", "box"), + ], + ); + } + + for event in events_tracked { + let readers = event_readers.entry(event).or_default(); + let writers = event_writers.entry(event).or_default(); + + let name = event + .split_once('<') + .unwrap() + .1 /* .split("::").last() + .unwrap()*/; + let name = &name[0..name.len() - 1]; + dot.add_node( + event, + &[ + ("color", "green"), + ("label", name), + ("tooltip", "nice event"), + ("shape", "ellipse"), + ], + ); + for writer in writers { + dot.add_edge(&node_string(writer), event, &[]); + } + for reader in readers { + dot.add_edge(event, &node_string(reader), &[]); + } + } + dot.finish().to_string() +} + +/// Internal but we use that as identifiers +fn node_index(node_id: &NodeId) -> usize { + match node_id { + NodeId::System(index) | NodeId::Set(index) => *index, + } +} +/// Internal but we use that as identifiers +fn node_string(node_id: &NodeId) -> String { + node_index(node_id).to_string() +} diff --git a/src/lib.rs b/src/lib.rs index 37f12df..3c8aa95 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ use bevy_ecs::schedule::{ScheduleLabel, Schedules}; mod dot; +pub mod event_graph; #[cfg(feature = "render_graph")] pub mod render_graph; pub mod schedule_graph; @@ -10,6 +11,48 @@ pub mod schedule_graph; #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)] struct ScheduleDebugGroup; +/// Formats the events into a dot graph. +#[track_caller] +pub fn events_graph_dot( + app: &mut App, + label: impl ScheduleLabel, + settings: &schedule_graph::Settings, +) -> String { + app.world + .resource_scope::(|world, mut schedules| { + let ignored_ambiguities = schedules.ignored_scheduling_ambiguities.clone(); + + let schedule = schedules + .get_mut(label) + .ok_or_else(|| "schedule doesn't exist".to_string()) + .unwrap(); + schedule.graph_mut().initialize(world); + let _ = schedule.graph_mut().build_schedule( + world.components(), + ScheduleDebugGroup.intern(), + &ignored_ambiguities, + ); + + event_graph::events_graph_dot(schedule, world, settings) + }) +} + +/// Prints the schedule with default settings. +pub fn print_events_graph(app: &mut App, schedule_label: impl ScheduleLabel) { + let dot = events_graph_dot( + app, + schedule_label, + &schedule_graph::Settings { + style: schedule_graph::settings::Style { + color_background: "white".to_string(), + ..schedule_graph::settings::Style::default() + }, + ..schedule_graph::Settings::default() + }, + ); + println!("{dot}"); +} + /// Formats the schedule into a dot graph. #[track_caller] pub fn schedule_graph_dot( From 99c6e8cdaacdbd98be7cbfc8665364de33045aa1 Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Sat, 23 Mar 2024 21:37:35 +0100 Subject: [PATCH 02/11] use pretty_type_name --- src/event_graph/mod.rs | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/event_graph/mod.rs b/src/event_graph/mod.rs index f668ec7..fa482bb 100644 --- a/src/event_graph/mod.rs +++ b/src/event_graph/mod.rs @@ -103,10 +103,7 @@ pub fn events_graph_dot( &node_string(s), &[ ("color", color), - ( - "label", - name.split('<').next().unwrap().split("::").last().unwrap(), - ), + ("label", &pretty_type_name::pretty_type_name_str(name)), ("tooltip", name), ("shape", "box"), ], @@ -117,18 +114,14 @@ pub fn events_graph_dot( let readers = event_readers.entry(event).or_default(); let writers = event_writers.entry(event).or_default(); - let name = event - .split_once('<') - .unwrap() - .1 /* .split("::").last() - .unwrap()*/; + let name = event.split_once('<').unwrap().1; let name = &name[0..name.len() - 1]; dot.add_node( event, &[ ("color", "green"), - ("label", name), - ("tooltip", "nice event"), + ("label", &pretty_type_name::pretty_type_name_str(name)), + ("tooltip", name), ("shape", "ellipse"), ], ); From 2807e280523f82f621ac0135fad0fc2d5906f101 Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Sat, 23 Mar 2024 21:51:52 +0100 Subject: [PATCH 03/11] use componentId to track events --- src/event_graph/mod.rs | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/event_graph/mod.rs b/src/event_graph/mod.rs index fa482bb..3ee4eb8 100644 --- a/src/event_graph/mod.rs +++ b/src/event_graph/mod.rs @@ -1,4 +1,5 @@ use bevy_ecs::{ + component::ComponentId, schedule::{NodeId, Schedule}, world::World, }; @@ -14,23 +15,17 @@ pub fn events_graph_dot( ) -> String { let graph = schedule.graph(); - /* - let hierarchy = graph.hierarchy().graph(); - let dependency = graph.dependency().graph(); - */ - let mut events_tracked = HashSet::new(); - let mut event_readers = HashMap::<&str, Vec>::new(); - let mut event_writers = HashMap::<&str, Vec>::new(); + let mut event_readers = HashMap::>::new(); + let mut event_writers = HashMap::>::new(); for (system_id, system, _condition) in graph.systems() { let accesses = system.component_access(); for access in accesses.reads() { let component = world.components().get_info(access).unwrap(); let name = component.name(); if name.starts_with("bevy_ecs::event::Events") { - // TODO: avoid relying on name, use TypeId ? - events_tracked.insert(name); - match event_readers.entry(name) { + events_tracked.insert(access); + match event_readers.entry(access) { bevy_utils::hashbrown::hash_map::Entry::Occupied(mut entry) => { entry.get_mut().push(system_id) } @@ -45,9 +40,9 @@ pub fn events_graph_dot( let component = world.components().get_info(access).unwrap(); let name = component.name(); if name.starts_with("bevy_ecs::event::Events") { - events_tracked.insert(name); + events_tracked.insert(access); - match event_writers.entry(name) { + match event_writers.entry(access) { bevy_utils::hashbrown::hash_map::Entry::Occupied(mut entry) => { entry.get_mut().push(system_id) } @@ -114,10 +109,15 @@ pub fn events_graph_dot( let readers = event_readers.entry(event).or_default(); let writers = event_writers.entry(event).or_default(); - let name = event.split_once('<').unwrap().1; + let component = world.components().get_info(event).unwrap(); + + // Relevant name is only what's inside "bevy::ecs::Events<(...)>" + let name = component.name(); + let name = name.split_once('<').unwrap().1; let name = &name[0..name.len() - 1]; + let event_id = event.index().to_string(); dot.add_node( - event, + &event_id, &[ ("color", "green"), ("label", &pretty_type_name::pretty_type_name_str(name)), @@ -126,10 +126,10 @@ pub fn events_graph_dot( ], ); for writer in writers { - dot.add_edge(&node_string(writer), event, &[]); + dot.add_edge(&node_string(writer), &event_id, &[]); } for reader in readers { - dot.add_edge(event, &node_string(reader), &[]); + dot.add_edge(&event_id, &node_string(reader), &[]); } } dot.finish().to_string() From 64cdbce60dd053ce09f85f2ddd2d6546964dcf1c Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Sat, 23 Mar 2024 21:52:13 +0100 Subject: [PATCH 04/11] removed todo --- src/event_graph/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/event_graph/mod.rs b/src/event_graph/mod.rs index 3ee4eb8..1234907 100644 --- a/src/event_graph/mod.rs +++ b/src/event_graph/mod.rs @@ -36,7 +36,6 @@ pub fn events_graph_dot( } } for access in accesses.writes() { - // TODO: avoid copying code? let component = world.components().get_info(access).unwrap(); let name = component.name(); if name.starts_with("bevy_ecs::event::Events") { From 85311d9b2e8dbd930f7aee40107765fc428e43cc Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Sat, 23 Mar 2024 22:45:40 +0100 Subject: [PATCH 05/11] prefix ids depending on their type --- src/event_graph/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/event_graph/mod.rs b/src/event_graph/mod.rs index 1234907..1b34738 100644 --- a/src/event_graph/mod.rs +++ b/src/event_graph/mod.rs @@ -114,7 +114,7 @@ pub fn events_graph_dot( let name = component.name(); let name = name.split_once('<').unwrap().1; let name = &name[0..name.len() - 1]; - let event_id = event.index().to_string(); + let event_id = format!("event_{0}", event.index()); dot.add_node( &event_id, &[ @@ -142,5 +142,5 @@ fn node_index(node_id: &NodeId) -> usize { } /// Internal but we use that as identifiers fn node_string(node_id: &NodeId) -> String { - node_index(node_id).to_string() + format!("system_{0}", node_index(node_id)) } From 5615dadaca6362fc650890be0109d3f37519964f Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Sun, 24 Mar 2024 21:17:40 +0100 Subject: [PATCH 06/11] ability to display multiple schedules at once --- examples/print_events_graph.rs | 10 ++- src/event_graph/mod.rs | 115 +++++++++++++++++++++------------ src/lib.rs | 38 ++++++----- 3 files changed, 105 insertions(+), 58 deletions(-) diff --git a/examples/print_events_graph.rs b/examples/print_events_graph.rs index d8d21be..03d99d0 100644 --- a/examples/print_events_graph.rs +++ b/examples/print_events_graph.rs @@ -4,5 +4,13 @@ use bevy::prelude::*; fn main() { let mut app = App::new(); app.add_plugins(DefaultPlugins.build().disable::()); - bevy_mod_debugdump::print_events_graph(&mut app, PostUpdate); + bevy_mod_debugdump::print_events_graph( + &mut app, + vec![ + Box::new(First), + Box::new(PreUpdate), + Box::new(Update), + Box::new(PostUpdate), + ], + ); } diff --git a/src/event_graph/mod.rs b/src/event_graph/mod.rs index 1b34738..f83b54b 100644 --- a/src/event_graph/mod.rs +++ b/src/event_graph/mod.rs @@ -1,24 +1,32 @@ use bevy_ecs::{ component::ComponentId, - schedule::{NodeId, Schedule}, + schedule::{NodeId, Schedule, ScheduleLabel, Schedules}, world::World, }; use bevy_utils::hashbrown::{HashMap, HashSet}; use crate::dot::DotGraph; +pub struct EventGraphContext { + events_tracked: HashSet, + event_readers: HashMap>, + event_writers: HashMap>, + schedule: Box, +} + /// Formats the events into a dot graph. -pub fn events_graph_dot( - schedule: &Schedule, - world: &World, - settings: &crate::schedule_graph::Settings, -) -> String { +pub fn events_graph_dot(schedule: &Schedule, world: &World) -> EventGraphContext { let graph = schedule.graph(); let mut events_tracked = HashSet::new(); let mut event_readers = HashMap::>::new(); let mut event_writers = HashMap::>::new(); for (system_id, system, _condition) in graph.systems() { + // TODO: make this configurable + let system_name = graph.system_at(system_id).name(); + if system_name.starts_with("bevy_ecs::event::event_update_system<") { + continue; + } let accesses = system.component_access(); for access in accesses.reads() { let component = world.components().get_info(access).unwrap(); @@ -40,7 +48,6 @@ pub fn events_graph_dot( let name = component.name(); if name.starts_with("bevy_ecs::event::Events") { events_tracked.insert(access); - match event_writers.entry(access) { bevy_utils::hashbrown::hash_map::Entry::Occupied(mut entry) => { entry.get_mut().push(system_id) @@ -52,27 +59,35 @@ pub fn events_graph_dot( } } } - let mut dot = DotGraph::new( - "", - "digraph", - &[ - ("compound", "true"), // enable ltail/lhead - ("splines", settings.style.edge_style.as_dot()), - ("rankdir", settings.style.schedule_rankdir.as_dot()), - ("bgcolor", &settings.style.color_background), - ("fontname", &settings.style.fontname), - ("nodesep", "0.15"), - ], - ) - .edge_attributes(&[("penwidth", &format!("{}", settings.style.penwidth_edge))]) - .node_attributes(&[("shape", "box"), ("style", "filled")]); + EventGraphContext { + events_tracked, + event_readers, + event_writers, + schedule: Box::new(schedule.label()), + } +} - let all_writers = event_writers +pub fn print_only_context( + schedules: &bevy_ecs::schedule::Schedules, + dot: &mut DotGraph, + ctx: &EventGraphContext, + world: &World, + _settings: &crate::schedule_graph::Settings, +) { + let schedule = schedules + .iter() + .find(|s| (*ctx.schedule).as_dyn_eq().dyn_eq(s.0.as_dyn_eq())) + .unwrap() + .1; + let graph = schedule.graph(); + let all_writers = ctx + .event_writers .values() .flatten() .copied() .collect::>(); - let all_readers = event_readers + let all_readers = ctx + .event_readers .values() .flatten() .copied() @@ -94,7 +109,7 @@ pub fn events_graph_dot( _ => panic!("Unexpected event handled."), }; dot.add_node( - &node_string(s), + name, &[ ("color", color), ("label", &pretty_type_name::pretty_type_name_str(name)), @@ -104,15 +119,15 @@ pub fn events_graph_dot( ); } - for event in events_tracked { - let readers = event_readers.entry(event).or_default(); - let writers = event_writers.entry(event).or_default(); + for event in ctx.events_tracked.iter() { + let readers = ctx.event_readers.get(event).cloned().unwrap_or_default(); + let writers = ctx.event_writers.get(event).cloned().unwrap_or_default(); - let component = world.components().get_info(event).unwrap(); + let component = world.components().get_info(*event).unwrap(); // Relevant name is only what's inside "bevy::ecs::Events<(...)>" - let name = component.name(); - let name = name.split_once('<').unwrap().1; + let full_name = component.name(); + let name = full_name.split_once('<').unwrap().1; let name = &name[0..name.len() - 1]; let event_id = format!("event_{0}", event.index()); dot.add_node( @@ -125,22 +140,40 @@ pub fn events_graph_dot( ], ); for writer in writers { - dot.add_edge(&node_string(writer), &event_id, &[]); + // We have to use full names, because nodeId is schedule specific, and I want to support multiple schedules displayed + let system_name = graph.get_system_at(writer).unwrap().name(); + dot.add_edge(&system_name, &event_id, &[]); } for reader in readers { - dot.add_edge(&event_id, &node_string(reader), &[]); + let system_name = graph.get_system_at(reader).unwrap().name(); + dot.add_edge(&event_id, &system_name, &[]); } } - dot.finish().to_string() } -/// Internal but we use that as identifiers -fn node_index(node_id: &NodeId) -> usize { - match node_id { - NodeId::System(index) | NodeId::Set(index) => *index, +pub fn print_context( + schedules: &Schedules, + ctxs: &Vec, + world: &World, + settings: &crate::schedule_graph::Settings, +) -> String { + let mut dot = DotGraph::new( + "", + "digraph", + &[ + ("compound", "true"), // enable ltail/lhead + ("splines", settings.style.edge_style.as_dot()), + ("rankdir", settings.style.schedule_rankdir.as_dot()), + ("bgcolor", &settings.style.color_background), + ("fontname", &settings.style.fontname), + ("nodesep", "0.15"), + ], + ) + .edge_attributes(&[("penwidth", &format!("{}", settings.style.penwidth_edge))]) + .node_attributes(&[("shape", "box"), ("style", "filled")]); + + for ctx in ctxs { + print_only_context(schedules, &mut dot, ctx, world, settings); } -} -/// Internal but we use that as identifiers -fn node_string(node_id: &NodeId) -> String { - format!("system_{0}", node_index(node_id)) + dot.finish().to_string() } diff --git a/src/lib.rs b/src/lib.rs index 3c8aa95..ce50d3d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,33 +15,39 @@ struct ScheduleDebugGroup; #[track_caller] pub fn events_graph_dot( app: &mut App, - label: impl ScheduleLabel, + labels: Vec>, settings: &schedule_graph::Settings, ) -> String { app.world .resource_scope::(|world, mut schedules| { let ignored_ambiguities = schedules.ignored_scheduling_ambiguities.clone(); + let mut contexts: Vec = Vec::new(); + for l in labels.iter() { + let Some((_, schedule)) = schedules + .iter_mut() + .find(|s| (**l).as_dyn_eq().dyn_eq(s.0.as_dyn_eq())) + else { + continue; + }; + schedule.graph_mut().initialize(world); - let schedule = schedules - .get_mut(label) - .ok_or_else(|| "schedule doesn't exist".to_string()) - .unwrap(); - schedule.graph_mut().initialize(world); - let _ = schedule.graph_mut().build_schedule( - world.components(), - ScheduleDebugGroup.intern(), - &ignored_ambiguities, - ); - - event_graph::events_graph_dot(schedule, world, settings) + let _ = schedule.graph_mut().build_schedule( + world.components(), + ScheduleDebugGroup.intern(), + &ignored_ambiguities, + ); + let context = event_graph::events_graph_dot(schedule, world); + contexts.push(context); + } + event_graph::print_context(schedules.as_ref(), &contexts, world, settings) }) } /// Prints the schedule with default settings. -pub fn print_events_graph(app: &mut App, schedule_label: impl ScheduleLabel) { +pub fn print_events_graph(app: &mut App, schedule_labels: Vec>) { let dot = events_graph_dot( app, - schedule_label, + schedule_labels, &schedule_graph::Settings { style: schedule_graph::settings::Style { color_background: "white".to_string(), @@ -107,7 +113,7 @@ pub fn render_graph_dot(app: &App, settings: &render_graph::Settings) -> String .unwrap_or_else(|_| panic!("no render app")); let render_graph = render_app.world.get_resource::().unwrap(); - render_graph::render_graph_dot(render_graph, &settings) + render_graph::render_graph_dot(render_graph, settings) } /// Prints the current render graph using [`render_graph_dot`](render_graph::render_graph_dot). From d6ab989f47d2e61f294b9042a76f8d49041ffbf4 Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Thu, 28 Mar 2024 20:26:17 +0100 Subject: [PATCH 07/11] wip settings taken into account for events --- src/event_graph/mod.rs | 61 ++++++-- src/event_graph/settings.rs | 248 ++++++++++++++++++++++++++++++++ src/event_graph/system_style.rs | 86 +++++++++++ src/lib.rs | 16 +-- 4 files changed, 387 insertions(+), 24 deletions(-) create mode 100644 src/event_graph/settings.rs create mode 100644 src/event_graph/system_style.rs diff --git a/src/event_graph/mod.rs b/src/event_graph/mod.rs index f83b54b..8b8bad5 100644 --- a/src/event_graph/mod.rs +++ b/src/event_graph/mod.rs @@ -1,3 +1,6 @@ +pub mod settings; +mod system_style; + use bevy_ecs::{ component::ComponentId, schedule::{NodeId, Schedule, ScheduleLabel, Schedules}, @@ -6,6 +9,7 @@ use bevy_ecs::{ use bevy_utils::hashbrown::{HashMap, HashSet}; use crate::dot::DotGraph; +pub use settings::Settings; pub struct EventGraphContext { events_tracked: HashSet, @@ -15,17 +19,21 @@ pub struct EventGraphContext { } /// Formats the events into a dot graph. -pub fn events_graph_dot(schedule: &Schedule, world: &World) -> EventGraphContext { +pub fn events_graph_dot( + schedule: &Schedule, + world: &World, + settings: &Settings, +) -> EventGraphContext { let graph = schedule.graph(); let mut events_tracked = HashSet::new(); let mut event_readers = HashMap::>::new(); let mut event_writers = HashMap::>::new(); for (system_id, system, _condition) in graph.systems() { - // TODO: make this configurable - let system_name = graph.system_at(system_id).name(); - if system_name.starts_with("bevy_ecs::event::event_update_system<") { - continue; + if let Some(include_system) = &settings.include_system { + if !(include_system)(system) { + continue; + } } let accesses = system.component_access(); for access in accesses.reads() { @@ -72,7 +80,7 @@ pub fn print_only_context( dot: &mut DotGraph, ctx: &EventGraphContext, world: &World, - _settings: &crate::schedule_graph::Settings, + settings: &Settings, ) { let schedule = schedules .iter() @@ -112,7 +120,7 @@ pub fn print_only_context( name, &[ ("color", color), - ("label", &pretty_type_name::pretty_type_name_str(name)), + ("label", &display_name(name, settings)), ("tooltip", name), ("shape", "box"), ], @@ -134,7 +142,7 @@ pub fn print_only_context( &event_id, &[ ("color", "green"), - ("label", &pretty_type_name::pretty_type_name_str(name)), + ("label", &display_name(name, settings)), ("tooltip", name), ("shape", "ellipse"), ], @@ -142,20 +150,51 @@ pub fn print_only_context( for writer in writers { // We have to use full names, because nodeId is schedule specific, and I want to support multiple schedules displayed let system_name = graph.get_system_at(writer).unwrap().name(); - dot.add_edge(&system_name, &event_id, &[]); + dot.add_edge( + &system_name, + &event_id, + &[ + // TODO: customize edges, colors in a same fashion as schedules + /* + ("lhead", &self.lref(to)), + ("ltail", &self.lref(from)), + ("tooltip", &self.edge_tooltip(from, to)), + */ + ("color", &settings.style.color_edge[0]), + ], + ); } for reader in readers { let system_name = graph.get_system_at(reader).unwrap().name(); - dot.add_edge(&event_id, &system_name, &[]); + dot.add_edge( + &event_id, + &system_name, + &[ + /* + ("lhead", &self.lref(to)), + ("ltail", &self.lref(from)), + ("tooltip", &self.edge_tooltip(from, to)), + */ + ("color", &settings.style.color_edge[0]), + ], + ); } } } +fn display_name(name: &str, settings: &Settings) -> String { + if settings.prettify_system_names { + pretty_type_name::pretty_type_name_str(name) + } else { + name.to_string() + } +} + pub fn print_context( schedules: &Schedules, ctxs: &Vec, world: &World, - settings: &crate::schedule_graph::Settings, + settings: &Settings, ) -> String { let mut dot = DotGraph::new( "", diff --git a/src/event_graph/settings.rs b/src/event_graph/settings.rs new file mode 100644 index 0000000..131b0a6 --- /dev/null +++ b/src/event_graph/settings.rs @@ -0,0 +1,248 @@ +use bevy_ecs::{component::ComponentId, system::System, world::World}; +use bevy_render::color::Color; + +use super::system_style::{color_to_hex, system_to_style, SystemStyle}; + +#[derive(Default, Clone, Copy)] +pub enum RankDir { + TopDown, + #[default] + LeftRight, +} +impl RankDir { + pub(crate) fn as_dot(&self) -> &'static str { + match self { + RankDir::TopDown => "TD", + RankDir::LeftRight => "LR", + } + } +} + +#[derive(Default, Clone, Copy)] +pub enum EdgeStyle { + None, + Line, + Polyline, + Curved, + Ortho, + #[default] + Spline, +} +impl EdgeStyle { + pub fn as_dot(&self) -> &'static str { + match self { + EdgeStyle::None => "none", + EdgeStyle::Line => "line", + EdgeStyle::Polyline => "polyline", + EdgeStyle::Curved => "curved", + EdgeStyle::Ortho => "ortho", + EdgeStyle::Spline => "spline", + } + } +} + +#[derive(Clone)] +pub struct Style { + pub schedule_rankdir: RankDir, + pub edge_style: EdgeStyle, + + pub fontname: String, + + pub color_background: String, + pub color_set: String, + pub color_set_label: String, + pub color_set_border: String, + pub color_edge: Vec, + pub multiple_set_edge_color: String, + + pub penwidth_edge: f32, +} +// colors are from https://iamkate.com/data/12-bit-rainbow/, without the #cc6666 +impl Style { + pub fn light() -> Style { + Style { + schedule_rankdir: RankDir::default(), + edge_style: EdgeStyle::default(), + fontname: "Helvetica".into(), + color_background: "white".into(), + color_set: "#00000008".into(), + color_set_border: "#00000040".into(), + color_set_label: "#000000".into(), + color_edge: vec![ + "#eede00".into(), + "#881877".into(), + "#00b0cc".into(), + "#aa3a55".into(), + "#44d488".into(), + "#0090cc".into(), + "#ee9e44".into(), + "#663699".into(), + "#3363bb".into(), + "#22c2bb".into(), + "#99d955".into(), + ], + multiple_set_edge_color: "blue".into(), + penwidth_edge: 2.0, + } + } + + pub fn dark_discord() -> Style { + Style { + schedule_rankdir: RankDir::default(), + edge_style: EdgeStyle::default(), + fontname: "Helvetica".into(), + color_background: "#35393f".into(), + color_set: "#ffffff44".into(), + color_set_border: "#ffffff50".into(), + color_set_label: "#ffffff".into(), + color_edge: vec![ + "#eede00".into(), + "#881877".into(), + "#00b0cc".into(), + "#aa3a55".into(), + "#44d488".into(), + "#0090cc".into(), + "#ee9e44".into(), + "#663699".into(), + "#3363bb".into(), + "#22c2bb".into(), + "#99d955".into(), + ], + multiple_set_edge_color: "blue".into(), + penwidth_edge: 2.0, + } + } + + pub fn dark_github() -> Style { + Style { + schedule_rankdir: RankDir::default(), + edge_style: EdgeStyle::default(), + fontname: "Helvetica".into(), + color_background: "#0d1117".into(), + color_set: "#ffffff44".into(), + color_set_border: "#ffffff50".into(), + color_set_label: "#ffffff".into(), + color_edge: vec![ + "#eede00".into(), + "#881877".into(), + "#00b0cc".into(), + "#aa3a55".into(), + "#44d488".into(), + "#0090cc".into(), + "#ee9e44".into(), + "#663699".into(), + "#3363bb".into(), + "#22c2bb".into(), + "#99d955".into(), + ], + multiple_set_edge_color: "blue".into(), + penwidth_edge: 2.0, + } + } +} +impl Default for Style { + fn default() -> Self { + Style::dark_github() + } +} + +pub struct NodeStyle { + pub bg_color: String, + pub text_color: String, + pub border_color: String, + pub border_width: String, +} + +// Function that maps `System` to `T` +type SystemMapperFn = Box) -> T>; + +pub struct Settings { + pub style: Style, + pub system_style: SystemMapperFn, + + /// When set to `Some`, will only include systems matching the predicate, and their ancestor sets + pub include_system: Option>, + + pub prettify_system_names: bool, +} + +impl Settings { + /// Set the `include_system` predicate to match only systems for which their names matches `filter` + pub fn filter_name(mut self, filter: impl Fn(&str) -> bool + 'static) -> Self { + self.include_system = Some(Box::new(move |system| { + let name = system.name(); + filter(&name) + })); + self + } + /// Set the `include_system` predicate to only match systems from the specified crate + pub fn filter_in_crate(mut self, crate_: &str) -> Self { + let crate_ = crate_.to_owned(); + self.include_system = Some(Box::new(move |system| { + let name = system.name(); + name.starts_with(&crate_) + })); + self + } + /// Set the `include_system` predicate to only match systems from the specified crates + pub fn filter_in_crates(mut self, crates: &[&str]) -> Self { + let crates: Vec<_> = crates.iter().map(|&s| s.to_owned()).collect(); + self.include_system = Some(Box::new(move |system| { + let name = system.name(); + crates.iter().any(|crate_| name.starts_with(crate_)) + })); + self + } + + pub fn get_system_style(&self, system: &dyn System) -> NodeStyle { + let style = (self.system_style)(system); + + // Check if bg is dark + let [h, s, l, _] = style.bg_color.as_hsla_f32(); + // TODO Fix following: https://ux.stackexchange.com/q/107318 + let is_dark = l < 0.6; + + // Calculate text color based on bg + let text_color = style.text_color.unwrap_or_else(|| { + if is_dark { + Color::hsl(h, s, 0.9) + } else { + Color::hsl(h, s, 0.1) + } + }); + + // Calculate border color based on bg + let border_color = style.border_color.unwrap_or_else(|| { + let offset = if is_dark { 0.2 } else { -0.2 }; + let border_l = (l + offset).clamp(0.0, 1.0); + + Color::hsl(h, s, border_l) + }); + + NodeStyle { + bg_color: color_to_hex(style.bg_color), + text_color: color_to_hex(text_color), + border_color: color_to_hex(border_color), + border_width: style.border_width.to_string(), + } + } +} + +impl Default for Settings { + fn default() -> Self { + Self { + style: Style::default(), + system_style: Box::new(system_to_style), + + include_system: Some(Box::new(exclude_bevy_event_update_system)), + + prettify_system_names: true, + } + } +} + +pub fn exclude_bevy_event_update_system(system: &dyn System) -> bool { + !system + .name() + .starts_with("bevy_ecs::event::event_update_system<") +} diff --git a/src/event_graph/system_style.rs b/src/event_graph/system_style.rs new file mode 100644 index 0000000..d817207 --- /dev/null +++ b/src/event_graph/system_style.rs @@ -0,0 +1,86 @@ +use once_cell::sync::Lazy; +use std::borrow::Cow; + +use bevy_ecs::system::System; +use bevy_render::color::Color; +use bevy_utils::HashMap; + +static CRATE_COLORS: Lazy> = Lazy::new(|| { + [ + // Beige/Red + ("bevy_transform", "FFE7B9"), + ("bevy_animation", "FFBDB9"), + // Greys + ("bevy_asset", "D1CBC5"), + ("bevy_scene", "BACFCB"), + ("bevy_time", "C7DDBD"), + // Greens + ("bevy_core", "3E583C"), + ("bevy_app", "639D18"), + ("bevy_ecs", "B0D34A"), + ("bevy_hierarchy", "E4FBA3"), + // Turquesa + ("bevy_audio", "98F1D1"), + // Purples/Pinks + ("bevy_winit", "664F72"), + ("bevy_a11y", "9163A6"), + ("bevy_window", "BB85D4"), + ("bevy_text", "E9BBFF"), + ("bevy_gilrs", "973977"), + ("bevy_input", "D36AAF"), + ("bevy_ui", "FFB1E5"), + // Blues + ("bevy_render", "70B9FC"), + ("bevy_pbr", "ABD5FC"), + ] + .into_iter() + .collect() +}); + +pub struct SystemStyle { + pub bg_color: Color, + pub text_color: Option, + pub border_color: Option, + pub border_width: f32, +} + +pub fn color_to_hex(color: Color) -> String { + format!( + "#{:0>2x}{:0>2x}{:0>2x}", + (color.r() * 255.0) as u8, + (color.g() * 255.0) as u8, + (color.b() * 255.0) as u8, + ) +} + +pub fn system_to_style(system: &dyn System) -> SystemStyle { + let name = system.name(); + let pretty_name: Cow = pretty_type_name::pretty_type_name_str(&name).into(); + let is_apply_system_buffers = pretty_name == "apply_system_buffers"; + let name_without_event = name + .trim_start_matches("bevy_ecs::event::Events<") + .trim_end_matches(">::update_system"); + let crate_name = name_without_event.split("::").next(); + + if is_apply_system_buffers { + SystemStyle { + bg_color: Color::hex("E70000").unwrap(), + text_color: Some(Color::hex("ffffff").unwrap()), + border_color: Some(Color::hex("5A0000").unwrap()), + border_width: 2.0, + } + } else { + let bg_color = crate_name + .and_then(|n| CRATE_COLORS.get(n)) + .map(Color::hex) + .unwrap_or(Color::hex("eff1f3")) + .unwrap(); + + SystemStyle { + bg_color, + text_color: None, + border_color: None, + border_width: 1.0, + } + } +} diff --git a/src/lib.rs b/src/lib.rs index ce50d3d..a43efd3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,7 +16,7 @@ struct ScheduleDebugGroup; pub fn events_graph_dot( app: &mut App, labels: Vec>, - settings: &schedule_graph::Settings, + settings: &event_graph::Settings, ) -> String { app.world .resource_scope::(|world, mut schedules| { @@ -36,7 +36,7 @@ pub fn events_graph_dot( ScheduleDebugGroup.intern(), &ignored_ambiguities, ); - let context = event_graph::events_graph_dot(schedule, world); + let context = event_graph::events_graph_dot(schedule, world, settings); contexts.push(context); } event_graph::print_context(schedules.as_ref(), &contexts, world, settings) @@ -45,17 +45,7 @@ pub fn events_graph_dot( /// Prints the schedule with default settings. pub fn print_events_graph(app: &mut App, schedule_labels: Vec>) { - let dot = events_graph_dot( - app, - schedule_labels, - &schedule_graph::Settings { - style: schedule_graph::settings::Style { - color_background: "white".to_string(), - ..schedule_graph::settings::Style::default() - }, - ..schedule_graph::Settings::default() - }, - ); + let dot = events_graph_dot(app, schedule_labels, &event_graph::Settings::default()); println!("{dot}"); } From 741049113b7561d53a077c2bb46333ff199c5057 Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Mon, 1 Apr 2024 15:57:50 +0200 Subject: [PATCH 08/11] similar style to schedule graph + more idiomatic schedule filtering through settings --- examples/print_events_graph.rs | 10 +-- src/event_graph/mod.rs | 119 +++++++++++++++++++++----------- src/event_graph/settings.rs | 65 ++++++++++++++++- src/event_graph/system_style.rs | 40 ++++++++++- src/lib.rs | 23 +++--- 5 files changed, 192 insertions(+), 65 deletions(-) diff --git a/examples/print_events_graph.rs b/examples/print_events_graph.rs index 03d99d0..d182827 100644 --- a/examples/print_events_graph.rs +++ b/examples/print_events_graph.rs @@ -4,13 +4,5 @@ use bevy::prelude::*; fn main() { let mut app = App::new(); app.add_plugins(DefaultPlugins.build().disable::()); - bevy_mod_debugdump::print_events_graph( - &mut app, - vec![ - Box::new(First), - Box::new(PreUpdate), - Box::new(Update), - Box::new(PostUpdate), - ], - ); + bevy_mod_debugdump::print_events_graph(&mut app); } diff --git a/src/event_graph/mod.rs b/src/event_graph/mod.rs index 8b8bad5..e12daea 100644 --- a/src/event_graph/mod.rs +++ b/src/event_graph/mod.rs @@ -1,6 +1,8 @@ pub mod settings; mod system_style; +use std::sync::atomic::AtomicUsize; + use bevy_ecs::{ component::ComponentId, schedule::{NodeId, Schedule, ScheduleLabel, Schedules}, @@ -11,19 +13,85 @@ use bevy_utils::hashbrown::{HashMap, HashSet}; use crate::dot::DotGraph; pub use settings::Settings; -pub struct EventGraphContext { +pub struct EventGraphContext<'a> { + settings: &'a Settings, + events_tracked: HashSet, event_readers: HashMap>, event_writers: HashMap>, schedule: Box, + + color_edge_idx: AtomicUsize, +} + +impl<'a> EventGraphContext<'a> { + fn next_edge_color(&self) -> &str { + use std::sync::atomic::Ordering; + let (Ok(idx) | Err(idx)) = + self.color_edge_idx + .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |a| { + Some((a + 1) % self.settings.style.color_edge.len()) + }); + + &self.settings.style.color_edge[idx] + } + + fn add_system( + &self, + dot: &mut DotGraph, + system: &NodeId, + graph: &bevy_ecs::schedule::ScheduleGraph, + ) { + let sys = &graph.get_system_at(*system).unwrap(); + let name = &sys.name(); + + let node_style = self.settings.get_system_style(*sys); + dot.add_node( + name, + &[ + ("label", &display_name(name, self.settings)), + ("tooltip", name), + ("shape", "box"), + ("fillcolor", &node_style.bg_color), + ("fontname", &self.settings.style.fontname), + ("fontcolor", &node_style.text_color), + ("color", &node_style.border_color), + ("penwidth", &node_style.border_width.to_string()), + ], + ); + } + + fn add_event(&self, dot: &mut DotGraph, event: &ComponentId, world: &World) -> String { + let component = world.components().get_info(*event).unwrap(); + // Relevant name is only what's inside "bevy::ecs::Events<(...)>" + let full_name = component.name(); + let name = full_name.split_once('<').unwrap().1; + let name = &name[0..name.len() - 1]; + let event_id = format!("event_{0}", event.index()); + let node_style = self.settings.get_event_style(component); + dot.add_node( + &event_id, + &[ + ("label", &display_name(name, self.settings)), + ("tooltip", name), + ("shape", "ellipse"), + ("fillcolor", &node_style.bg_color), + ("fontname", &self.settings.style.fontname), + ("fontcolor", &node_style.text_color), + ("color", &node_style.border_color), + ("penwidth", &node_style.border_width.to_string()), + ], + ); + event_id + } } /// Formats the events into a dot graph. -pub fn events_graph_dot( +pub fn events_graph_dot<'a>( schedule: &Schedule, world: &World, - settings: &Settings, -) -> EventGraphContext { + settings: &'a Settings, +) -> EventGraphContext<'a> { let graph = schedule.graph(); let mut events_tracked = HashSet::new(); @@ -68,10 +136,12 @@ pub fn events_graph_dot( } } EventGraphContext { + settings, events_tracked, event_readers, event_writers, schedule: Box::new(schedule.label()), + color_edge_idx: AtomicUsize::new(0), } } @@ -80,7 +150,6 @@ pub fn print_only_context( dot: &mut DotGraph, ctx: &EventGraphContext, world: &World, - settings: &Settings, ) { let schedule = schedules .iter() @@ -109,44 +178,15 @@ pub fn print_only_context( .collect::>(); for s in all_systems { - let name = &graph.get_system_at(*s).unwrap().name(); - let color = match (all_writers.contains(s), all_readers.contains(s)) { - (true, false) => "yellow", - (false, true) => "red", - (true, true) => "orange", - _ => panic!("Unexpected event handled."), - }; - dot.add_node( - name, - &[ - ("color", color), - ("label", &display_name(name, settings)), - ("tooltip", name), - ("shape", "box"), - ], - ); + ctx.add_system(dot, s, graph); } for event in ctx.events_tracked.iter() { let readers = ctx.event_readers.get(event).cloned().unwrap_or_default(); let writers = ctx.event_writers.get(event).cloned().unwrap_or_default(); - let component = world.components().get_info(*event).unwrap(); + let event_id = ctx.add_event(dot, event, world); - // Relevant name is only what's inside "bevy::ecs::Events<(...)>" - let full_name = component.name(); - let name = full_name.split_once('<').unwrap().1; - let name = &name[0..name.len() - 1]; - let event_id = format!("event_{0}", event.index()); - dot.add_node( - &event_id, - &[ - ("color", "green"), - ("label", &display_name(name, settings)), - ("tooltip", name), - ("shape", "ellipse"), - ], - ); for writer in writers { // We have to use full names, because nodeId is schedule specific, and I want to support multiple schedules displayed let system_name = graph.get_system_at(writer).unwrap().name(); @@ -160,12 +200,13 @@ pub fn print_only_context( ("ltail", &self.lref(from)), ("tooltip", &self.edge_tooltip(from, to)), */ - ("color", &settings.style.color_edge[0]), + ("color", ctx.next_edge_color()), ], ); } for reader in readers { let system_name = graph.get_system_at(reader).unwrap().name(); + dot.add_edge( &event_id, &system_name, @@ -175,7 +216,7 @@ pub fn print_only_context( ("ltail", &self.lref(from)), ("tooltip", &self.edge_tooltip(from, to)), */ - ("color", &settings.style.color_edge[0]), + ("color", ctx.next_edge_color()), ], ); } @@ -212,7 +253,7 @@ pub fn print_context( .node_attributes(&[("shape", "box"), ("style", "filled")]); for ctx in ctxs { - print_only_context(schedules, &mut dot, ctx, world, settings); + print_only_context(schedules, &mut dot, ctx, world); } dot.finish().to_string() } diff --git a/src/event_graph/settings.rs b/src/event_graph/settings.rs index 131b0a6..abbc0fb 100644 --- a/src/event_graph/settings.rs +++ b/src/event_graph/settings.rs @@ -1,7 +1,14 @@ -use bevy_ecs::{component::ComponentId, system::System, world::World}; +use bevy_app::{First, PostUpdate, PreUpdate, Update}; +use bevy_ecs::{ + component::ComponentInfo, + schedule::{Schedule, ScheduleLabel}, + system::System, +}; use bevy_render::color::Color; -use super::system_style::{color_to_hex, system_to_style, SystemStyle}; +use super::system_style::{ + color_to_hex, event_to_style, system_to_style, ComponentInfoStyle, SystemStyle, +}; #[derive(Default, Clone, Copy)] pub enum RankDir { @@ -156,12 +163,20 @@ pub struct NodeStyle { // Function that maps `System` to `T` type SystemMapperFn = Box) -> T>; +// Function that maps `ComponentInfo` to `T` +type ComponentInfoMapperFn = Box T>; + +// Function that maps `Schedule` to `T` +type ScheduleMapperFn = Box T>; + pub struct Settings { pub style: Style, pub system_style: SystemMapperFn, + pub event_style: ComponentInfoMapperFn, /// When set to `Some`, will only include systems matching the predicate, and their ancestor sets pub include_system: Option>, + pub include_schedule: Option>, pub prettify_system_names: bool, } @@ -226,6 +241,39 @@ impl Settings { border_width: style.border_width.to_string(), } } + + pub fn get_event_style(&self, system: &ComponentInfo) -> NodeStyle { + let style = (self.event_style)(system); + + // Check if bg is dark + let [h, s, l, _] = style.bg_color.as_hsla_f32(); + // TODO Fix following: https://ux.stackexchange.com/q/107318 + let is_dark = l < 0.6; + + // Calculate text color based on bg + let text_color = style.text_color.unwrap_or_else(|| { + if is_dark { + Color::hsl(h, s, 0.9) + } else { + Color::hsl(h, s, 0.1) + } + }); + + // Calculate border color based on bg + let border_color = style.border_color.unwrap_or_else(|| { + let offset = if is_dark { 0.2 } else { -0.2 }; + let border_l = (l + offset).clamp(0.0, 1.0); + + Color::hsl(h, s, border_l) + }); + + NodeStyle { + bg_color: color_to_hex(style.bg_color), + text_color: color_to_hex(text_color), + border_color: color_to_hex(border_color), + border_width: style.border_width.to_string(), + } + } } impl Default for Settings { @@ -233,8 +281,10 @@ impl Default for Settings { Self { style: Style::default(), system_style: Box::new(system_to_style), + event_style: Box::new(event_to_style), include_system: Some(Box::new(exclude_bevy_event_update_system)), + include_schedule: Some(Box::new(base_schedule_update)), prettify_system_names: true, } @@ -246,3 +296,14 @@ pub fn exclude_bevy_event_update_system(system: &dyn System) .name() .starts_with("bevy_ecs::event::event_update_system<") } +pub fn base_schedule_update(schedule: &Schedule) -> bool { + let labels: Vec> = vec![ + Box::new(First), + Box::new(PreUpdate), + Box::new(Update), + Box::new(PostUpdate), + ]; + labels + .iter() + .any(|s| (*schedule.label().0).as_dyn_eq().dyn_eq((**s).as_dyn_eq())) +} diff --git a/src/event_graph/system_style.rs b/src/event_graph/system_style.rs index d817207..ad60051 100644 --- a/src/event_graph/system_style.rs +++ b/src/event_graph/system_style.rs @@ -1,7 +1,7 @@ use once_cell::sync::Lazy; use std::borrow::Cow; -use bevy_ecs::system::System; +use bevy_ecs::{component::ComponentInfo, system::System}; use bevy_render::color::Color; use bevy_utils::HashMap; @@ -43,6 +43,12 @@ pub struct SystemStyle { pub border_color: Option, pub border_width: f32, } +pub struct ComponentInfoStyle { + pub bg_color: Color, + pub text_color: Option, + pub border_color: Option, + pub border_width: f32, +} pub fn color_to_hex(color: Color) -> String { format!( @@ -84,3 +90,35 @@ pub fn system_to_style(system: &dyn System) -> SystemStyle { } } } + +pub fn event_to_style(system: &ComponentInfo) -> ComponentInfoStyle { + let name = system.name(); + let pretty_name: Cow = pretty_type_name::pretty_type_name_str(name).into(); + let is_apply_system_buffers = pretty_name == "apply_system_buffers"; + let name_without_event = name + .trim_start_matches("bevy_ecs::event::Events<") + .trim_end_matches(">::update_system"); + let crate_name = name_without_event.split("::").next(); + + if is_apply_system_buffers { + ComponentInfoStyle { + bg_color: Color::hex("E70000").unwrap(), + text_color: Some(Color::hex("ffffff").unwrap()), + border_color: Some(Color::hex("5A0000").unwrap()), + border_width: 2.0, + } + } else { + let bg_color = crate_name + .and_then(|n| CRATE_COLORS.get(n)) + .map(Color::hex) + .unwrap_or(Color::hex("eff1f3")) + .unwrap(); + + ComponentInfoStyle { + bg_color, + text_color: None, + border_color: None, + border_width: 1.0, + } + } +} diff --git a/src/lib.rs b/src/lib.rs index a43efd3..2008790 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,22 +13,17 @@ struct ScheduleDebugGroup; /// Formats the events into a dot graph. #[track_caller] -pub fn events_graph_dot( - app: &mut App, - labels: Vec>, - settings: &event_graph::Settings, -) -> String { +pub fn events_graph_dot(app: &mut App, settings: &event_graph::Settings) -> String { app.world .resource_scope::(|world, mut schedules| { let ignored_ambiguities = schedules.ignored_scheduling_ambiguities.clone(); let mut contexts: Vec = Vec::new(); - for l in labels.iter() { - let Some((_, schedule)) = schedules - .iter_mut() - .find(|s| (**l).as_dyn_eq().dyn_eq(s.0.as_dyn_eq())) - else { - continue; - }; + for (_l, schedule) in schedules.iter_mut() { + if let Some(include_schedule) = &settings.include_schedule { + if !(include_schedule)(schedule) { + continue; + } + } schedule.graph_mut().initialize(world); let _ = schedule.graph_mut().build_schedule( @@ -44,8 +39,8 @@ pub fn events_graph_dot( } /// Prints the schedule with default settings. -pub fn print_events_graph(app: &mut App, schedule_labels: Vec>) { - let dot = events_graph_dot(app, schedule_labels, &event_graph::Settings::default()); +pub fn print_events_graph(app: &mut App) { + let dot = events_graph_dot(app, &event_graph::Settings::default()); println!("{dot}"); } From d5c2685455bb58470bae11fbf32c3881a82a5217 Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Thu, 4 Apr 2024 14:20:41 +0200 Subject: [PATCH 09/11] subgraph for schedules --- src/event_graph/mod.rs | 16 +++++++++++++++- src/event_graph/settings.rs | 24 ++++++++++++------------ 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/event_graph/mod.rs b/src/event_graph/mod.rs index e12daea..046c67d 100644 --- a/src/event_graph/mod.rs +++ b/src/event_graph/mod.rs @@ -253,7 +253,21 @@ pub fn print_context( .node_attributes(&[("shape", "box"), ("style", "filled")]); for ctx in ctxs { - print_only_context(schedules, &mut dot, ctx, world); + let schedule_name = format!("{:?}", ctx.schedule); + let mut schedule_graph = DotGraph::subgraph( + &schedule_name, + &[ + ("style", "rounded,filled"), + ("label", &schedule_name), + ("tooltip", &schedule_name), + ("fillcolor", &settings.style.color_schedule), + ("fontcolor", &settings.style.color_schedule_label), + ("color", &settings.style.color_schedule_border), + ("penwidth", "2"), + ], + ); + print_only_context(schedules, &mut schedule_graph, ctx, world); + dot.add_sub_graph(schedule_graph); } dot.finish().to_string() } diff --git a/src/event_graph/settings.rs b/src/event_graph/settings.rs index abbc0fb..f73e1af 100644 --- a/src/event_graph/settings.rs +++ b/src/event_graph/settings.rs @@ -56,9 +56,9 @@ pub struct Style { pub fontname: String, pub color_background: String, - pub color_set: String, - pub color_set_label: String, - pub color_set_border: String, + pub color_schedule: String, + pub color_schedule_label: String, + pub color_schedule_border: String, pub color_edge: Vec, pub multiple_set_edge_color: String, @@ -72,9 +72,9 @@ impl Style { edge_style: EdgeStyle::default(), fontname: "Helvetica".into(), color_background: "white".into(), - color_set: "#00000008".into(), - color_set_border: "#00000040".into(), - color_set_label: "#000000".into(), + color_schedule: "#00000008".into(), + color_schedule_border: "#00000040".into(), + color_schedule_label: "#000000".into(), color_edge: vec![ "#eede00".into(), "#881877".into(), @@ -99,9 +99,9 @@ impl Style { edge_style: EdgeStyle::default(), fontname: "Helvetica".into(), color_background: "#35393f".into(), - color_set: "#ffffff44".into(), - color_set_border: "#ffffff50".into(), - color_set_label: "#ffffff".into(), + color_schedule: "#ffffff44".into(), + color_schedule_border: "#ffffff50".into(), + color_schedule_label: "#ffffff".into(), color_edge: vec![ "#eede00".into(), "#881877".into(), @@ -126,9 +126,9 @@ impl Style { edge_style: EdgeStyle::default(), fontname: "Helvetica".into(), color_background: "#0d1117".into(), - color_set: "#ffffff44".into(), - color_set_border: "#ffffff50".into(), - color_set_label: "#ffffff".into(), + color_schedule: "#ffffff44".into(), + color_schedule_border: "#ffffff50".into(), + color_schedule_label: "#ffffff".into(), color_edge: vec![ "#eede00".into(), "#881877".into(), From fe087b41c2012d29e7181df696474dce1b605f83 Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Thu, 4 Apr 2024 14:26:01 +0200 Subject: [PATCH 10/11] removed unused setting --- src/event_graph/settings.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/event_graph/settings.rs b/src/event_graph/settings.rs index f73e1af..899a8ad 100644 --- a/src/event_graph/settings.rs +++ b/src/event_graph/settings.rs @@ -60,7 +60,6 @@ pub struct Style { pub color_schedule_label: String, pub color_schedule_border: String, pub color_edge: Vec, - pub multiple_set_edge_color: String, pub penwidth_edge: f32, } @@ -88,7 +87,6 @@ impl Style { "#22c2bb".into(), "#99d955".into(), ], - multiple_set_edge_color: "blue".into(), penwidth_edge: 2.0, } } @@ -115,7 +113,6 @@ impl Style { "#22c2bb".into(), "#99d955".into(), ], - multiple_set_edge_color: "blue".into(), penwidth_edge: 2.0, } } @@ -142,7 +139,6 @@ impl Style { "#22c2bb".into(), "#99d955".into(), ], - multiple_set_edge_color: "blue".into(), penwidth_edge: 2.0, } } From 7532da7e05ebfcf9e813e1acbb6d96a1568a6be3 Mon Sep 17 00:00:00 2001 From: Thierry Berger Date: Tue, 14 May 2024 11:30:12 +0200 Subject: [PATCH 11/11] render events found in different schedules outside their group --- src/event_graph/mod.rs | 56 ++++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/src/event_graph/mod.rs b/src/event_graph/mod.rs index 046c67d..61c08f1 100644 --- a/src/event_graph/mod.rs +++ b/src/event_graph/mod.rs @@ -147,8 +147,10 @@ pub fn events_graph_dot<'a>( pub fn print_only_context( schedules: &bevy_ecs::schedule::Schedules, - dot: &mut DotGraph, + events_dotgraph: &mut DotGraph, + schedules_dotgraph: &mut DotGraph, ctx: &EventGraphContext, + other_ctxs: &[EventGraphContext], world: &World, ) { let schedule = schedules @@ -157,40 +159,42 @@ pub fn print_only_context( .unwrap() .1; let graph = schedule.graph(); - let all_writers = ctx - .event_writers - .values() - .flatten() - .copied() - .collect::>(); - let all_readers = ctx - .event_readers - .values() - .flatten() - .copied() - .collect::>(); + { + let all_writers = ctx.event_writers.values().flatten(); + let all_readers = ctx.event_readers.values().flatten(); - let all_systems = all_writers - .iter() - .chain(all_readers.iter()) - .collect::>() - .into_iter() - .collect::>(); + // Deduplicate systems + let all_systems = all_writers + .chain(all_readers) + .collect::>() + .into_iter() + .collect::>(); - for s in all_systems { - ctx.add_system(dot, s, graph); + for s in all_systems { + ctx.add_system(schedules_dotgraph, s, graph); + } } - for event in ctx.events_tracked.iter() { let readers = ctx.event_readers.get(event).cloned().unwrap_or_default(); let writers = ctx.event_writers.get(event).cloned().unwrap_or_default(); - let event_id = ctx.add_event(dot, event, world); + let graph_to_write_to = if other_ctxs + .iter() + .filter(|c| c.events_tracked.contains(event)) + .count() + > 1 + { + &mut *events_dotgraph + } else { + &mut *schedules_dotgraph + }; + + let event_id = ctx.add_event(graph_to_write_to, event, world); for writer in writers { // We have to use full names, because nodeId is schedule specific, and I want to support multiple schedules displayed let system_name = graph.get_system_at(writer).unwrap().name(); - dot.add_edge( + graph_to_write_to.add_edge( &system_name, &event_id, &[ @@ -207,7 +211,7 @@ pub fn print_only_context( for reader in readers { let system_name = graph.get_system_at(reader).unwrap().name(); - dot.add_edge( + graph_to_write_to.add_edge( &event_id, &system_name, &[ @@ -266,7 +270,7 @@ pub fn print_context( ("penwidth", "2"), ], ); - print_only_context(schedules, &mut schedule_graph, ctx, world); + print_only_context(schedules, &mut dot, &mut schedule_graph, ctx, ctxs, world); dot.add_sub_graph(schedule_graph); } dot.finish().to_string()