Drop method exported from WASM + removing leaks. (#6365)

Fixes #6317

The `drop` method is available in the WASM object. This can be tested by typing `ensoglApp.wasm.drop()` in the dev console - all objects should be removed and all connections closed.

# Important Notes
* This PR fixed serveral leaks by this occasion
* A new tool for tracking leaks was added to prelude's `debug` module.
This commit is contained in:
Adam Obuchowicz 2023-04-25 17:38:29 +02:00 committed by GitHub
parent 63de18e367
commit 952de24e79
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 415 additions and 280 deletions

View File

@ -64,6 +64,12 @@ extern crate core;
use prelude::*;
use wasm_bindgen::prelude::*;
mod profile_workflow;
#[cfg(test)]
mod tests;
// ==============
// === Export ===
@ -82,13 +88,9 @@ pub mod transport;
pub use crate::ide::*;
pub use engine_protocol;
use enso_executor::web::EventLoopExecutor;
pub use ide_view as view;
#[cfg(test)]
mod tests;
/// Common types that should be visible across the whole IDE crate.
pub mod prelude {
pub use ast::prelude::*;
@ -126,6 +128,12 @@ pub mod prelude {
pub use wasm_bindgen_test::wasm_bindgen_test_configure;
}
// ====================
// === Entry Points ===
// ====================
// These imports are required to have all entry points (such as examples) and `before_main`
// functions (such as the dynamic-asset loader), available in the IDE.
#[allow(unused_imports)]
@ -136,13 +144,23 @@ mod imported_for_entry_points {
}
#[allow(unused_imports)]
use imported_for_entry_points::*;
mod profile_workflow;
// ===================
// === Entry Point ===
// ===================
// ====================
// === Global State ===
// ====================
thread_local! {
static EXECUTOR: RefCell<Option<EventLoopExecutor>> = default();
static IDE: RefCell<Option<Result<Ide, FailedIde>>> = default();
}
// =======================
// === IDE Entry Point ===
// =======================
/// IDE startup function.
#[entry_point(ide)]
@ -159,16 +177,43 @@ pub fn main() {
"debug_mode_is_active",
analytics::AnonymousData(debug_mode),
);
let config =
crate::config::Startup::from_web_arguments().expect("Failed to read configuration");
let executor = crate::executor::setup_global_executor();
let initializer = crate::ide::initializer::Initializer::new(config);
let config = config::Startup::from_web_arguments().expect("Failed to read configuration");
let executor = executor::setup_global_executor();
EXECUTOR.with(move |global_executor| global_executor.replace(Some(executor)));
let initializer = Initializer::new(config);
executor::global::spawn(async move {
let ide = initializer.start().await;
ensogl::system::web::document
.get_element_by_id("loader")
.map(|t| t.parent_node().map(|p| p.remove_child(&t).unwrap()));
std::mem::forget(ide);
IDE.with(move |global_ide| global_ide.replace(Some(ide)));
});
std::mem::forget(executor);
}
// ================
// === IDE Drop ===
// ================
/// Drop all structure created so far.
///
/// All connections will be closed and all visuals will be removed.
#[wasm_bindgen]
pub fn drop() {
let ide = IDE.with(RefCell::take);
if let Some(Ok(ide)) = &ide {
//TODO[ao] #6420 We should not do this, but somehow the `dom` field in the scene is
// leaking.
ide.ensogl_app.display.default_scene.dom.root.remove();
}
mem::drop(ide);
EXECUTOR.with(RefCell::take);
leak_detector::TRACKED_OBJECTS.with(|objects| {
let objects = objects.borrow();
if !objects.is_empty() {
error!("Tracked objects leaked after dropping entire application!");
error!("Leaked objects: {objects:#?}");
}
})
}

View File

@ -167,7 +167,6 @@ impl Presenter {
root_frp.switch_view_to_project <+ welcome_view_frp.open_project.constant(());
}
Self { model, network }.init()
}

View File

@ -417,7 +417,7 @@ impl display::Object for BreadcrumbsModel {
#[derive(Debug, Clone, CloneRef)]
#[allow(missing_docs)]
pub struct Breadcrumbs {
model: Rc<BreadcrumbsModel>,
model: BreadcrumbsModel,
frp: Frp,
}
@ -426,7 +426,7 @@ impl Breadcrumbs {
pub fn new(app: Application) -> Self {
let scene = app.display.default_scene.clone_ref();
let frp = Frp::new();
let model = Rc::new(BreadcrumbsModel::new(app, &frp));
let model = BreadcrumbsModel::new(app, &frp);
let network = &frp.network;
// === Breadcrumb selection ===
@ -435,9 +435,8 @@ impl Breadcrumbs {
// === Selecting ===
_breadcrumb_selection <- frp.select_breadcrumb.map(f!([model,frp](index)
frp.source.breadcrumb_select.emit(model.select_breadcrumb(*index));
));
frp.source.breadcrumb_select <+
frp.select_breadcrumb.map(f!((index) model.select_breadcrumb(*index)));
// === Stack Operations ===
@ -489,14 +488,14 @@ impl Breadcrumbs {
// === User Interaction ===
eval_ model.project_name.frp.output.mouse_down(frp.select_breadcrumb.emit(0));
eval_ frp.cancel_project_name_editing(model.project_name.frp.cancel_editing.emit(()));
eval_ frp.outside_press(model.project_name.frp.outside_press.emit(()));
frp.select_breadcrumb <+ model.project_name.frp.output.mouse_down.constant(0);
model.project_name.frp.cancel_editing <+ frp.cancel_project_name_editing;
model.project_name.frp.outside_press <+ frp.outside_press;
popped_count <= frp.output.breadcrumb_select.map(|selected| (0..selected.0).collect_vec());
local_calls <= frp.output.breadcrumb_select.map(|selected| selected.1.clone());
eval_ popped_count(frp.source.breadcrumb_pop.emit(()));
eval local_calls((local_call) frp.source.breadcrumb_push.emit(local_call));
frp.source.breadcrumb_pop <+ popped_count.constant(());
frp.source.breadcrumb_push <+ local_calls;
// === Select ===
@ -511,8 +510,8 @@ impl Breadcrumbs {
popped_count <= selected.map(|selected| (0..selected.0).collect_vec());
local_calls <= selected.map(|selected| selected.1.clone());
eval_ popped_count(frp.input.debug_pop_breadcrumb.emit(()));
eval local_calls((local_call) frp.input.debug_push_breadcrumb.emit(local_call));
frp.input.debug_pop_breadcrumb <+ popped_count.constant(());
frp.input.debug_push_breadcrumb <+ local_calls;
// === Relayout ===

View File

@ -8,7 +8,7 @@
use ensogl::prelude::*;
use crate::application::command::FrpNetworkProvider;
use crate::GraphEditorModelWithNetwork;
use crate::GraphEditorModel;
use crate::NodeId;
use enso_frp as frp;
@ -33,11 +33,7 @@ const ANIMATION_LENGTH_COEFFIENT: f32 = 15.0;
/// Initialize edited node growth/shrink animator. It would handle scene layer change for the edited
/// node as well.
pub fn initialize_edited_node_animator(
model: &GraphEditorModelWithNetwork,
frp: &crate::Frp,
scene: &Scene,
) {
pub fn initialize_edited_node_animator(model: &GraphEditorModel, frp: &crate::Frp, scene: &Scene) {
let network = &frp.network();
let out = &frp.output;
let searcher_cam = scene.layers.node_searcher.camera();
@ -112,7 +108,7 @@ pub fn initialize_edited_node_animator(
// === Helpers ===
impl GraphEditorModelWithNetwork {
impl GraphEditorModel {
/// Move node to the `edited_node` scene layer, so that it is rendered by the separate camera.
#[profile(Debug)]
fn move_node_to_edited_node_layer(&self, node_id: NodeId) {

View File

@ -48,7 +48,6 @@ use crate::component::node;
use crate::component::type_coloring;
use crate::component::visualization;
use crate::component::visualization::instance::PreprocessorConfiguration;
use crate::component::visualization::MockDataGenerator3D;
use crate::data::enso;
pub use crate::node::profiling::Status as NodeProfilingStatus;
@ -125,6 +124,7 @@ fn traffic_lights_gap_width() -> f32 {
}
// =================
// === SharedVec ===
// =================
@ -774,7 +774,7 @@ ensogl::define_endpoints_2! {
impl FrpNetworkProvider for GraphEditor {
fn network(&self) -> &frp::Network {
&self.model.network
&self.frp.network
}
}
@ -1401,11 +1401,11 @@ pub fn crumbs_overlap(src: &[span_tree::Crumb], tgt: &[span_tree::Crumb]) -> boo
// === GraphEditorModelWithNetwork ===
// ===================================
#[derive(Debug, Clone, CloneRef)]
#[derive(Clone, CloneRef, Debug)]
#[allow(missing_docs)] // FIXME[everyone] Public-facing API should be documented.
pub struct GraphEditorModelWithNetwork {
pub model: GraphEditorModel,
pub network: frp::Network,
pub network: frp::WeakNetwork,
}
impl Deref for GraphEditorModelWithNetwork {
@ -1419,7 +1419,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().downgrade();
let model = GraphEditorModel::new(app, cursor, frp);
Self { model, network }
}
@ -1454,11 +1454,12 @@ impl GraphEditorModelWithNetwork {
let edge_id = edge.id();
self.add_child(&edge);
self.edges.insert(edge.clone_ref());
let network = &self.network;
frp::extend! { network
eval_ edge.view.frp.shape_events.mouse_down_primary (edge_click.emit(edge_id));
eval_ edge.view.frp.shape_events.mouse_over (edge_over.emit(edge_id));
eval_ edge.view.frp.shape_events.mouse_out (edge_out.emit(edge_id));
if let Some(network) = &self.network.upgrade_or_warn() {
frp::extend! { network
eval_ edge.view.frp.shape_events.mouse_down_primary (edge_click.emit(edge_id));
eval_ edge.view.frp.shape_events.mouse_over (edge_over.emit(edge_id));
eval_ edge.view.frp.shape_events.mouse_out (edge_out.emit(edge_id));
}
}
edge_id
}
@ -1473,7 +1474,7 @@ impl GraphEditorModelWithNetwork {
let first_detached = self.edges.detached_target.is_empty();
self.edges.detached_target.insert(edge_id);
if first_detached {
self.frp.private.output.on_some_edges_targets_unset.emit(());
self.frp.output.on_some_edges_targets_unset.emit(());
}
edge_id
}
@ -1488,7 +1489,7 @@ impl GraphEditorModelWithNetwork {
let first_detached = self.edges.detached_source.is_empty();
self.edges.detached_source.insert(edge_id);
if first_detached {
self.frp.private.output.on_some_edges_sources_unset.emit(());
self.frp.output.on_some_edges_sources_unset.emit(());
}
edge_id
}
@ -1571,198 +1572,202 @@ impl GraphEditorModelWithNetwork {
let model = &self.model;
let NodeCreationContext { pointer_style, output_press, input_press, output } = ctx;
frp::new_bridge_network! { [self.network, node_network] graph_node_bridge
eval_ node.background_press(touch.nodes.down.emit(node_id));
if let Some(network) = self.network.upgrade_or_warn() {
frp::new_bridge_network! { [network, node_network] graph_node_bridge
eval_ node.background_press(touch.nodes.down.emit(node_id));
hovered <- node.output.hover.map (move |t| Some(Switch::new(node_id,*t)));
output.node_hovered <+ hovered;
hovered <- node.output.hover.map (move |t| Some(Switch::new(node_id,*t)));
output.node_hovered <+ hovered;
eval node.comment ([model](comment)
model.frp.private.output.node_comment_set.emit((node_id,comment.clone()))
);
eval node.comment ([model](comment)
model.frp.output.node_comment_set.emit((node_id,comment.clone()))
);
node.set_output_expression_visibility <+ self.frp.nodes_labels_visible;
node.set_output_expression_visibility <+ self.frp.output.nodes_labels_visible;
pointer_style <+ node_model.input.frp.pointer_style;
pointer_style <+ node_model.input.frp.pointer_style;
eval node_model.output.frp.on_port_press ([output_press](crumbs){
let target = EdgeEndpoint::new(node_id,crumbs.clone());
output_press.emit(target);
});
eval node_model.input.frp.on_port_press ([input_press](crumbs)
let target = EdgeEndpoint::new(node_id,crumbs.clone());
input_press.emit(target);
);
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.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.private.output.hover_node_output.emit(output);
});
let neutral_color = model.styles_frp.get_color(theme::code::types::any::selection);
_eval <- all_with(&node_model.input.frp.on_port_type_change,&neutral_color,
f!(((crumbs,_),neutral_color)
model.with_input_edge_id(node_id,crumbs,|id|
model.refresh_edge_color(id,neutral_color.into())
)
));
_eval <- all_with(&node_model.input.frp.on_port_type_change,&neutral_color,
f!(((crumbs,_),neutral_color)
model.with_output_edge_id(node_id,crumbs,|id|
model.refresh_edge_color(id,neutral_color.into())
)
));
let is_editing = &node_model.input.frp.editing;
expression_change_temporary <- node.on_expression_modified.gate(is_editing);
expression_change_permanent <- node.on_expression_modified.gate_not(is_editing);
temporary_expression <- expression_change_temporary.map2(
&node_model.input.set_expression,
move |(crumbs, code), expr| expr.code_with_replaced_span(crumbs, code)
);
eval temporary_expression([model] (code) {
model.frp.private.output.node_expression_set.emit((node_id, code));
});
eval expression_change_permanent([model]((crumbs,code)) {
let args = (node_id, crumbs.clone(), code.clone());
model.frp.private.output.node_expression_span_set.emit(args)
});
eval node.requested_widgets([model]((call_id, target_id)) {
let args = (node_id, *call_id, *target_id);
model.frp.private.output.widgets_requested.emit(args)
});
let node_expression_edit = node.model().input.expression_edit.clone_ref();
model.frp.private.output.node_expression_edited <+ node_expression_edit.map(
move |(expr, selection)| (node_id, expr.clone_ref(), selection.clone())
);
model.frp.private.output.request_import <+ node.request_import;
// === Actions ===
model.frp.private.output.node_action_context_switch <+ node.view.context_switch.map(
f!([] (active) (node_id, *active))
);
eval node.view.freeze ((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,model](is_skipped) {
model.frp.private.output.node_action_skip.emit((node_id,*is_skipped));
set_node_disabled.emit(is_skipped);
});
// === Visualizations ===
visualization_shown <- node.visualization_visible.gate(&node.visualization_visible);
visualization_hidden <- node.visualization_visible.gate_not(&node.visualization_visible);
let vis_is_selected = node_model.visualization.frp.is_selected.clone_ref();
selected <- vis_is_selected.on_true();
deselected <- vis_is_selected.on_false();
output.on_visualization_select <+ selected.constant(Switch::On(node_id));
output.on_visualization_select <+ deselected.constant(Switch::Off(node_id));
preprocessor_changed <-
node_model.visualization.frp.preprocessor.map(move |preprocessor| {
(node_id,preprocessor.clone())
eval node_model.output.frp.on_port_press ([output_press](crumbs){
let target = EdgeEndpoint::new(node_id,crumbs.clone());
output_press.emit(target);
});
output.visualization_preprocessor_changed <+ preprocessor_changed.gate(&node.visualization_visible);
eval node_model.input.frp.on_port_press ([input_press](crumbs)
let target = EdgeEndpoint::new(node_id,crumbs.clone());
input_press.emit(target);
);
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.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.output.hover_node_output.emit(output);
});
let neutral_color = model.styles_frp.get_color(theme::code::types::any::selection);
_eval <- all_with(&node_model.input.frp.on_port_type_change,&neutral_color,
f!(((crumbs,_),neutral_color)
model.with_input_edge_id(node_id,crumbs,|id|
model.refresh_edge_color(id,neutral_color.into())
)
));
_eval <- all_with(&node_model.input.frp.on_port_type_change,&neutral_color,
f!(((crumbs,_),neutral_color)
model.with_output_edge_id(node_id,crumbs,|id|
model.refresh_edge_color(id,neutral_color.into())
)
));
let is_editing = &node_model.input.frp.editing;
expression_change_temporary <- node.on_expression_modified.gate(is_editing);
expression_change_permanent <- node.on_expression_modified.gate_not(is_editing);
temporary_expression <- expression_change_temporary.map2(
&node_model.input.set_expression,
move |(crumbs, code), expr| expr.code_with_replaced_span(crumbs, code)
);
eval temporary_expression([model] (code) {
model.frp.output.node_expression_set.emit((node_id, code));
});
eval expression_change_permanent([model]((crumbs,code)) {
let args = (node_id, crumbs.clone(), code.clone());
model.frp.output.node_expression_span_set.emit(args)
});
eval node.requested_widgets([model]((call_id, target_id)) {
let args = (node_id, *call_id, *target_id);
model.frp.output.widgets_requested.emit(args)
});
let node_expression_edit = node.model().input.expression_edit.clone_ref();
model.frp.output.node_expression_edited <+ node_expression_edit.map(
move |(expr, selection)| (node_id, expr.clone_ref(), selection.clone())
);
model.frp.output.request_import <+ node.request_import;
metadata <- any(...);
metadata <+ node_model.visualization.frp.preprocessor.map(visualization::Metadata::new);
// === Actions ===
// Ensure the graph editor knows about internal changes to the visualisation. If the
// visualisation changes that should indicate that the old one has been disabled and a
// new one has been enabled.
// TODO: Create a better API for updating the controller about visualisation changes
// (see #896)
output.visualization_hidden <+ visualization_hidden.constant(node_id);
output.visualization_shown <+
visualization_shown.map2(&metadata,move |_,metadata| (node_id,metadata.clone()));
model.frp.output.node_action_context_switch <+ node.view.context_switch.map(
f!([] (active) (node_id, *active))
);
eval node.view.freeze ((is_frozen) {
model.frp.output.node_action_freeze.emit((node_id,*is_frozen));
});
let set_node_disabled = &node.set_disabled;
eval node.view.skip ([set_node_disabled,model](is_skipped) {
model.frp.output.node_action_skip.emit((node_id,*is_skipped));
set_node_disabled.emit(is_skipped);
});
init <- source::<()>();
enabled_visualization_path <- init.all_with3(
&node.visualization_enabled, &node.visualization_path,
move |_init, is_enabled, path| (node_id, is_enabled.and_option(path.clone()))
);
output.enabled_visualization_path <+ enabled_visualization_path;
// === Visualizations ===
visualization_shown <- node.visualization_visible.gate(&node.visualization_visible);
visualization_hidden <- node.visualization_visible.gate_not(&node.visualization_visible);
let vis_is_selected = node_model.visualization.frp.is_selected.clone_ref();
selected <- vis_is_selected.on_true();
deselected <- vis_is_selected.on_false();
output.on_visualization_select <+ selected.constant(Switch::On(node_id));
output.on_visualization_select <+ deselected.constant(Switch::Off(node_id));
preprocessor_changed <-
node_model.visualization.frp.preprocessor.map(move |preprocessor| {
(node_id,preprocessor.clone())
});
output.visualization_preprocessor_changed <+ preprocessor_changed.gate(&node.visualization_visible);
// === View Mode ===
metadata <- any(...);
metadata <+ node_model.visualization.frp.preprocessor.map(visualization::Metadata::new);
node.set_view_mode <+ self.model.frp.view_mode;
// Ensure the graph editor knows about internal changes to the visualisation. If the
// visualisation changes that should indicate that the old one has been disabled and a
// new one has been enabled.
// TODO: Create a better API for updating the controller about visualisation changes
// (see #896)
output.visualization_hidden <+ visualization_hidden.constant(node_id);
output.visualization_shown <+
visualization_shown.map2(&metadata,move |_,metadata| (node_id,metadata.clone()));
// === Read-only mode ===
node.set_read_only <+ self.model.frp.set_read_only;
init <- source::<()>();
enabled_visualization_path <- init.all_with3(
&node.visualization_enabled, &node.visualization_path,
move |_init, is_enabled, path| (node_id, is_enabled.and_option(path.clone()))
);
output.enabled_visualization_path <+ enabled_visualization_path;
// === Profiling ===
// === View Mode ===
let profiling_min_duration = &self.model.profiling_statuses.min_duration;
node.set_profiling_min_global_duration <+ self.model.profiling_statuses.min_duration;
node.set_profiling_min_global_duration(profiling_min_duration.value());
let profiling_max_duration = &self.model.profiling_statuses.max_duration;
node.set_profiling_max_global_duration <+ self.model.profiling_statuses.max_duration;
node.set_profiling_max_global_duration(profiling_max_duration.value());
node.set_view_mode <+ self.model.frp.output.view_mode;
// === Execution Environment ===
// === Read-only mode ===
node.set_execution_environment <+ self.model.frp.set_execution_environment;
node.set_read_only <+ self.model.frp.input.set_read_only;
// === Profiling ===
let profiling_min_duration = &self.model.profiling_statuses.min_duration;
node.set_profiling_min_global_duration <+ self.model.profiling_statuses.min_duration;
node.set_profiling_min_global_duration(profiling_min_duration.value());
let profiling_max_duration = &self.model.profiling_statuses.max_duration;
node.set_profiling_max_global_duration <+ self.model.profiling_statuses.max_duration;
node.set_profiling_max_global_duration(profiling_max_duration.value());
// === Execution Environment ===
node.set_execution_environment <+ self.model.frp.input.set_execution_environment;
}
// === Panning camera to created node ===
// Node position and bounding box are not available immediately after the node is
// created, but only after the Node's display object is updated. Therefore,
// in order to pan the camera to the bounding box of a newly created node,
// we need to wait until: 1. the position of the newly created node becomes
// updated, and then 2. the bounding box of the node becomes updated.
// When the sequence is detected, and if the node is being edited, we pan the camera to
// it. Regardless whether the node is being edited, we drop the network, as
// we don't want to pan the camera for any later updates of the bounding
// box.
let pan_network = frp::Network::new("network_for_camera_pan_to_new_node");
let pan_network_container = RefCell::new(Some(pan_network.clone()));
frp::new_bridge_network! { [network, node_network, pan_network] graph_node_pan_bridge
pos_updated <- node.output.position.constant(true);
bbox_updated_after_pos_updated <- node.output.bounding_box.gate(&pos_updated);
let node_being_edited = &self.frp.output.node_being_edited;
_eval <- bbox_updated_after_pos_updated.map2(node_being_edited, f!([model](_, node) {
pan_network_container.replace(None);
if *node == Some(node_id) {
model.pan_camera_to_node(node_id);
}
}));
}
node.set_view_mode(self.model.frp_public.output.view_mode.value());
let initial_metadata = visualization::Metadata {
preprocessor: node_model.visualization.frp.preprocessor.value(),
};
metadata.emit(initial_metadata);
init.emit(());
}
// === Panning camera to created node ===
// Node position and bounding box are not available immediately after the node is created,
// but only after the Node's display object is updated. Therefore, in order to pan the
// camera to the bounding box of a newly created node, we need to wait until:
// 1. the position of the newly created node becomes updated, and then
// 2. the bounding box of the node becomes updated.
// When the sequence is detected, and if the node is being edited, we pan the camera to it.
// Regardless whether the node is being edited, we drop the network, as we don't want to
// pan the camera for any later updates of the bounding box.
let pan_network = frp::Network::new("network_for_camera_pan_to_new_node");
let pan_network_container = RefCell::new(Some(pan_network.clone()));
frp::new_bridge_network! { [self.network, node_network, pan_network] graph_node_pan_bridge
pos_updated <- node.output.position.constant(true);
bbox_updated_after_pos_updated <- node.output.bounding_box.gate(&pos_updated);
let node_being_edited = &self.frp.node_being_edited;
_eval <- bbox_updated_after_pos_updated.map2(node_being_edited, f!([model](_, node) {
pan_network_container.replace(None);
if *node == Some(node_id) {
model.pan_camera_to_node(node_id);
}
}));
}
node.set_view_mode(self.model.frp.view_mode.value());
let initial_metadata = visualization::Metadata {
preprocessor: node_model.visualization.frp.preprocessor.value(),
};
metadata.emit(initial_metadata);
init.emit(());
self.nodes.insert(node_id, node.clone_ref());
node
}
@ -1790,7 +1795,8 @@ pub struct GraphEditorModel {
tooltip: Tooltip,
touch_state: TouchState,
visualisations: Visualisations,
frp: Frp,
frp: api::Private,
frp_public: api::Public,
profiling_statuses: profiling::Statuses,
profiling_button: component::profiling::Button,
styles_frp: StyleWatchFrp,
@ -1816,7 +1822,6 @@ impl GraphEditorModel {
let execution_mode_selector = execution_mode_selector::ExecutionModeSelector::new(app);
let app = app.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();
@ -1826,7 +1831,7 @@ impl GraphEditorModel {
ensogl_drop_manager::Manager::new(&scene.dom.root.clone_ref().into(), scene);
let styles_frp = StyleWatchFrp::new(&scene.style_sheet);
let selection_controller = selection::Controller::new(
&frp,
frp,
&app.cursor,
&scene.mouse.frp_deprecated,
&touch_state,
@ -1845,11 +1850,12 @@ impl GraphEditorModel {
tooltip,
touch_state,
visualisations,
frp,
navigator,
profiling_statuses,
profiling_button,
add_node_button,
frp: frp.private.clone_ref(),
frp_public: frp.public.clone_ref(),
styles_frp,
selection_controller,
execution_mode_selector,
@ -1886,8 +1892,8 @@ impl GraphEditorModel {
impl GraphEditorModel {
/// Create a new node and return a unique identifier.
pub fn add_node(&self) -> NodeId {
self.frp.add_node.emit(());
let (node_id, _, _) = self.frp.node_added.value();
self.frp_public.input.add_node.emit(());
let (node_id, _, _) = self.frp_public.output.node_added.value();
node_id
}
@ -1900,7 +1906,7 @@ impl GraphEditorModel {
/// Create a new node and place it at `pos`.
pub fn add_node_at(&self, pos: Vector2) -> NodeId {
let node_id = self.add_node();
self.frp.set_node_position((node_id, pos));
self.frp_public.input.set_node_position.emit((node_id, pos));
node_id
}
}
@ -1988,7 +1994,7 @@ impl GraphEditorModel {
let node_id = node_id.into();
self.nodes.remove(&node_id);
self.nodes.selected.remove_item(&node_id);
self.frp.private.output.on_visualization_select.emit(Switch::Off(node_id));
self.frp.output.on_visualization_select.emit(Switch::Off(node_id));
}
fn node_in_edges(&self, node_id: impl Into<NodeId>) -> Vec<EdgeId> {
@ -2108,7 +2114,7 @@ impl GraphEditorModel {
self.refresh_edge_position(edge_id);
self.refresh_edge_source_size(edge_id);
if first_detached {
self.frp.private.output.on_some_edges_sources_unset.emit(());
self.frp.output.on_some_edges_sources_unset.emit(());
}
}
}
@ -2125,7 +2131,7 @@ impl GraphEditorModel {
self.edges.detached_target.remove(&edge_id);
let all_attached = self.edges.detached_target.is_empty();
if all_attached {
self.frp.private.output.on_all_edges_targets_set.emit(());
self.frp.output.on_all_edges_targets_set.emit(());
}
edge.view.frp.target_attached.emit(true);
@ -2145,7 +2151,7 @@ impl GraphEditorModel {
edge.view.frp.target_attached.emit(false);
self.refresh_edge_position(edge_id);
if first_detached {
self.frp.private.output.on_some_edges_targets_unset.emit(());
self.frp.output.on_some_edges_targets_unset.emit(());
}
};
}
@ -2186,10 +2192,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.private.output.on_all_edges_targets_set.emit(());
self.frp.output.on_all_edges_targets_set.emit(());
}
if no_detached_sources {
self.frp.private.output.on_all_edges_sources_set.emit(());
self.frp.output.on_all_edges_sources_set.emit(());
}
}
@ -2487,7 +2493,7 @@ impl GraphEditorModel {
}
fn edge_hover_type(&self) -> Option<Type> {
let hover_tgt = self.frp.hover_node_input.value();
let hover_tgt = self.frp_public.output.hover_node_input.value();
hover_tgt.and_then(|tgt| {
self.with_node(tgt.node_id, |node| node.model().input.port_type(&tgt.port)).flatten()
})
@ -2510,7 +2516,7 @@ impl GraphEditorModel {
// FIXME : StyleWatch is unsuitable here, as it was designed as an internal tool for shape
// system (#795)
let styles = StyleWatch::new(&self.scene().style_sheet);
match self.frp.view_mode.value() {
match self.frp_public.output.view_mode.value() {
view::Mode::Normal => {
let edge_type = self
.edge_hover_type()
@ -2769,7 +2775,7 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
let network = frp.network();
let nodes = &model.nodes;
let edges = &model.edges;
let inputs = &model.frp;
let inputs = &frp.private.input;
let mouse = &scene.mouse.frp_deprecated;
let touch = &model.touch_state;
let vis_registry = &model.vis_registry;
@ -2788,7 +2794,7 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
// Drop the currently dragged edge if read-only mode is enabled.
read_only_enabled <- inputs.set_read_only.on_true();
inputs.drop_dragged_edge <+ read_only_enabled;
frp.drop_dragged_edge <+ read_only_enabled;
}
@ -2842,7 +2848,7 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
// Go level down on node double click.
enter_on_node <= target_to_enter.map(|target| target.is_symbol().as_some(()));
output_port_is_hovered <- inputs.hover_node_output.map(Option::is_some);
output_port_is_hovered <- frp.output.hover_node_output.map(Option::is_some);
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();
@ -2918,12 +2924,12 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
on_connect_follow_mode <- any(on_output_connect_follow_mode,on_input_connect_follow_mode);
connect_drag_mode <- any(on_connect_drag_mode,on_connect_follow_mode);
on_detached_edge <- any(&inputs.on_some_edges_targets_unset,&inputs.on_some_edges_sources_unset);
on_detached_edge <- any(&frp.private.output.on_some_edges_targets_unset,&frp.private.output.on_some_edges_sources_unset);
has_detached_edge <- bool(&out.on_all_edges_endpoints_set,&on_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));
frp.press_node_input <+ node_input_touch.down;
frp.press_node_output <+ node_output_touch.down;
}
@ -2987,10 +2993,10 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
output_down <- node_output_touch.down.constant(());
input_down <- node_input_touch.down.constant(());
has_detached_edge_on_output_down <- has_detached_edge.sample(&inputs.hover_node_output);
has_detached_edge_on_output_down <- has_detached_edge.sample(&frp.output.hover_node_output);
port_input_mouse_up <- inputs.hover_node_input.sample(&mouse.up_primary).unwrap();
port_output_mouse_up <- inputs.hover_node_output.sample(&mouse.up_primary).unwrap();
port_input_mouse_up <- frp.output.hover_node_input.sample(&mouse.up_primary).unwrap();
port_output_mouse_up <- frp.output.hover_node_output.sample(&mouse.up_primary).unwrap();
attach_all_edge_inputs <- any (port_input_mouse_up, inputs.press_node_input, inputs.set_detached_edge_targets);
attach_all_edge_outputs <- any (port_output_mouse_up, inputs.press_node_output, inputs.set_detached_edge_sources);
@ -2999,7 +3005,7 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
create_edge_from_input <- node_input_touch.down.map(|value| value.clone()).gate_not(&inputs.set_read_only);
on_new_edge <- any(&output_down,&input_down);
let selection_mode = selection::get_mode(network,inputs);
let selection_mode = selection::get_mode(network,&frp);
keep_selection <- selection_mode.map(|t| *t != selection::Mode::Normal);
deselect_edges <- on_new_edge.gate_not(&keep_selection);
eval_ deselect_edges ( model.clear_all_detached_edges() );
@ -3077,7 +3083,7 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
frp::extend! { network
node_added_with_button <- model.add_node_button.clicked.gate_not(&inputs.set_read_only);
input_start_node_creation_from_port <- inputs.hover_node_output.sample(
input_start_node_creation_from_port <- out.hover_node_output.sample(
&inputs.start_node_creation_from_port);
start_node_creation_from_port <- input_start_node_creation_from_port.filter_map(
|v| v.clone());
@ -3220,13 +3226,12 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
// === Remove Node ===
frp::extend! { network
all_nodes <= inputs.remove_all_nodes . map(f_!(model.all_nodes()));
selected_nodes <= inputs.remove_selected_nodes . map(f_!(model.nodes.all_selected()));
nodes_to_remove <- any (all_nodes, selected_nodes);
frp.public.input.remove_all_node_edges <+ nodes_to_remove;
all_nodes <= inputs.remove_all_nodes . map(f_!(model.all_nodes()));
selected_nodes <= inputs.remove_selected_nodes . map(f_!(model.nodes.all_selected()));
nodes_to_remove <- any (all_nodes, selected_nodes);
eval nodes_to_remove ((node_id) inputs.remove_all_node_edges.emit(node_id));
out.node_removed <+ nodes_to_remove;
out.node_removed <+ nodes_to_remove;
}
@ -3406,13 +3411,13 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
frp::extend! { network
detached_edge <- any(&inputs.on_some_edges_targets_unset,&inputs.on_some_edges_sources_unset);
detached_edge <- any(&out.on_some_edges_targets_unset,&out.on_some_edges_sources_unset);
update_edge <- any(detached_edge,on_new_edge_source,on_new_edge_target);
cursor_pos_on_update <- cursor_pos_in_scene.sample(&update_edge);
edge_refresh_cursor_pos <- any(cursor_pos_on_update,cursor_pos_in_scene);
is_hovering_output <- inputs.hover_node_output.map(|target| target.is_some()).sampler();
hover_node <- inputs.hover_node_output.unwrap();
is_hovering_output <- out.hover_node_output.map(|target| target.is_some()).sampler();
hover_node <- out.hover_node_output.unwrap();
edge_refresh_on_node_hover <- all(edge_refresh_cursor_pos,hover_node).gate(&is_hovering_output);
edge_refresh_cursor_pos_no_hover <- edge_refresh_cursor_pos.gate_not(&is_hovering_output);
@ -3508,17 +3513,6 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
// === Vis Update Data ===
frp::extend! { network
// TODO remove this once real data is available.
let sample_data_generator = MockDataGenerator3D::default();
def _set_dumy_data = inputs.debug_set_test_visualization_data_for_selected_node.map(f!([nodes,inputs](_) {
for node_id in &*nodes.selected.raw.borrow() {
let data = Rc::new(sample_data_generator.generate_data()); // FIXME: why rc?
let content = serde_json::to_value(data).unwrap();
let data = visualization::Data::from(content);
inputs.set_visualization_data.emit((*node_id,data));
}
}));
eval inputs.set_visualization_data ([nodes]((node_id,data)) {
if let Some(node) = nodes.get_cloned(node_id) {
node.model().visualization.frp.set_data.emit(data);
@ -3633,9 +3627,9 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
node_to_enter <= inputs.enter_selected_node.map(f_!(model.nodes.last_selected()));
out.node_entered <+ node_to_enter;
removed_edges_on_enter <= out.node_entered.map(f_!(model.model.clear_all_detached_edges()));
removed_edges_on_enter <= out.node_entered.map(f_!(model.clear_all_detached_edges()));
out.node_exited <+ inputs.exit_node;
removed_edges_on_exit <= out.node_exited.map(f_!(model.model.clear_all_detached_edges()));
removed_edges_on_exit <= out.node_exited.map(f_!(model.clear_all_detached_edges()));
out.on_edge_drop <+ any(removed_edges_on_enter,removed_edges_on_exit);
@ -3677,7 +3671,7 @@ fn new_graph_editor(app: &Application) -> GraphEditor {
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);
let neutral_color = model.styles_frp.get_color(theme::code::types::any::selection);
eval out.on_edge_source_set ([model,neutral_color]((id, _))
model.refresh_edge_color(*id,neutral_color.value().into()));
eval out.on_edge_target_set ([model,neutral_color]((id, _))

View File

@ -117,7 +117,7 @@ pub fn below_line_and_left_aligned(
line_y: f32,
align_x: f32,
) -> Vector2 {
let y_gap = graph_editor.frp.default_y_gap_between_nodes.value();
let y_gap = graph_editor.frp_public.output.default_y_gap_between_nodes.value();
let y_offset = y_gap + node::HEIGHT / 2.0;
let starting_point = Vector2(align_x, line_y - y_offset);
let direction = Vector2(-1.0, 0.0);
@ -245,10 +245,10 @@ pub fn on_ray(
starting_point: Vector2,
direction: Vector2,
) -> Option<Vector2> {
let x_gap = graph_editor.frp.default_x_gap_between_nodes.value();
let y_gap = graph_editor.frp.default_y_gap_between_nodes.value();
let x_gap = graph_editor.frp_public.output.default_x_gap_between_nodes.value();
let y_gap = graph_editor.frp_public.output.default_y_gap_between_nodes.value();
// This is how much horizontal space we are looking for.
let min_spacing = graph_editor.frp.min_x_spacing_for_new_nodes.value();
let min_spacing = graph_editor.frp_public.output.min_x_spacing_for_new_nodes.value();
let nodes = graph_editor.nodes.all.raw.borrow();
// The "occupied area" for given node consists of:
// - area taken by node view (obviously);

View File

@ -326,8 +326,9 @@ impl Default for Keyboard {
// === Dom ===
// ===========
/// DOM element manager
#[derive(Clone, CloneRef, Debug)]
/// DOM element manager. Creates root div element containing [`DomLayers`] upon construction and
/// removes them once dropped.
#[derive(Clone, Debug)]
pub struct Dom {
/// Root DOM element of the scene.
pub root: web::dom::WithKnownShape<web::HtmlDivElement>,
@ -363,6 +364,12 @@ impl Default for Dom {
}
}
impl Drop for Dom {
fn drop(&mut self) {
self.root.remove();
}
}
// =================
@ -496,14 +503,14 @@ impl Dirty {
#[derive(Clone, CloneRef, Debug)]
#[allow(missing_docs)]
pub struct Renderer {
dom: Dom,
dom: Rc<Dom>,
variables: UniformScope,
pub pipeline: Rc<CloneCell<render::Pipeline>>,
pub composer: Rc<RefCell<Option<render::Composer>>>,
}
impl Renderer {
fn new(dom: &Dom, variables: &UniformScope) -> Self {
fn new(dom: &Rc<Dom>, variables: &UniformScope) -> Self {
let dom = dom.clone_ref();
let variables = variables.clone_ref();
let pipeline = default();
@ -776,7 +783,7 @@ pub struct UpdateStatus {
#[derive(Clone, CloneRef, Debug)]
pub struct SceneData {
pub display_object: display::object::Root,
pub dom: Dom,
pub dom: Rc<Dom>,
pub context: Rc<RefCell<Option<Context>>>,
pub context_lost_handler: Rc<RefCell<Option<ContextLostHandler>>>,
pub variables: UniformScope,
@ -810,7 +817,7 @@ impl SceneData {
) -> Self {
debug!("Initializing.");
let display_mode = display_mode.clone_ref();
let dom = Dom::new();
let dom = default();
let display_object = display::object::Root::new_named("Scene");
let variables = world::with_context(|t| t.variables.clone_ref());
let dirty = Dirty::new(on_mut);

View File

@ -62,14 +62,18 @@ impl EventLoopExecutor {
/// attempt achieving as much progress on this executor's tasks as possible
/// without stalling.
pub fn runner(&self) -> impl FnMut(animation::TimeInfo) {
let executor = self.executor.clone();
// We pass weak handle to the runner to ensure all scheduled futures will be dropped
// once executor is dropped.
let executor = Rc::downgrade(&self.executor);
move |_| {
let _profiler =
profiler::start_debug!(profiler::APP_LIFETIME, "EventLoopExecutor::runner");
// Safe, because this is the only place borrowing executor and loop
// callback shall never be re-entrant.
let mut executor = executor.borrow_mut();
executor.run_until_stalled();
if let Some(executor) = executor.upgrade() {
// Safe, because this is the only place borrowing executor and loop
// callback shall never be re-entrant.
let mut executor = executor.borrow_mut();
executor.run_until_stalled();
}
}
}

View File

@ -56,7 +56,7 @@ pub use internal::backtrace;
/// mark each copy with unique id (the original copy has id of 0). Once enabled, it will print
/// backtrace of each clone, clone_ref or drop operation with assigned name (the same for all
/// copies) and copy id.
#[derive(Debug, Default)]
#[derive(Debug)]
pub struct TraceCopies {
clone_id: u64,
handle: Rc<RefCell<Option<ImString>>>,
@ -75,6 +75,10 @@ fn next_clone_id() -> u64 {
}
impl TraceCopies {
pub fn new() -> Self {
Self { clone_id: next_clone_id(), handle: default() }
}
/// Create enabled structure with appointed entity name (shared between all copies).
pub fn enabled(name: impl Into<ImString>) -> Self {
let this: Self = default();
@ -90,6 +94,12 @@ impl TraceCopies {
}
}
impl Default for TraceCopies {
fn default() -> Self {
Self::new()
}
}
impl Clone for TraceCopies {
fn clone(&self) -> Self {
let borrow = self.handle.borrow();
@ -126,3 +136,84 @@ impl Drop for TraceCopies {
}
}
}
// ====================
// === LeakDetector ===
// ====================
/// A module containing an utility for detecting leaks.
///
/// If you suspect a particular struct is leaking, i.e. its instances are still present when we
/// expect them to be removed, you may add a [`Trace`] field to it. Then, at the point where we
/// expect all instances to be dropped, we may check the [`TRACKED_OBJECTS`] global variable, what
/// instances are still alive and their creation backtraces.
pub mod leak_detector {
use crate::*;
thread_local! {
/// The structure mapping the existing tracking copies with [`Trace`] structure to their
/// creation backtraces.
///
/// You may check/print it at various points where you expect no traced objects should
/// persist.
pub static TRACKED_OBJECTS: RefCell<HashMap<u64, String>> = default();
}
/// A utility for tracing all copies of CloneRef-able entity and keeping list of existing ones.
///
/// This is a wrapper for [`TraceCopies`] which also register each enabled copy in
/// [`TRACKED_OBJECTS`] global variable. The variable may be then checked for leaks in moments
/// when we expect it to be empty.
#[derive(Debug, Default)]
pub struct Trace {
instance: TraceCopies,
}
impl Trace {
/// Create enabled structure with appointed entity name (shared between all copies).
///
/// See [`TraceCopies::enabled`] and [`Trace::enable`].
pub fn enabled(name: impl Into<ImString>) -> Self {
let instance = TraceCopies::enabled(name);
Self::register_tracked_object(&instance);
Self { instance }
}
/// Assign a name to the entity (shared between all copies), start printing logs and
/// register its creation backtrace in [`TRACKED_OBJECTS`].
///
/// See [`TraceCopies::enable`].
pub fn enable(&self, name: impl Into<ImString>) {
self.instance.enable(name);
Self::register_tracked_object(&self.instance);
}
fn register_tracked_object(instance: &TraceCopies) {
let id = instance.clone_id;
let bt = backtrace();
TRACKED_OBJECTS.with(|objs| objs.borrow_mut().insert(id, bt));
}
}
impl Clone for Trace {
fn clone(&self) -> Self {
let instance = self.instance.clone();
let enabled = instance.handle.borrow().is_some();
if enabled {
Self::register_tracked_object(&instance);
}
Self { instance }
}
}
impl_clone_ref_as_clone!(Trace);
impl Drop for Trace {
fn drop(&mut self) {
let id = self.instance.clone_id;
TRACKED_OBJECTS.with(|objs| objs.borrow_mut().remove(&id));
}
}
}