diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index edc9516cda8..b257ebd3f53 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -8,7 +8,7 @@ CHANGELOG.md rust-toolchain.toml @MichaelMauderer @4e6 @mwu-tow @farmaazon rustfmt.toml @MichaelMauderer @4e6 @mwu-tow @farmaazon Cargo.lock @MichaelMauderer @4e6 @mwu-tow @farmaazon @wdanilo -Cargo.toml @MichaelMauderer @4e6 @mwu-tow @farmaazon +Cargo.toml @MichaelMauderer @4e6 @mwu-tow @farmaazon @wdanilo /lib/rust/ @MichaelMauderer @4e6 @mwu-tow @farmaazon @wdanilo /lib/rust/ensogl/ @MichaelMauderer @wdanilo @farmaazon /integration-test/ @MichaelMauderer @wdanilo @farmaazon diff --git a/Cargo.lock b/Cargo.lock index cca9c640d50..2438fcb9bb7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -898,6 +898,8 @@ dependencies = [ "serde", "serde_json", "sha3", + "strum", + "strum_macros", "tokio", "uuid", "wasm-bindgen-test", @@ -1191,6 +1193,14 @@ dependencies = [ "syn", ] +[[package]] +name = "enso-profiler-metadata" +version = "0.1.0" +dependencies = [ + "enso-profiler", + "serde", +] + [[package]] name = "enso-shapely" version = "0.2.0" @@ -1339,6 +1349,7 @@ dependencies = [ "num-traits", "num_enum", "rustc-hash", + "serde", "shrinkwraprs 0.3.0", "smallvec 1.8.0", "typenum", @@ -1472,7 +1483,9 @@ version = "0.1.0" dependencies = [ "enso-frp", "enso-profiler", + "enso-profiler-data", "enso-profiler-flame-graph", + "enso-profiler-metadata", "enso-web", "ensogl-core", "ensogl-flame-graph", @@ -1480,7 +1493,10 @@ dependencies = [ "ensogl-text", "ensogl-text-msdf-sys", "futures 0.3.21", + "serde", "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", ] [[package]] @@ -2136,6 +2152,12 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -2455,6 +2477,7 @@ name = "json-rpc" version = "0.1.0" dependencies = [ "enso-prelude", + "enso-profiler", "enso-shapely", "enso-web", "failure", @@ -3927,6 +3950,25 @@ dependencies = [ "wasm-bindgen-test", ] +[[package]] +name = "strum" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e96acfc1b70604b8b2f1ffa4c57e59176c7dbb05d556c71ecd2f5498a1dee7f8" + +[[package]] +name = "strum_macros" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6878079b17446e4d3eba6192bb0a2950d5b14f0ed8424b852310e5a94345d0ef" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + [[package]] name = "syn" version = "1.0.86" diff --git a/Cargo.toml b/Cargo.toml index 157a35f0b17..86a62bc0fa2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ # where plausible. members = [ "app/gui", + "app/gui/enso-profiler-metadata", "build/enso-formatter", "build/rust-scripts", "lib/rust/*", diff --git a/app/gui/controller/engine-protocol/Cargo.toml b/app/gui/controller/engine-protocol/Cargo.toml index 9e7644c1a5f..e2486e05c07 100644 --- a/app/gui/controller/engine-protocol/Cargo.toml +++ b/app/gui/controller/engine-protocol/Cargo.toml @@ -8,21 +8,23 @@ edition = "2021" crate-type = ["cdylib", "rlib"] [dependencies] +chrono = { version = "0.4", features = ["serde"] } enso-data-structures = { path = "../../../../lib/rust/data-structures" } enso-logger = { path = "../../../../lib/rust/logger"} enso-prelude = { path = "../../../../lib/rust/prelude"} enso-shapely = { path = "../../../../lib/rust/shapely"} enso-text = { path = "../../../../lib/rust/text"} -json-rpc = { path = "../../../../lib/rust/json-rpc" } -chrono = { version = "0.4", features = ["serde"] } failure = { version = "0.1.8" } flatbuffers = { version = "0.5" } futures = { version = "0.3.1" } -mockall = { version = "0.7.1", features = ["nightly"] } hex = { version = "0.4.2" } +json-rpc = { path = "../../../../lib/rust/json-rpc" } +mockall = { version = "0.7.1", features = ["nightly"] } serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0" } sha3 = { version = "0.8.2" } +strum = "0.24.0" +strum_macros = "0.24.0" uuid = { version = "0.8", features = ["serde", "v4", "wasm-bindgen"] } [dev-dependencies] diff --git a/app/gui/controller/engine-protocol/src/language_server/types.rs b/app/gui/controller/engine-protocol/src/language_server/types.rs index c85afe1f4b6..37a91711386 100644 --- a/app/gui/controller/engine-protocol/src/language_server/types.rs +++ b/app/gui/controller/engine-protocol/src/language_server/types.rs @@ -2,6 +2,7 @@ use crate::language_server::*; +use strum_macros::IntoStaticStr; // ============= @@ -105,7 +106,7 @@ impl Path { // ==================== /// Notification generated by the Language Server. -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, IntoStaticStr)] #[serde(tag = "method", content = "params")] pub enum Notification { /// Filesystem event occurred for a watched path. diff --git a/app/gui/enso-profiler-metadata/Cargo.toml b/app/gui/enso-profiler-metadata/Cargo.toml new file mode 100644 index 00000000000..cc4c56ecb28 --- /dev/null +++ b/app/gui/enso-profiler-metadata/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "enso-profiler-metadata" +version = "0.1.0" +edition = "2021" + +[dependencies] +enso-profiler = { path = "../../../lib/rust/profiler"} +serde = { version = "1" } + diff --git a/app/gui/enso-profiler-metadata/src/lib.rs b/app/gui/enso-profiler-metadata/src/lib.rs new file mode 100644 index 00000000000..b66a716c76a --- /dev/null +++ b/app/gui/enso-profiler-metadata/src/lib.rs @@ -0,0 +1,25 @@ +//! Enso specific metadata logging utilities for use with the profiling framework. +use serde::Serializer; +use std::fmt::Display; +use std::fmt::Formatter; + + + +// ================ +// === Metadata === +// ================ + +/// Metadata that is logged within the Enso core libraries. +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Eq, PartialEq)] +pub enum Metadata { + /// An RPC event that was received from the backend. + RpcEvent(String), +} + +impl Display for Metadata { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Metadata::RpcEvent(name) => f.collect_str(name), + } + } +} diff --git a/app/gui/src/lib.rs b/app/gui/src/lib.rs index 0dbb188233d..2d542b46b53 100644 --- a/app/gui/src/lib.rs +++ b/app/gui/src/lib.rs @@ -95,7 +95,7 @@ pub mod prelude { pub use crate::model; pub use crate::model::traits::*; - pub use enso_profiler as profiler; + pub use enso_profiler; pub use enso_profiler::prelude::*; pub use engine_protocol::prelude::BoxFuture; diff --git a/app/gui/src/model/project/synchronized.rs b/app/gui/src/model/project/synchronized.rs index e5402d9d699..5ac4cb8f485 100644 --- a/app/gui/src/model/project/synchronized.rs +++ b/app/gui/src/model/project/synchronized.rs @@ -28,6 +28,22 @@ use parser::Parser; +// ================= +// === Profiling === +// ================= + +thread_local! { + /// A common preamble used to start every shader program. + static RPC_EVENT_LOGGER: enso_profiler::MetadataLogger<& 'static str> = enso_profiler::MetadataLogger::new("RpcEvent"); +} + +/// Log an RPC Event to the profiling framework. +pub fn log_rpc_event(event_name: &'static str) { + RPC_EVENT_LOGGER.with(|logger| logger.log(event_name)); +} + + + // ================================= // === ExecutionContextsRegistry === // ================================= @@ -457,6 +473,14 @@ impl Project { debug!(logger, "Received an event from the json-rpc protocol: {event:?}"); use engine_protocol::language_server::Event; use engine_protocol::language_server::Notification; + + // Profiler logging + if let Event::Notification(notification) = &event { + let name: &'static str = notification.into(); + log_rpc_event(name); + } + + // Event Handling match event { Event::Notification(Notification::FileEvent(_)) => {} Event::Notification(Notification::ExpressionUpdates(updates)) => { diff --git a/app/gui/view/graph-editor/src/component/node/growth_animation.rs b/app/gui/view/graph-editor/src/component/node/growth_animation.rs index 00502a7c066..2591671224e 100644 --- a/app/gui/view/graph-editor/src/component/node/growth_animation.rs +++ b/app/gui/view/graph-editor/src/component/node/growth_animation.rs @@ -10,6 +10,7 @@ use ensogl::prelude::*; use crate::GraphEditorModelWithNetwork; use crate::NodeId; +use crate::application::command::FrpNetworkProvider; use enso_frp as frp; use ensogl::animation::easing::EndStatus::Normal; use ensogl::display::Scene; @@ -17,7 +18,6 @@ use ensogl::Animation; use ensogl::Easing; - /// Describes the "speed" of growth/shrink animation. /// /// To determine the duration of the blending animation, we divide the length of the camera path by @@ -37,7 +37,7 @@ pub fn initialize_edited_node_animator( frp: &crate::Frp, scene: &Scene, ) { - let network = &frp.network; + let network = &frp.network(); let out = &frp.output; let searcher_cam = scene.layers.node_searcher.camera(); let edited_node_cam = scene.layers.edited_node.camera(); diff --git a/app/gui/view/graph-editor/src/lib.rs b/app/gui/view/graph-editor/src/lib.rs index 7dbbf2457c8..dd28e1d9cf0 100644 --- a/app/gui/view/graph-editor/src/lib.rs +++ b/app/gui/view/graph-editor/src/lib.rs @@ -50,6 +50,7 @@ use crate::component::visualization::MockDataGenerator3D; use crate::data::enso; pub use crate::node::profiling::Status as NodeProfilingStatus; +use crate::application::command::FrpNetworkProvider; use enso_config::ARGS; use enso_frp as frp; use ensogl::application; @@ -72,7 +73,6 @@ use ensogl::DEPRECATED_Tween; use ensogl_hardcoded_theme as theme; - // =============== // === Prelude === // =============== @@ -424,7 +424,7 @@ pub struct NodeSource { pub node: NodeId, } -ensogl::define_endpoints! { +ensogl::define_endpoints_2! { Input { // === General === /// Cancel the operation being currently performed. Often mapped to the escape key. @@ -711,7 +711,7 @@ ensogl::define_endpoints! { } } -impl application::command::FrpNetworkProvider for GraphEditor { +impl FrpNetworkProvider for GraphEditor { fn network(&self) -> &frp::Network { &self.model.network } @@ -1337,7 +1337,7 @@ impl Deref for GraphEditorModelWithNetwork { impl GraphEditorModelWithNetwork { /// Constructor. pub fn new(app: &Application, cursor: cursor::Cursor, frp: &Frp) -> Self { - let network = frp.network.clone_ref(); // FIXME make weak + let network = frp.network().clone_ref(); // FIXME make weak let model = GraphEditorModel::new(app, cursor, frp); Self { model, network } } @@ -1391,7 +1391,7 @@ impl GraphEditorModelWithNetwork { let first_detached = self.edges.detached_target.is_empty(); self.edges.detached_target.insert(edge_id); if first_detached { - self.frp.source.on_some_edges_targets_unset.emit(()); + self.frp.private.output.on_some_edges_targets_unset.emit(()); } edge_id } @@ -1406,7 +1406,7 @@ impl GraphEditorModelWithNetwork { let first_detached = self.edges.detached_source.is_empty(); self.edges.detached_source.insert(edge_id); if first_detached { - self.frp.source.on_some_edges_sources_unset.emit(()); + self.frp.private.output.on_some_edges_sources_unset.emit(()); } edge_id } @@ -1444,7 +1444,7 @@ struct NodeCreationContext<'a> { tooltip_update: &'a frp::Any, output_press: &'a frp::Source, input_press: &'a frp::Source, - output: &'a FrpEndpoints, + output: &'a api::private::Output, } impl GraphEditorModelWithNetwork { @@ -1477,7 +1477,6 @@ impl GraphEditorModelWithNetwork { } fn new_node(&self, ctx: &NodeCreationContext) -> Node { - use ensogl::application::command::FrpNetworkProvider; let view = component::Node::new(&self.app, self.vis_registry.clone_ref()); let node = Node::new(view); let node_model = node.model(); @@ -1499,10 +1498,10 @@ impl GraphEditorModelWithNetwork { eval_ node.background_press(touch.nodes.down.emit(node_id)); hovered <- node.output.hover.map (move |t| Some(Switch::new(node_id,*t))); - output.source.node_hovered <+ hovered; + output.node_hovered <+ hovered; eval node.comment ([model](comment) - model.frp.source.node_comment_set.emit((node_id,comment.clone())) + model.frp.private.output.node_comment_set.emit((node_id,comment.clone())) ); node.set_output_expression_visibility <+ self.frp.nodes_labels_visible; @@ -1523,12 +1522,12 @@ impl GraphEditorModelWithNetwork { eval node_model.input.frp.on_port_hover ([model](t) { let crumbs = t.on(); let target = crumbs.map(|c| EdgeEndpoint::new(node_id,c.clone())); - model.frp.source.hover_node_input.emit(target); + model.frp.private.output.hover_node_input.emit(target); }); eval node_model.output.frp.on_port_hover ([model](hover) { let output = hover.on().map(|crumbs| EdgeEndpoint::new(node_id,crumbs.clone())); - model.frp.source.hover_node_output.emit(output); + model.frp.private.output.hover_node_output.emit(output); }); let neutral_color = model.styles_frp.get_color(theme::code::types::any::selection); @@ -1547,18 +1546,18 @@ impl GraphEditorModelWithNetwork { ) )); - eval node.expression((t) output.source.node_expression_set.emit((node_id,t.into()))); + eval node.expression((t) model.frp.private.output.node_expression_set.emit((node_id,t.into()))); // === Actions === eval node.view.freeze ((is_frozen) { - output.source.node_action_freeze.emit((node_id,*is_frozen)); + model.frp.private.output.node_action_freeze.emit((node_id,*is_frozen)); }); let set_node_disabled = &node.set_disabled; - eval node.view.skip ([set_node_disabled,output](is_skipped) { - output.source.node_action_skip.emit((node_id,*is_skipped)); + eval node.view.skip ([set_node_disabled,model](is_skipped) { + model.frp.private.output.node_action_skip.emit((node_id,*is_skipped)); set_node_disabled.emit(is_skipped); }); @@ -1572,11 +1571,11 @@ impl GraphEditorModelWithNetwork { selected <- vis_is_selected.on_true(); deselected <- vis_is_selected.on_false(); - output.source.visualization_preprocessor_changed <+ + output.visualization_preprocessor_changed <+ node_model.visualization.frp.preprocessor.map(move |preprocessor| (node_id,preprocessor.clone())); - output.source.on_visualization_select <+ selected.constant(Switch::On(node_id)); - output.source.on_visualization_select <+ deselected.constant(Switch::Off(node_id)); + output.on_visualization_select <+ selected.constant(Switch::On(node_id)); + output.on_visualization_select <+ deselected.constant(Switch::Off(node_id)); metadata <- any(...); metadata <+ node_model.visualization.frp.preprocessor.map(visualization::Metadata::new); @@ -1586,8 +1585,8 @@ impl GraphEditorModelWithNetwork { // new one has been enabled. // TODO: Create a better API for updating the controller about visualisation changes // (see #896) - output.source.visualization_hidden <+ visualization_hidden.constant(node_id); - output.source.visualization_shown <+ + output.visualization_hidden <+ visualization_hidden.constant(node_id); + output.visualization_shown <+ visualization_shown.map2(&metadata,move |_,metadata| (node_id,metadata.clone())); @@ -1596,7 +1595,7 @@ impl GraphEditorModelWithNetwork { &node.visualization_enabled, &node.visualization_path, move |_init, is_enabled, path| (node_id, is_enabled.and_option(path.clone())) ); - output.source.enabled_visualization_path <+ enabled_visualization_path; + output.enabled_visualization_path <+ enabled_visualization_path; // === View Mode === @@ -1651,7 +1650,7 @@ pub struct GraphEditorModel { tooltip: Tooltip, touch_state: TouchState, visualisations: Visualisations, - frp: FrpEndpoints, + frp: Frp, profiling_statuses: profiling::Statuses, profiling_button: component::profiling::Button, styles_frp: StyleWatchFrp, @@ -1664,7 +1663,7 @@ pub struct GraphEditorModel { impl GraphEditorModel { #[allow(missing_docs)] // FIXME[everyone] All pub functions should have docs. pub fn new(app: &Application, cursor: cursor::Cursor, frp: &Frp) -> Self { - let network = &frp.network; + let network = frp.network(); let scene = &app.display.default_scene; let logger = Logger::new("GraphEditor"); let display_object = display::object::Instance::new(&logger); @@ -1675,7 +1674,7 @@ impl GraphEditorModel { let touch_state = TouchState::new(network, &scene.mouse.frp); let breadcrumbs = component::Breadcrumbs::new(app.clone_ref()); let app = app.clone_ref(); - let frp = frp.output.clone_ref(); + let frp = frp.clone_ref(); let navigator = Navigator::new(scene, &scene.camera()); let tooltip = Tooltip::new(&app); let profiling_statuses = profiling::Statuses::new(); @@ -1840,7 +1839,7 @@ impl GraphEditorModel { let node_id = node_id.into(); self.nodes.remove(&node_id); self.nodes.selected.remove_item(&node_id); - self.frp.source.on_visualization_select.emit(Switch::Off(node_id)); + self.frp.private.output.on_visualization_select.emit(Switch::Off(node_id)); } fn node_in_edges(&self, node_id: impl Into) -> Vec { @@ -1925,7 +1924,7 @@ impl GraphEditorModel { self.refresh_edge_position(edge_id); self.refresh_edge_source_size(edge_id); if first_detached { - self.frp.source.on_some_edges_sources_unset.emit(()); + self.frp.private.output.on_some_edges_sources_unset.emit(()); } } } @@ -1942,7 +1941,7 @@ impl GraphEditorModel { self.edges.detached_target.remove(&edge_id); let all_attached = self.edges.detached_target.is_empty(); if all_attached { - self.frp.source.on_all_edges_targets_set.emit(()); + self.frp.private.output.on_all_edges_targets_set.emit(()); } edge.view.frp.target_attached.emit(true); @@ -1962,7 +1961,7 @@ impl GraphEditorModel { edge.view.frp.target_attached.emit(false); self.refresh_edge_position(edge_id); if first_detached { - self.frp.source.on_some_edges_targets_unset.emit(()); + self.frp.private.output.on_some_edges_targets_unset.emit(()); } }; } @@ -2003,10 +2002,10 @@ impl GraphEditorModel { let no_detached_sources = self.edges.detached_source.is_empty(); let no_detached_targets = self.edges.detached_target.is_empty(); if no_detached_targets { - self.frp.source.on_all_edges_targets_set.emit(()); + self.frp.private.output.on_all_edges_targets_set.emit(()); } if no_detached_sources { - self.frp.source.on_all_edges_sources_set.emit(()); + self.frp.private.output.on_all_edges_sources_set.emit(()); } } @@ -2398,9 +2397,9 @@ impl GraphEditor { } impl Deref for GraphEditor { - type Target = Frp; + type Target = api::Public; fn deref(&self) -> &Self::Target { - &self.frp + &self.frp.public } } @@ -2504,7 +2503,7 @@ fn new_graph_editor(app: &Application) -> GraphEditor { let cursor = &app.cursor; let frp = Frp::new(); let model = GraphEditorModelWithNetwork::new(app, cursor.clone_ref(), &frp); - let network = &frp.network; + let network = frp.network(); let nodes = &model.nodes; let edges = &model.edges; let inputs = &model.frp; @@ -2512,7 +2511,7 @@ fn new_graph_editor(app: &Application) -> GraphEditor { let touch = &model.touch_state; let vis_registry = &model.vis_registry; let logger = &model.logger; - let out = &frp.output; + let out = &frp.private.output; let selection_controller = &model.selection_controller; // FIXME : StyleWatch is unsuitable here, as it was designed as an internal tool for shape @@ -2538,7 +2537,7 @@ fn new_graph_editor(app: &Application) -> GraphEditor { eval_ disable_navigator ( model.navigator.disable() ); eval_ enable_navigator ( model.navigator.enable() ); - out.source.navigator_active <+ inputs.set_navigator_disabled + out.navigator_active <+ inputs.set_navigator_disabled || out.some_visualisation_selected; } @@ -2576,7 +2575,7 @@ fn new_graph_editor(app: &Application) -> GraphEditor { // Go level up on background click. enter_on_background <= target_to_enter.map(|target| target.is_background().as_some(())); - out.source.node_exited <+ enter_on_background; + out.node_exited <+ enter_on_background; // Go level down on node double click. enter_on_node <= target_to_enter.map(|target| target.is_symbol().as_some(())); @@ -2584,7 +2583,7 @@ fn new_graph_editor(app: &Application) -> GraphEditor { enter_node <- enter_on_node.gate_not(&output_port_is_hovered); node_switch_to_enter <- out.node_hovered.sample(&enter_node).unwrap(); node_to_enter <- node_switch_to_enter.map(|switch| switch.on().cloned()).unwrap(); - out.source.node_entered <+ node_to_enter; + out.node_entered <+ node_to_enter; } @@ -2648,7 +2647,7 @@ fn new_graph_editor(app: &Application) -> GraphEditor { let node_input_touch = TouchNetwork::::new(network,mouse); let node_output_touch = TouchNetwork::::new(network,mouse); node_expression_set <- source(); - out.source.node_expression_set <+ node_expression_set; + out.node_expression_set <+ node_expression_set; on_output_connect_drag_mode <- node_output_touch.down.constant(true); on_output_connect_follow_mode <- node_output_touch.selected.constant(false); @@ -2661,7 +2660,7 @@ fn new_graph_editor(app: &Application) -> GraphEditor { on_detached_edge <- any(&inputs.on_some_edges_targets_unset,&inputs.on_some_edges_sources_unset); has_detached_edge <- bool(&out.on_all_edges_endpoints_set,&on_detached_edge); - out.source.has_detached_edge <+ has_detached_edge; + out.has_detached_edge <+ has_detached_edge; eval node_input_touch.down ((target) model.frp.press_node_input.emit(target)); eval node_output_touch.down ((target) model.frp.press_node_output.emit(target)); @@ -2716,8 +2715,8 @@ fn new_graph_editor(app: &Application) -> GraphEditor { on_edge_source_unset <= edge_source_click.map(f!(((id,_)) model.with_edge_source(*id,|t|(*id,t)))); on_edge_target_unset <= edge_target_click.map(f!(((id,_)) model.with_edge_target(*id,|t|(*id,t)))); - out.source.on_edge_source_unset <+ on_edge_source_unset; - out.source.on_edge_target_unset <+ on_edge_target_unset; + out.on_edge_source_unset <+ on_edge_source_unset; + out.on_edge_target_unset <+ on_edge_target_unset; } @@ -2755,13 +2754,13 @@ fn new_graph_editor(app: &Application) -> GraphEditor { Some(model.new_edge_from_input(&edge_mouse_down,&edge_over,&edge_out)) })).unwrap(); - out.source.on_edge_add <+ new_output_edge; + out.on_edge_add <+ new_output_edge; new_edge_source <- new_output_edge.map2(&node_output_touch.down, move |id,target| (*id,target.clone())); - out.source.on_edge_source_set <+ new_edge_source; + out.on_edge_source_set <+ new_edge_source; - out.source.on_edge_add <+ new_input_edge; + out.on_edge_add <+ new_input_edge; new_edge_target <- new_input_edge.map2(&node_input_touch.down, move |id,target| (*id,target.clone())); - out.source.on_edge_target_set <+ new_edge_target; + out.on_edge_target_set <+ new_edge_target; } @@ -2775,36 +2774,36 @@ fn new_graph_editor(app: &Application) -> GraphEditor { clicked_to_drop_edge <- was_edge_detached_when_background_selected.on_true(); clicked_to_abort_edit <- was_edge_detached_when_background_selected.on_false(); - out.source.on_edge_source_set <+ inputs.set_edge_source; - out.source.on_edge_target_set <+ inputs.set_edge_target; + out.on_edge_source_set <+ inputs.set_edge_source; + out.on_edge_target_set <+ inputs.set_edge_target; let endpoints = inputs.connect_nodes.clone_ref(); edge <- endpoints . map(f_!(model.new_edge_from_output(&edge_mouse_down,&edge_over,&edge_out))); new_edge_source <- endpoints . _0() . map2(&edge, |t,id| (*id,t.clone())); new_edge_target <- endpoints . _1() . map2(&edge, |t,id| (*id,t.clone())); - out.source.on_edge_add <+ edge; - out.source.on_edge_source_set <+ new_edge_source; - out.source.on_edge_target_set <+ new_edge_target; + out.on_edge_add <+ edge; + out.on_edge_source_set <+ new_edge_source; + out.on_edge_target_set <+ new_edge_target; detached_edges_without_targets <= attach_all_edge_inputs.map(f_!(model.take_edges_with_detached_targets())); detached_edges_without_sources <= attach_all_edge_outputs.map(f_!(model.take_edges_with_detached_sources())); new_edge_target <- detached_edges_without_targets.map2(&attach_all_edge_inputs, |id,t| (*id,t.clone())); - out.source.on_edge_target_set <+ new_edge_target; + out.on_edge_target_set <+ new_edge_target; new_edge_source <- detached_edges_without_sources.map2(&attach_all_edge_outputs, |id,t| (*id,t.clone())); - out.source.on_edge_source_set <+ new_edge_source; + out.on_edge_source_set <+ new_edge_source; on_new_edge_source <- new_edge_source.constant(()); on_new_edge_target <- new_edge_target.constant(()); overlapping_edges <= out.on_edge_target_set._1().map(f!((t) model.overlapping_edges(t))); - out.source.on_edge_drop <+ overlapping_edges; + out.on_edge_drop <+ overlapping_edges; drop_on_bg_up <- background_up.gate(&connect_drag_mode); drop_edges <- any (drop_on_bg_up,clicked_to_drop_edge); edge_dropped_to_create_node <= drop_edges.map(f_!(model.edges_with_detached_targets())); - out.source.on_edge_drop_to_create_node <+ edge_dropped_to_create_node; + out.on_edge_drop_to_create_node <+ edge_dropped_to_create_node; remove_all_detached_edges <- any (drop_edges, inputs.drop_dragged_edge); edge_to_remove_without_targets <= remove_all_detached_edges.map(f_!(model.take_edges_with_detached_targets())); @@ -2824,7 +2823,7 @@ fn new_graph_editor(app: &Application) -> GraphEditor { |v| v.clone()); removed_edges_on_node_creation_from_port <= start_node_creation_from_port.map(f_!( model.model.clear_all_detached_edges())); - out.source.on_edge_drop <+ removed_edges_on_node_creation_from_port; + out.on_edge_drop <+ removed_edges_on_node_creation_from_port; input_add_node_way <- inputs.add_node.constant(WayOfCreatingNode::AddNodeEvent); input_start_creation_way <- inputs.start_node_creation.constant( @@ -2852,7 +2851,7 @@ fn new_graph_editor(app: &Application) -> GraphEditor { }; model.create_node(&ctx, way, *mouse_pos) })); - out.source.node_added <+ new_node.map(|&(id, src, should_edit)| (id, src, should_edit)); + out.node_added <+ new_node.map(|&(id, src, should_edit)| (id, src, should_edit)); node_to_edit_after_adding <- new_node.filter_map(|&(id,_,cond)| cond.as_some(id)); } @@ -2870,16 +2869,16 @@ fn new_graph_editor(app: &Application) -> GraphEditor { node_being_edited <- out.node_being_edited.map(|n| n.unwrap_or_default()); // The "finish" events must be emitted before "start", to properly cover the "switch" case. - out.source.node_editing_finished <+ node_being_edited.sample(&stop_edit); - out.source.node_editing_finished <+ node_being_edited.sample(&edit_switch); - out.source.node_editing_started <+ edit_node; + out.node_editing_finished <+ node_being_edited.sample(&stop_edit); + out.node_editing_finished <+ node_being_edited.sample(&edit_switch); + out.node_editing_started <+ edit_node; - out.source.node_being_edited <+ out.node_editing_started.map(|n| Some(*n));; - out.source.node_being_edited <+ out.node_editing_finished.constant(None); - out.source.node_editing <+ out.node_being_edited.map(|t|t.is_some()); + out.node_being_edited <+ out.node_editing_started.map(|n| Some(*n));; + out.node_being_edited <+ out.node_editing_finished.constant(None); + out.node_editing <+ out.node_being_edited.map(|t|t.is_some()); - out.source.node_edit_mode <+ edit_mode; - out.source.nodes_labels_visible <+ out.node_edit_mode || node_in_edit_mode; + out.node_edit_mode <+ edit_mode; + out.nodes_labels_visible <+ out.node_edit_mode || node_in_edit_mode; eval out.node_editing_started ([model] (id) { if let Some(node) = model.nodes.get_cloned_ref(id) { @@ -2950,7 +2949,7 @@ fn new_graph_editor(app: &Application) -> GraphEditor { nodes_to_remove <- any (all_nodes, selected_nodes); eval nodes_to_remove ((node_id) inputs.remove_all_node_edges.emit(node_id)); - out.source.node_removed <+ nodes_to_remove; + out.node_removed <+ nodes_to_remove; } @@ -2964,7 +2963,7 @@ fn new_graph_editor(app: &Application) -> GraphEditor { nodes_to_collapse <- inputs.collapse_selected_nodes . map(move |_| (model_clone.nodes.all_selected(),empty_id) ); - out.source.nodes_collapsed <+ nodes_to_collapse; + out.nodes_collapsed <+ nodes_to_collapse; } @@ -2972,7 +2971,7 @@ fn new_graph_editor(app: &Application) -> GraphEditor { frp::extend! { network set_node_expression_string <- inputs.set_node_expression.map(|(id,expr)| (*id,expr.code.clone())); - out.source.node_expression_set <+ set_node_expression_string; + out.node_expression_set <+ set_node_expression_string; } @@ -3081,7 +3080,7 @@ fn new_graph_editor(app: &Application) -> GraphEditor { main_tgt_pos_diff <- node_tgt_pos.map2(&main_tgt_pos_prev,|t,s|t-s).gate_not(&just_pressed); drag_tgt <= drag_tgts.sample(&main_tgt_pos_diff); tgt_new_pos <- drag_tgt.map2(&main_tgt_pos_diff,f!((id,tx) model.node_pos_mod(id,*tx))); - out.source.node_position_set <+ tgt_new_pos; + out.node_position_set <+ tgt_new_pos; // === Batch Update === @@ -3089,7 +3088,7 @@ fn new_graph_editor(app: &Application) -> GraphEditor { after_drag <- touch.nodes.up.gate_not(&just_pressed); tgt_after_drag <= drag_tgts.sample(&after_drag); tgt_after_drag_new_pos <- tgt_after_drag.map(f!([model](id)(*id,model.node_position(id)))); - out.source.node_position_set_batched <+ tgt_after_drag_new_pos; + out.node_position_set_batched <+ tgt_after_drag_new_pos; // === Mouse style === @@ -3102,8 +3101,8 @@ fn new_graph_editor(app: &Application) -> GraphEditor { // === Set Node Position === - out.source.node_position_set <+ inputs.set_node_position; - out.source.node_position_set_batched <+ inputs.set_node_position; + out.node_position_set <+ inputs.set_node_position; + out.node_position_set_batched <+ inputs.set_node_position; eval out.node_position_set (((id,pos)) model.set_node_position(id,*pos)); } @@ -3221,7 +3220,7 @@ fn new_graph_editor(app: &Application) -> GraphEditor { } }); - out.source.some_visualisation_selected <+ out.on_visualization_select.map(f_!([model] { + out.some_visualisation_selected <+ out.on_visualization_select.map(f_!([model] { !model.visualisations.selected.is_empty() })); }; @@ -3312,10 +3311,10 @@ fn new_graph_editor(app: &Application) -> GraphEditor { } }); - out.source.visualization_fullscreen <+ viz_fullscreen_on.map(|id| Some(*id)); - out.source.visualization_fullscreen <+ inputs.close_fullscreen_visualization.constant(None); + out.visualization_fullscreen <+ viz_fullscreen_on.map(|id| Some(*id)); + out.visualization_fullscreen <+ inputs.close_fullscreen_visualization.constant(None); - out.source.is_fs_visualization_displayed <+ out.visualization_fullscreen.map(Option::is_some); + out.is_fs_visualization_displayed <+ out.visualization_fullscreen.map(Option::is_some); // === Register Visualization === @@ -3329,17 +3328,17 @@ fn new_graph_editor(app: &Application) -> GraphEditor { vis_registry.remove_all_visualizations(); vis_registry.add_default_visualizations(); }); - out.source.visualization_registry_reload_requested <+ inputs.reload_visualization_registry; + out.visualization_registry_reload_requested <+ inputs.reload_visualization_registry; // === Entering and Exiting Nodes === node_to_enter <= inputs.enter_selected_node.map(f_!(model.nodes.last_selected())); - out.source.node_entered <+ node_to_enter; + out.node_entered <+ node_to_enter; removed_edges_on_enter <= out.node_entered.map(f_!(model.model.clear_all_detached_edges())); - out.source.node_exited <+ inputs.exit_node; + out.node_exited <+ inputs.exit_node; removed_edges_on_exit <= out.node_exited.map(f_!(model.model.clear_all_detached_edges())); - out.source.on_edge_drop <+ any(removed_edges_on_enter,removed_edges_on_exit); + out.on_edge_drop <+ any(removed_edges_on_enter,removed_edges_on_exit); @@ -3370,15 +3369,15 @@ fn new_graph_editor(app: &Application) -> GraphEditor { is_only_tgt_not_set <- out.on_edge_source_set.map(f!(((id,_)) model.with_edge_map_target(*id,|_|()).is_none())); - out.source.on_edge_source_set_with_target_not_set <+ out.on_edge_source_set.gate(&is_only_tgt_not_set); - out.source.on_edge_only_target_not_set <+ out.on_edge_source_set_with_target_not_set._0(); - out.source.on_edge_only_target_not_set <+ out.on_edge_target_unset._0(); + out.on_edge_source_set_with_target_not_set <+ out.on_edge_source_set.gate(&is_only_tgt_not_set); + out.on_edge_only_target_not_set <+ out.on_edge_source_set_with_target_not_set._0(); + out.on_edge_only_target_not_set <+ out.on_edge_target_unset._0(); is_only_src_not_set <- out.on_edge_target_set.map(f!(((id,_)) model.with_edge_map_source(*id,|_|()).is_none())); - out.source.on_edge_target_set_with_source_not_set <+ out.on_edge_target_set.gate(&is_only_src_not_set); - out.source.on_edge_only_source_not_set <+ out.on_edge_target_set_with_source_not_set._0(); - out.source.on_edge_only_source_not_set <+ out.on_edge_source_unset._0(); + out.on_edge_target_set_with_source_not_set <+ out.on_edge_target_set.gate(&is_only_src_not_set); + out.on_edge_only_source_not_set <+ out.on_edge_target_set_with_source_not_set._0(); + out.on_edge_only_source_not_set <+ out.on_edge_source_unset._0(); let neutral_color = model.model.styles_frp.get_color(theme::code::types::any::selection); eval out.on_edge_source_set ([model,neutral_color]((id, _)) @@ -3399,10 +3398,10 @@ fn new_graph_editor(app: &Application) -> GraphEditor { some_edge_sources_unset <- out.on_all_edges_sources_set ?? out.on_some_edges_sources_unset; some_edge_targets_unset <- out.on_all_edges_targets_set ?? out.on_some_edges_targets_unset; some_edge_endpoints_unset <- out.some_edge_targets_unset || out.some_edge_sources_unset; - out.source.some_edge_sources_unset <+ some_edge_sources_unset; - out.source.some_edge_targets_unset <+ some_edge_targets_unset; - out.source.some_edge_endpoints_unset <+ some_edge_endpoints_unset; - out.source.on_all_edges_endpoints_set <+ out.some_edge_endpoints_unset.on_false(); + out.some_edge_sources_unset <+ some_edge_sources_unset; + out.some_edge_targets_unset <+ some_edge_targets_unset; + out.some_edge_endpoints_unset <+ some_edge_endpoints_unset; + out.on_all_edges_endpoints_set <+ out.some_edge_endpoints_unset.on_false(); // === Endpoints === @@ -3413,10 +3412,10 @@ fn new_graph_editor(app: &Application) -> GraphEditor { edge_endpoint_set <- any(out.on_edge_source_set,out.on_edge_target_set)._0(); both_endpoints_set <- edge_endpoint_set.map(f!((id) model.is_connection(id))); new_edge_with_both_endpoints_set <- edge_endpoint_set.gate(&both_endpoints_set); - out.source.on_edge_endpoints_set <+ new_edge_with_both_endpoints_set; - out.source.on_edge_endpoint_set <+ any(out.on_edge_source_set,out.on_edge_target_set); - out.source.on_edge_endpoint_unset <+ any(out.on_edge_source_unset,out.on_edge_target_unset); - out.source.on_edge_endpoint_unset <+ any(edge_source_drop,edge_target_drop); + out.on_edge_endpoints_set <+ new_edge_with_both_endpoints_set; + out.on_edge_endpoint_set <+ any(out.on_edge_source_set,out.on_edge_target_set); + out.on_edge_endpoint_unset <+ any(out.on_edge_source_unset,out.on_edge_target_unset); + out.on_edge_endpoint_unset <+ any(edge_source_drop,edge_target_drop); // === Drop === @@ -3433,14 +3432,14 @@ fn new_graph_editor(app: &Application) -> GraphEditor { eval out.node_deselected ((id) model.nodes.deselect(id)); eval out.node_removed ((id) model.remove_node(id)); model.profiling_statuses.remove <+ out.node_removed; - out.source.on_visualization_select <+ out.node_removed.map(|&id| Switch::Off(id)); + out.on_visualization_select <+ out.node_removed.map(|&id| Switch::Off(id)); eval inputs.set_node_expression (((id,expr)) model.set_node_expression(id,expr)); port_to_refresh <= inputs.set_node_expression.map(f!(((id,_))model.node_in_edges(id))); eval port_to_refresh ((id) model.set_edge_target_connection_status(*id,true)); // === Remove implementation === - out.source.node_removed <+ inputs.remove_node; + out.node_removed <+ inputs.remove_node; } @@ -3452,7 +3451,7 @@ fn new_graph_editor(app: &Application) -> GraphEditor { input_edges_to_rm <= rm_input_edges . map(f!((node_id) model.node_in_edges(node_id))); output_edges_to_rm <= rm_output_edges . map(f!((node_id) model.node_out_edges(node_id))); edges_to_rm <- any (inputs.remove_edge, input_edges_to_rm, output_edges_to_rm); - out.source.on_edge_drop <+ edges_to_rm; + out.on_edge_drop <+ edges_to_rm; } @@ -3546,7 +3545,7 @@ fn new_graph_editor(app: &Application) -> GraphEditor { } ); file_dropped <= files_with_positions; - out.source.file_dropped <+ file_dropped; + out.file_dropped <+ file_dropped; } @@ -3557,8 +3556,8 @@ fn new_graph_editor(app: &Application) -> GraphEditor { let profiling_mode_transition = Animation::new(network); frp::extend! { network - out.source.view_mode <+ frp.toggle_profiling_mode.map2(&frp.view_mode,|_,&mode| mode.switch()); - out.source.view_mode <+ model.profiling_button.view_mode; + out.view_mode <+ frp.toggle_profiling_mode.map2(&frp.view_mode,|_,&mode| mode.switch()); + out.view_mode <+ model.profiling_button.view_mode; model.profiling_button.set_view_mode <+ out.view_mode.on_change(); _eval <- all_with(&out.view_mode,&neutral_color,f!((_,neutral_color) @@ -3588,13 +3587,13 @@ fn new_graph_editor(app: &Application) -> GraphEditor { let default_y_gap = styles.get_number_or(default_y_gap_path, 0.0); let min_x_spacing = styles.get_number_or(min_x_spacing_path, 0.0); frp::extend! { network - frp.source.default_x_gap_between_nodes <+ default_x_gap; - frp.source.default_y_gap_between_nodes <+ default_y_gap; - frp.source.min_x_spacing_for_new_nodes <+ min_x_spacing; + frp.private.output.default_x_gap_between_nodes <+ default_x_gap; + frp.private.output.default_y_gap_between_nodes <+ default_y_gap; + frp.private.output.min_x_spacing_for_new_nodes <+ min_x_spacing; } - frp.source.default_x_gap_between_nodes.emit(default_x_gap.value()); - frp.source.default_y_gap_between_nodes.emit(default_y_gap.value()); - frp.source.min_x_spacing_for_new_nodes.emit(min_x_spacing.value()); + frp.private.output.default_x_gap_between_nodes.emit(default_x_gap.value()); + frp.private.output.default_y_gap_between_nodes.emit(default_y_gap.value()); + frp.private.output.min_x_spacing_for_new_nodes.emit(min_x_spacing.value()); @@ -3603,7 +3602,7 @@ fn new_graph_editor(app: &Application) -> GraphEditor { // ================== frp::extend! { network - out.source.debug_mode <+ frp.set_debug_mode; + out.debug_mode <+ frp.set_debug_mode; limit_max_zoom <- frp.set_debug_mode.on_false(); unlimit_max_zoom <- frp.set_debug_mode.on_true(); diff --git a/app/gui/view/graph-editor/src/selection.rs b/app/gui/view/graph-editor/src/selection.rs index 69e4de10ea6..18aff1057c2 100644 --- a/app/gui/view/graph-editor/src/selection.rs +++ b/app/gui/view/graph-editor/src/selection.rs @@ -241,7 +241,7 @@ fn get_nodes_in_bounding_box(bounding_box: &BoundingBox, nodes: &Nodes) -> Vec frp::stream::Stream { +pub fn get_mode(network: &frp::Network, editor: &crate::Frp) -> frp::stream::Stream { frp::extend! { network let multi_select_flag = crate::enable_disable_toggle @@ -307,7 +307,7 @@ pub struct Controller { impl Controller { pub fn new( - editor: &crate::FrpEndpoints, + editor: &crate::Frp, cursor: &Cursor, mouse: &frp::io::Mouse, touch: &TouchState, @@ -317,6 +317,7 @@ impl Controller { let selection_mode = get_mode(&network, editor); let cursor_selection_nodes = node_set::Set::new(); + let editor = &editor.private; frp::extend! { network deselect_all_nodes <- any_(...); @@ -324,16 +325,16 @@ impl Controller { enable_area_selection <- source(); // === Graph Editor Internal API === - eval editor.select_node ((node_id) nodes.select(node_id)); - eval editor.deselect_node ((node_id) nodes.select(node_id)); - editor.source.node_selected <+ editor.select_node; - editor.source.node_deselected <+ editor.deselect_node; + eval editor.input.select_node ((node_id) nodes.select(node_id)); + eval editor.input.deselect_node ((node_id) nodes.select(node_id)); + editor.output.node_selected <+ editor.input.select_node; + editor.output.node_deselected <+ editor.input.deselect_node; // === Selection Box & Mouse IO === on_press_style <- mouse.down_primary . constant(cursor::Style::new_press()); on_release_style <- mouse.up_primary . constant(cursor::Style::default()); - edit_mode <- bool(&editor.edit_mode_off,&editor.edit_mode_on); + edit_mode <- bool(&editor.input.edit_mode_off,&editor.input.edit_mode_on); not_edit_mode <- edit_mode.not(); should_area_select <- not_edit_mode && enable_area_selection; @@ -399,20 +400,20 @@ impl Controller { |info,mode| mode.area_should_deselect(info.was_selected) ); - editor.source.node_selected <+ node_added.gate(&should_select); - editor.source.node_deselected <+ node_added.gate(&should_deselect); + editor.output.node_selected <+ node_added.gate(&should_select); + editor.output.node_deselected <+ node_added.gate(&should_deselect); // Node leaves selection area, revert to previous selection state. node_removed <- cursor_selection_nodes.removed.map(f!([](node_info) { if !node_info.was_selected { Some(node_info.node) } else {None} })).unwrap(); - editor.source.node_deselected <+ node_removed; + editor.output.node_deselected <+ node_removed; // === Single Node Selection Box & Mouse IO === - should_not_select <- edit_mode || editor.some_edge_endpoints_unset; + should_not_select <- edit_mode || editor.output.some_edge_endpoints_unset; node_to_select_non_edit <- touch.nodes.selected.gate_not(&should_not_select); node_to_select_edit <- touch.nodes.down.gate(&edit_mode); node_to_select <- any(node_to_select_non_edit, @@ -429,17 +430,17 @@ impl Controller { deselect_on_select <- node_to_select.gate_not(&keep_selection); deselect_all_nodes <+ deselect_on_select; - deselect_all_nodes <+ editor.deselect_all_nodes; + deselect_all_nodes <+ editor.input.deselect_all_nodes; deselect_on_bg_press <- touch.background.selected.gate_not(&keep_selection); deselect_all_nodes <+ deselect_on_bg_press; all_nodes_to_deselect <= deselect_all_nodes.map(f_!(nodes.selected.mem_take())); - editor.source.node_deselected <+ all_nodes_to_deselect; + editor.output.node_deselected <+ all_nodes_to_deselect; node_selected <- node_to_select.gate(&should_select); node_deselected <- node_to_select.gate(&should_deselect); - editor.source.node_selected <+ node_selected; - editor.source.node_deselected <+ node_deselected; + editor.output.node_selected <+ node_selected; + editor.output.node_deselected <+ node_deselected; // === Output bindings === cursor_style <- any(on_press_style,on_release_style,cursor_selection); diff --git a/lib/rust/ensogl/component/flame-graph/src/block.rs b/lib/rust/ensogl/component/flame-graph/src/block.rs index 328d0ad11c1..4fa68b55b22 100644 --- a/lib/rust/ensogl/component/flame-graph/src/block.rs +++ b/lib/rust/ensogl/component/flame-graph/src/block.rs @@ -5,12 +5,16 @@ use ensogl_core::prelude::*; use ensogl::frp; use ensogl_core::application::Application; +use ensogl_core::data::color; +use ensogl_core::data::color::Lcha; use ensogl_core::display; use ensogl_core::display::shape::StyleWatchFrp; use ensogl_gui_component::component; use ensogl_gui_component::component::ComponentView; use ensogl_text as text; +use super::BASE_TEXT_SIZE; + // ======================= @@ -31,11 +35,11 @@ const EMPTY_LABEL: &str = ""; mod background { use super::*; ensogl_core::define_shape_system! { - (style:Style) { + (style:Style,color_rgba:Vector4) { let width : Var = "input_size.x".into(); let height : Var = "input_size.y".into(); let zoom = Var::::from("1.0/zoom()"); - let base_color = style.get_color("flame_graph_color"); + let color = Var::::from(color_rgba); let shape = Rect((&width,&height)); @@ -49,7 +53,7 @@ mod background { let shape = shape - left; let shape = shape - right; - let shape = shape.fill(base_color); + let shape = shape.fill(color); (shape).into() } @@ -65,7 +69,8 @@ mod background { ensogl_core::define_endpoints_2! { Input { set_content(String), - set_size(Vector2) + set_size(Vector2), + set_color(Lcha) } Output {} } @@ -77,6 +82,7 @@ impl component::Frp for Frp { frp::extend! { network eval api.input.set_content((t) model.set_content(t)); eval api.input.set_size((size) model.set_size(*size)); + eval api.input.set_color((color) model.set_color(*color)); is_hovered <- bool(&background.mouse_out, &background.mouse_over); eval is_hovered((hovered) model.set_label_visible(*hovered)); @@ -146,6 +152,9 @@ impl Model { let text_layer = &self.app.display.default_scene.layers.tooltip_text; label.add_to_scene_layer(text_layer); + label.set_default_text_size(text::Size( + BASE_TEXT_SIZE / self.app.display.default_scene.camera().zoom(), + )); let text_size = label.height.value(); let text_origin = Vector2( TEXT_OFFSET_X - self.background.size.get().x / 2.0, @@ -156,6 +165,10 @@ impl Model { self.label.set(label); } + + fn set_color(&self, color: Lcha) { + self.background.color_rgba.set(color::Rgba::from(color).into()); + } } impl display::Object for Model { diff --git a/lib/rust/ensogl/component/flame-graph/src/lib.rs b/lib/rust/ensogl/component/flame-graph/src/lib.rs index 14a80417412..e5b30c94e38 100644 --- a/lib/rust/ensogl/component/flame-graph/src/lib.rs +++ b/lib/rust/ensogl/component/flame-graph/src/lib.rs @@ -22,7 +22,12 @@ use ensogl_core::display; mod block; +mod mark; +use enso_profiler_flame_graph::State; +use ensogl_core::data::color; +use ensogl_core::display::shape::StyleWatchFrp; +use mark::Mark; pub use block::Block; @@ -34,6 +39,7 @@ pub use block::Block; const ROW_HEIGHT: f64 = 20.0; const ROW_PADDING: f64 = 5.0; +pub(crate) const BASE_TEXT_SIZE: f32 = 18.0; @@ -48,6 +54,7 @@ const ROW_PADDING: f64 = 5.0; pub struct FlameGraph { display_object: display::object::Instance, blocks: Vec, + marks: Vec, } /// Instantiate a `Block` shape for the given block data from the profiler. @@ -63,33 +70,82 @@ fn shape_from_block(block: profiler_flame_graph::Block, app: &Application) -> Bl component.set_size.emit(size); component.set_position_xy(pos); + let style = StyleWatchFrp::new(&app.display.default_scene.style_sheet); + + let color: color::Rgba = match block.state { + State::Active => style.get_color("flame_graph_block_color_active").value(), + State::Paused => style.get_color("flame_graph_block_color_paused").value(), + }; + let color: color::Lcha = color.into(); + component.set_color(color); + component } +/// Instantiate a `Mark` shape for the given block data from the profiler. +fn shape_from_mark(mark: profiler_flame_graph::Mark, app: &Application) -> Mark { + let component = app.new_view::(); + let x = mark.position as f32; + let y = 0.0; + let pos = Vector2::new(x, y); + + let label = format!("{} ({:.1}ms)", mark.label, mark.position); + + component.set_content.emit(label); + component.set_position_xy(pos); + + component +} + +const MIN_INTERVAL_TIME_MS: f64 = 0.0; +const X_SCALE: f64 = 1.0; + impl FlameGraph { /// Create a `FlameGraph` EnsoGL component from the given graph data from the profiler. pub fn from_data(data: profiler_flame_graph::Graph, app: &Application) -> Self { let logger = Logger::new("FlameGraph"); let display_object = display::object::Instance::new(&logger); - let min_time = - data.blocks.iter().map(|block| block.start.floor() as u32).min().unwrap_or_default(); + let blocks = data.blocks.into_iter().filter(|block| block.width() > MIN_INTERVAL_TIME_MS); + let marks = data.marks; - let blocks_zero_aligned = data.blocks.into_iter().map(|mut block| { - block.start -= min_time as f64; - block.end -= min_time as f64; + let origin_x = + blocks.clone().map(|block| block.start.floor() as u32).min().unwrap_or_default(); + + let blocks_zero_aligned = blocks.clone().map(|mut block| { + // Shift + block.start -= origin_x as f64; + block.end -= origin_x as f64; + // Scale + block.start *= X_SCALE; + block.end *= X_SCALE; block }); - let blocks = blocks_zero_aligned.map(|block| shape_from_block(block, app)).collect_vec(); blocks.iter().for_each(|item| display_object.add_child(item)); - Self { display_object, blocks } + + + let blocks_marks_aligned = marks.into_iter().map(|mut mark| { + mark.position -= origin_x as f64; + mark.position *= X_SCALE; + mark + }); + let marks: Vec<_> = + blocks_marks_aligned.into_iter().map(|mark| shape_from_mark(mark, app)).collect(); + marks.iter().for_each(|item| display_object.add_child(item)); + + Self { display_object, blocks, marks } } /// Return a reference to the blocks that make up the flame graph. pub fn blocks(&self) -> &[Block] { &self.blocks } + + /// Return a reference to the marks that make up the flame graph. + pub fn marks(&self) -> &[Mark] { + &self.marks + } } impl display::Object for FlameGraph { diff --git a/lib/rust/ensogl/component/flame-graph/src/mark.rs b/lib/rust/ensogl/component/flame-graph/src/mark.rs new file mode 100644 index 00000000000..bd3df4ed9c0 --- /dev/null +++ b/lib/rust/ensogl/component/flame-graph/src/mark.rs @@ -0,0 +1,189 @@ +//! A single mark component that is used to build up a flame graph. + +use ensogl_core::display::shape::*; +use ensogl_core::prelude::*; + +use ensogl::frp; +use ensogl_core::application::Application; +use ensogl_core::data::color; +use ensogl_core::display; +use ensogl_core::display::shape::StyleWatchFrp; +use ensogl_gui_component::component; +use ensogl_gui_component::component::ComponentView; +use ensogl_text as text; + +use super::BASE_TEXT_SIZE; + + + +// ================= +// === Constants === +// ================= + + +const EMPTY_LABEL: &str = ""; + +const MARK_WIDTH: f32 = 2.0; +const MARK_HOVER_AREA_WIDTH: f32 = 20.0; + +/// Invisible dummy color to catch hover events. +const HOVER_COLOR: color::Rgba = color::Rgba::new(1.0, 0.0, 0.0, 0.000_001); + +const INFINITE: f32 = 99999.0; + +const TEXT_OFFSET_X: f32 = MARK_WIDTH + 8.0; + + + +// ========================= +// === Shape Definition === +// ========================= + +mod background { + use super::*; + ensogl_core::define_shape_system! { + (style:Style) { + let width : Var = MARK_WIDTH.px(); + let height : Var = INFINITE.px(); + let zoom = &Var::::from("1.0/zoom()"); + let base_color = style.get_color("flame_graph_mark_color"); + + let shape = Rect((&width*zoom,&height)); + let shape = shape.fill(base_color); + + let hover_area = shape.grow(zoom * MARK_HOVER_AREA_WIDTH.px()); + let hover_area = hover_area.fill(HOVER_COLOR); + + (shape + hover_area).into() + } + } +} + + + +// =========== +// === FRP === +// =========== + +ensogl_core::define_endpoints_2! { + Input { + set_content(String), + } + Output {} +} + +impl component::Frp for Frp { + fn init(api: &Self::Private, app: &Application, model: &Model, _style: &StyleWatchFrp) { + let network = &api.network; + let background = &model.background.events; + + let cursor_pos = app.cursor.frp.scene_position.clone_ref(); + frp::extend! { network + eval api.input.set_content((t) model.set_content(t)); + + is_hovered <- bool(&background.mouse_out, &background.mouse_over); + eval is_hovered((hovered) model.set_label_visible(*hovered)); + + on_mouse_over_pos <- cursor_pos.gate(&is_hovered); + eval on_mouse_over_pos((pos) model.set_label_y(pos.y)); + } + + model.set_size(Vector2::new(MARK_WIDTH + 2.0 * MARK_HOVER_AREA_WIDTH, INFINITE)); + } +} + + + +// ============= +// === Model === +// ============= + +#[derive(Clone, CloneRef, Debug)] +pub struct Model { + app: Application, + background: background::View, + label: Rc>>, + display_object: display::object::Instance, + text: Rc>>, +} + +impl component::Model for Model { + fn label() -> &'static str { + "FlameGraphMark" + } + + fn new(app: &Application, logger: &Logger) -> Self { + let scene = &app.display.default_scene; + let display_object = display::object::Instance::new(&logger); + let label = default(); + let text = default(); + + let background = background::View::new(&logger); + display_object.add_child(&background); + scene.layers.tooltip.add_exclusive(&background); + + let app = app.clone_ref(); + Model { app, background, label, display_object, text } + } +} + +impl Model { + fn set_size(&self, size: Vector2) { + self.background.size.set(size); + } + + fn set_content(&self, t: &str) { + self.text.set(t.to_owned()); + if let Some(label) = self.label.borrow().deref() { + label.set_content(t.to_owned()) + } + } + + fn set_label_visible(&self, visible: bool) { + if visible { + self.enable_label(); + } else { + self.label.take().for_each(|label| label.unset_parent()) + } + } + + fn enable_label(&self) { + let label = self.app.new_view::(); + self.add_child(&label); + + let text_layer = &self.app.display.default_scene.layers.tooltip_text; + label.add_to_scene_layer(text_layer); + label.set_default_text_size(text::Size( + BASE_TEXT_SIZE / self.app.display.default_scene.camera().zoom(), + )); + + label.set_position_x(TEXT_OFFSET_X); + label.set_content(self.label_text()); + self.label.set(label); + } + + fn set_label_y(&self, y: f32) { + if let Some(label) = self.label.deref().borrow().as_ref() { + label.set_position_y(y) + } + } + + fn label_text(&self) -> String { + self.text.borrow().clone().unwrap_or_else(|| EMPTY_LABEL.to_owned()) + } +} + +impl display::Object for Model { + fn display_object(&self) -> &display::object::Instance { + &self.display_object + } +} + + + +// ================= +// === Component === +// ================= + +#[allow(missing_docs)] +pub type Mark = ComponentView; diff --git a/lib/rust/ensogl/core/Cargo.toml b/lib/rust/ensogl/core/Cargo.toml index d2604fc828d..5b5529e3b9a 100644 --- a/lib/rust/ensogl/core/Cargo.toml +++ b/lib/rust/ensogl/core/Cargo.toml @@ -39,6 +39,7 @@ nalgebra = { version = "0.26.1" } num_enum = { version = "0.5.1" } num-traits = { version = "0.2" } rustc-hash = { version = "1.0.1" } +serde = { version = "1" } shrinkwraprs = { version = "0.3.0" } smallvec = { version = "1.0.0" } typenum = { version = "1.11.2" } diff --git a/lib/rust/ensogl/core/src/application/frp.rs b/lib/rust/ensogl/core/src/application/frp.rs index 384029e327f..055ef2e5fbd 100644 --- a/lib/rust/ensogl/core/src/application/frp.rs +++ b/lib/rust/ensogl/core/src/application/frp.rs @@ -1110,7 +1110,7 @@ macro_rules! define_endpoints_2_normalized_public { use $crate::application::command::*; $crate::frp::extend! { $output_opts network - $( $out_field <- private_output.$out_field.sampler(); )* + $( $out_field <- private_output.$out_field.profile().sampler(); )* } let mut status_map : HashMap> = default(); let mut command_map : HashMap = default(); diff --git a/lib/rust/ensogl/example/profiling-run-graph/Cargo.toml b/lib/rust/ensogl/example/profiling-run-graph/Cargo.toml index e00172ead7f..fbc280f6329 100644 --- a/lib/rust/ensogl/example/profiling-run-graph/Cargo.toml +++ b/lib/rust/ensogl/example/profiling-run-graph/Cargo.toml @@ -7,10 +7,11 @@ edition = "2021" [lib] crate-type = ["cdylib", "rlib"] - [dependencies] enso-frp = { path = "../../../frp" } enso-profiler = { path = "../../../profiler" } +enso-profiler-data = { path = "../../../profiler/data" } +enso-profiler-metadata = { path = "../../../../../app/gui/enso-profiler-metadata" } enso-profiler-flame-graph = { path = "../../../profiler/flame-graph" } enso-web = { path = "../../../web" } ensogl-core = { path = "../../core" } @@ -19,5 +20,17 @@ ensogl-hardcoded-theme = { path = "../../app/theme/hardcoded" } ensogl-text = { path = "../../component/text" } ensogl-text-msdf-sys = { path = "../../component/text/msdf-sys" } futures = "0.3" +serde = "1" wasm-bindgen = { version = "0.2.58", features = [ "nightly" ] } +wasm-bindgen-futures = "0.4" +[dependencies.web-sys] +version = "0.3" +features = [ + 'Headers', + 'Request', + 'RequestInit', + 'RequestMode', + 'Response', + 'Window', +] diff --git a/lib/rust/ensogl/example/profiling-run-graph/src/lib.rs b/lib/rust/ensogl/example/profiling-run-graph/src/lib.rs index 6767c148359..6594aeddff9 100644 --- a/lib/rust/ensogl/example/profiling-run-graph/src/lib.rs +++ b/lib/rust/ensogl/example/profiling-run-graph/src/lib.rs @@ -1,4 +1,7 @@ -//! Demo scene showing a sample flame graph. +//! Demo scene showing a sample flame graph. Can be used to display a log file, if you have one. +//! To do so, set the `PROFILER_LOG_NAME` to contain the profiling log name and it +//! will be used for rendering the visualisation. See the docs of `PROFILER_LOG_NAME` for more +//! information. // === Standard Linter Configuration === #![deny(non_ascii_idents)] @@ -9,15 +12,16 @@ #![warn(missing_docs)] #![warn(trivial_casts)] #![warn(trivial_numeric_casts)] -#![warn(unused_import_braces)] -#![warn(unused_qualifications)] +#![allow(unused_qualifications)] use ensogl_core::prelude::*; use wasm_bindgen::prelude::*; use enso_profiler as profiler; use enso_profiler::profile; +use enso_profiler_data::Profile; use enso_profiler_flame_graph as profiler_flame_graph; +use enso_profiler_metadata::Metadata; use ensogl_core::application::Application; use ensogl_core::data::color; use ensogl_core::display::navigation::navigator::Navigator; @@ -27,6 +31,12 @@ use ensogl_core::display::Scene; use ensogl_core::system::web; use ensogl_flame_graph as flame_graph; +/// Content of a profiler log, that will be rendered. If this is `None` some dummy data will be +/// generated and rendered. The file must be located in the assets subdirectory that is +/// served by the webserver, i.e. `enso/dist/content` or `app/ide-desktop/lib/content/assets`. +/// For example use `Some("profile.json"))`. +const PROFILER_LOG_NAME: Option<&str> = None; + // =================== @@ -36,7 +46,7 @@ use ensogl_flame_graph as flame_graph; /// The example entry point. #[entry_point] #[allow(dead_code)] -pub fn main() { +pub async fn main() { web::forward_panic_hook_to_console(); web::set_stack_trace_limit(); @@ -48,10 +58,21 @@ pub fn main() { init_theme(scene); - // Generate Test data - futures::executor::block_on(start_project()); + let profile = + if let Some(profile) = get_log_data().await { profile } else { create_dummy_data().await }; + + let mut measurements = profiler_flame_graph::Graph::new_hybrid_graph(&profile); + + let marks = profile + .iter_metadata() + .map(|metadata: &enso_profiler_data::Metadata| { + let position = metadata.mark.into_ms(); + let label = metadata.data.to_string(); + profiler_flame_graph::Mark { position, label } + }) + .collect(); + measurements.marks = marks; - let measurements = profiler_flame_graph::Graph::take_from_log(); let flame_graph = flame_graph::FlameGraph::from_data(measurements, app); world.add_child(&flame_graph); @@ -76,7 +97,9 @@ fn init_theme(scene: &Scene) { let theme_manager = theme::Manager::from(&scene.style_sheet); let theme = theme::Theme::new(); - theme.set("flame_graph_color", color::Rgb::new(1.0, 45.0 / 255.0, 0.0)); + theme.set("flame_graph_block_color_active", color::Lcha::blue_green(0.5, 0.8)); + theme.set("flame_graph_block_color_paused", color::Lcha::blue_green(0.8, 0.0)); + theme.set("flame_graph_mark_color", color::Lcha::blue_green(0.9, 0.1)); theme_manager.register("theme", theme); @@ -87,10 +110,50 @@ fn init_theme(scene: &Scene) { } + +// ============================ +// === Profiler Log Reading === +// ============================ + +/// Read the `PROFILER_LOG_NAME` data from a file. +async fn get_data_raw() -> Option { + use wasm_bindgen::JsCast; + + let url = &["assets/", PROFILER_LOG_NAME?].concat(); + let mut opts = web_sys::RequestInit::new(); + opts.method("GET"); + opts.mode(web_sys::RequestMode::Cors); + let request = web_sys::Request::new_with_str_and_init(url, &opts).unwrap(); + request.headers().set("Accept", "application/json").unwrap(); + let window = web_sys::window().unwrap(); + let response = window.fetch_with_request(&request); + let response = wasm_bindgen_futures::JsFuture::from(response).await.unwrap(); + assert!(response.is_instance_of::()); + let response: web_sys::Response = response.dyn_into().unwrap(); + let data = response.text().unwrap(); + let data = wasm_bindgen_futures::JsFuture::from(data).await.unwrap(); + data.as_string() +} + +async fn get_log_data() -> Option> { + let data = get_data_raw().await; + data.and_then(|data| data.parse().ok()) +} + + + // ========================== // === Dummy Computations === // ========================== +async fn create_dummy_data() -> Profile { + start_project().await; + + let log = profiler::internal::take_log(); + let profile: Result, _> = log.parse(); + profile.expect("Failed to deserialize profiling event log.") +} + /// A dummy computation that is intended to take some time based on input (where a higher number ///takes longer). fn work(n: u32) { diff --git a/lib/rust/json-rpc/Cargo.toml b/lib/rust/json-rpc/Cargo.toml index e28200f62c6..286845e8512 100644 --- a/lib/rust/json-rpc/Cargo.toml +++ b/lib/rust/json-rpc/Cargo.toml @@ -11,6 +11,7 @@ crate-type = ["cdylib", "rlib"] enso-prelude = { path = "../prelude", features = ["futures"]} enso-shapely = { path = "../shapely"} enso-web = { path = "../web" } +enso-profiler = {path = "../profiler"} futures = { version = "0.3.1" } failure = { version = "0.1.6" } serde = { version = "1.0.0", features = ["derive"] } diff --git a/lib/rust/json-rpc/src/lib.rs b/lib/rust/json-rpc/src/lib.rs index f33e6809654..5e0876fe6ee 100644 --- a/lib/rust/json-rpc/src/lib.rs +++ b/lib/rust/json-rpc/src/lib.rs @@ -31,6 +31,7 @@ pub mod transport; pub use api::RemoteMethodCall; pub use api::Result; pub use enso_prelude as prelude; +pub use enso_profiler; pub use enso_web as ensogl; pub use error::RpcError; pub use handler::Event; diff --git a/lib/rust/json-rpc/src/macros.rs b/lib/rust/json-rpc/src/macros.rs index 94274671477..89ee4620bd2 100644 --- a/lib/rust/json-rpc/src/macros.rs +++ b/lib/rust/json-rpc/src/macros.rs @@ -89,11 +89,28 @@ macro_rules! make_rpc_methods { $(fn $method<'a>(&'a self, $($param_name:&'a $param_ty),*) -> std::pin::Pin>>> { use json_rpc::api::RemoteMethodCall; + use $crate::enso_profiler as profiler; + use $crate::enso_profiler::internal::StartState; + use $crate::enso_profiler::internal::Profiler; + + let label = profiler::internal::Label(stringify!($method)); + let parent = profiler::internal::EventId::implicit(); + let now = Some(profiler::internal::Timestamp::now()); + let profiler = profiler::Task::start(parent, label, now, StartState::Active); + let phantom = std::marker::PhantomData; let input = $method_input { phantom, $($param_name:&$param_name),* }; let input_json = serde_json::to_value(input).unwrap(); let name = $method_input::NAME; let result_fut = self.handler.borrow().open_request_with_json(name,&input_json); + + profiler.pause(); + + let result_fut = result_fut.map(move |value| { + profiler.resume(); + profiler.finish(); + value + }); Box::pin(result_fut) })* diff --git a/lib/rust/profiler/data/src/lib.rs b/lib/rust/profiler/data/src/lib.rs index f6450b117cf..8113250e080 100644 --- a/lib/rust/profiler/data/src/lib.rs +++ b/lib/rust/profiler/data/src/lib.rs @@ -223,13 +223,16 @@ impl Profile { pub fn root_measurement(&self) -> &Measurement { self.measurements.last().unwrap() } -} -impl Profile { /// A virtual interval containing the top-level intervals as children. pub fn root_interval(&self) -> &ActiveInterval { self.intervals.last().unwrap() } + + /// Iterate over only the metadata stored in the profile. + pub fn iter_metadata(&self) -> impl Iterator> { + self.intervals.iter().flat_map(|interval| interval.metadata.iter()) + } } diff --git a/lib/rust/profiler/flame-graph/src/lib.rs b/lib/rust/profiler/flame-graph/src/lib.rs index 54428229950..69b56ddf4b5 100644 --- a/lib/rust/profiler/flame-graph/src/lib.rs +++ b/lib/rust/profiler/flame-graph/src/lib.rs @@ -6,15 +6,25 @@ #![deny(non_ascii_idents)] #![warn(unsafe_code)] +use crate::State::Active; +use crate::State::Paused; + use enso_profiler as profiler; use enso_profiler_data as data; - // ================== // === Block Data === // ================== +/// Indicates whether a block indicates an active interval or a paused interval. Paused intervals +/// are used to represent async tasks that are started and awaited, but that have made no progress. +#[derive(Copy, Clone, Debug)] +pub enum State { + Active, + Paused, +} + /// A `Block` contains the data required to render a single block of a frame graph. #[derive(Clone, Debug)] pub struct Block { @@ -26,6 +36,8 @@ pub struct Block { pub row: u32, /// The label to be displayed with the block. pub label: String, + /// Indicates what state this block represents (active/paused). + pub state: State, } impl Block { @@ -36,6 +48,20 @@ impl Block { } +// ================== +// === Mark Data === +// ================== + +/// A `Mark` contains the data required to render a mark that indicates a labeled point in time. +#[derive(Clone, Debug)] +pub struct Mark { + /// X coordinate of the mark. + pub position: f64, + /// The label to be displayed with the mark. + pub label: String, +} + + // ================== // === Graph Data === @@ -47,6 +73,8 @@ impl Block { pub struct Graph { /// Collection of all blocks making up the flame graph. pub blocks: Vec, + /// Collection of marks that can be shown in the flame graph. + pub marks: Vec, } impl Graph { @@ -100,7 +128,8 @@ impl<'p, Metadata> CallgraphBuilder<'p, Metadata> { builder.visit_interval(*child, 0); } let Self { blocks, .. } = builder; - Graph { blocks } + let marks = Vec::default(); + Graph { blocks, marks } } } @@ -115,7 +144,7 @@ impl<'p, Metadata> CallgraphBuilder<'p, Metadata> { return; } let label = self.profile[active.measurement].label.to_string(); - self.blocks.push(Block { start, end, label, row }); + self.blocks.push(Block { start, end, label, row, state: Active }); for child in &active.children { self.visit_interval(*child, row + 1); } @@ -146,7 +175,8 @@ impl<'p, Metadata> RungraphBuilder<'p, Metadata> { builder.visit_measurement(*child); } let Self { blocks, .. } = builder; - Graph { blocks } + let marks = Vec::default(); + Graph { blocks, marks } } } @@ -158,26 +188,66 @@ impl<'p, Metadata> RungraphBuilder<'p, Metadata> { if measurement.intervals.len() >= 2 { let row = self.next_row; self.next_row += 1; - for active in &measurement.intervals { - let active = &self.profile[*active]; - let start = active.interval.start.into_ms(); - let mut end = active.interval.end.map(|mark| mark.into_ms()).unwrap_or(f64::MAX); - // The rungraph view should illustrate every time an async task wakes up, because - // wakeups reveal the dependencies between tasks: A task is always awake before - // and after awaiting another task. - // - // These wakeups are often momentary, when there is no (or trivial) work between - // .await points. The real intervals would often be difficult or impossible to see, - // so here we enlarge short intervals to a value that will make them visible at a - // moderate zoom level. - const DURATION_FLOOR_MS: f64 = 3.0; - if end < start + DURATION_FLOOR_MS { - end = start + DURATION_FLOOR_MS; + let window_size = 2; // Current and next element. + for window in measurement.intervals.windows(window_size) { + if let [current, next] = window { + let current = &self.profile[*current]; + let next = &self.profile[*next]; + + let current_start = current.interval.start.into_ms(); + let current_end = + current.interval.end.map(|mark| mark.into_ms()).unwrap_or(f64::MAX); + let next_start = next.interval.start.into_ms(); + + let active_interval = [current_start, current_end]; + let sleep_interval = [current_end, next_start]; + + let label_active = self.profile[current.measurement].label.to_string(); + let label_sleep = + format!("{} (inactive)", self.profile[current.measurement].label); + + self.blocks.push(Block { + start: active_interval[0], + end: active_interval[1], + label: label_active, + row, + state: Active, + }); + + self.blocks.push(Block { + start: sleep_interval[0], + end: sleep_interval[1], + label: label_sleep, + row, + state: Paused, + }); } - let label = self.profile[active.measurement].label.to_string(); - self.blocks.push(Block { start, end, label, row }); } + + // Add first inactive interval. + let first = measurement.intervals.first().unwrap(); // There are at least two intervals. + let first = &self.profile[*first]; + self.blocks.push(Block { + start: measurement.created.into_ms(), + end: first.interval.start.into_ms(), + label: self.profile[first.measurement].label.to_string(), + row, + state: Paused, + }); + + // Add last active interval. + let last = measurement.intervals.last().unwrap(); // There are at least two intervals. + let last = &self.profile[*last]; + self.blocks.push(Block { + start: last.interval.start.into_ms(), + end: last.interval.end.map(|end| end.into_ms()).unwrap_or(f64::INFINITY), + label: self.profile[last.measurement].label.to_string(), + row, + state: Active, + }); } + + // Recourse through children. for child in &measurement.children { self.visit_measurement(*child); } @@ -201,7 +271,8 @@ fn new_hybrid_graph(profile: &data::Profile) -> Graph { callgraph.visit_interval(*child, next_row); } let CallgraphBuilder { blocks, .. } = callgraph; - Graph { blocks } + let marks = Vec::default(); + Graph { blocks, marks } } @@ -231,7 +302,8 @@ impl From for Graph { grapher.visit_frame(frame, label.to_string(), 0); } let FlamegraphGrapher { blocks, .. } = grapher; - Self { blocks } + let marks = Vec::default(); + Self { blocks, marks } } } @@ -246,7 +318,7 @@ impl FlamegraphGrapher { fn visit_frame(&mut self, frame: &data::aggregate::Frame, label: String, row: u32) { let start = self.time; let end = self.time + frame.total_duration(); - self.blocks.push(Block { start, end, label, row }); + self.blocks.push(Block { start, end, label, row, state: State::Active }); for (label, frame) in &frame.children { self.visit_frame(frame, label.to_string(), row + 1); }