Developers should find the ide-gui controller codebase logical and easy to work with. (#3188)

This commit is contained in:
Adam Obuchowicz 2021-12-15 11:40:14 +01:00 committed by GitHub
parent e48e7e333b
commit 567ddd701c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 1461 additions and 5 deletions

7
Cargo.lock generated
View File

@ -2059,6 +2059,7 @@ dependencies = [
"ensogl-text-msdf-sys",
"ide-view-graph-editor",
"js-sys",
"multi-map",
"nalgebra 0.26.2",
"ordered-float 2.8.0",
"parser",
@ -2524,6 +2525,12 @@ dependencies = [
"syn",
]
[[package]]
name = "multi-map"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bba551d6d795f74a01767577ea8339560bf0a65354e0417b7e915ed608443d46"
[[package]]
name = "nalgebra"
version = "0.21.1"

View File

@ -56,5 +56,6 @@ ensogl::read_args! {
authentication_enabled : bool,
email : String,
application_config_url : String,
rust_new_presentation_layer : bool,
}
}

View File

@ -93,6 +93,11 @@ pub struct Node {
}
impl Node {
/// Get the node's id.
pub fn id(&self) -> double_representation::node::Id {
self.main_line.id()
}
/// Get the node's position.
pub fn position(&self) -> Option<model::module::Position> {
self.metadata.as_ref().and_then(|m| m.position)

View File

@ -2,10 +2,12 @@
pub mod initializer;
pub mod integration;
pub use initializer::Initializer;
use crate::prelude::*;
use crate::controller::project::INITIAL_MODULE_NAME;
use crate::ide::integration::Integration;
use crate::presenter::Presenter;
use analytics::AnonymousData;
use enso_frp as frp;
@ -13,8 +15,6 @@ use ensogl::application::Application;
use ensogl::system::web::sleep;
use std::time::Duration;
pub use initializer::Initializer;
// =================
@ -33,6 +33,17 @@ const ALIVE_LOG_INTERVAL_SEC: u64 = 60;
// === Ide ===
// ===========
/// One of the integration implementations.
///
/// The new, refactored integration is called "Presenter", but it is not yet fully implemented.
/// To test it, run IDE with `--rust-new-presentation-layer` option. By default, the old integration
/// is used.
#[derive(Debug)]
enum Integration {
Old(integration::Integration),
New(Presenter),
}
/// The main Ide structure.
///
/// This structure is a root of all objects in our application. It includes both layers:
@ -54,7 +65,11 @@ impl Ide {
view: ide_view::root::View,
controller: controller::Ide,
) -> Self {
let integration = integration::Integration::new(controller, view);
let integration = if enso_config::ARGS.rust_new_presentation_layer.unwrap_or(false) {
Integration::New(Presenter::new(controller, view))
} else {
Integration::Old(integration::Integration::new(controller, view))
};
let network = frp::Network::new("Ide");
Ide { application, integration, network }.init()
}

View File

@ -16,6 +16,7 @@
#![feature(map_try_insert)]
#![feature(assert_matches)]
#![feature(cell_filter_map)]
#![feature(hash_drain_filter)]
#![recursion_limit = "512"]
#![warn(missing_docs)]
#![warn(trivial_casts)]
@ -33,6 +34,7 @@ pub mod executor;
pub mod ide;
pub mod model;
pub mod notification;
pub mod presenter;
pub mod sync;
pub mod test;
pub mod transport;

148
app/gui/src/presenter.rs Normal file
View File

@ -0,0 +1,148 @@
//! The Presenter is a layer between logical part of the IDE (controllers, engine models) and the
//! views (the P letter in MVP pattern). The presenter reacts to changes in the controllers, and
//! actively updates the view. It also passes all user interactions from view to controllers.
//!
//! **The presenters are not fully implemented**. Therefore, the old integration defined in
//! [`crate::integration`] is used by default. The presenters may be tested by passing
//! `--rust-new-presentation-layer` commandline argument.
pub mod graph;
pub mod project;
pub use graph::Graph;
pub use project::Project;
use crate::prelude::*;
use crate::controller::ide::StatusNotification;
use crate::executor::global::spawn_stream_handler;
use crate::presenter;
use ide_view as view;
use ide_view::graph_editor::SharedHashMap;
// =============
// === Model ===
// =============
#[derive(Debug)]
struct Model {
logger: Logger,
current_project: RefCell<Option<Project>>,
controller: controller::Ide,
view: view::root::View,
}
impl Model {
/// Instantiate a new project presenter, which will display current project in the view.
fn setup_and_display_new_project(self: Rc<Self>) {
// Remove the old integration first. We want to be sure the old and new integrations will
// not race for the view.
*self.current_project.borrow_mut() = None;
if let Some(project_model) = self.controller.current_project() {
// We know the name of new project before it loads. We set it right now to avoid
// displaying placeholder on the scene during loading.
let project_view = self.view.project();
let breadcrumbs = &project_view.graph().model.breadcrumbs;
breadcrumbs.project_name(project_model.name().to_string());
let status_notifications = self.controller.status_notifications().clone_ref();
let project_controller =
controller::Project::new(project_model, status_notifications.clone_ref());
executor::global::spawn(async move {
match presenter::Project::initialize(project_controller, project_view).await {
Ok(project) => {
*self.current_project.borrow_mut() = Some(project);
}
Err(err) => {
let err_msg = format!("Failed to initialize project: {}", err);
error!(self.logger, "{err_msg}");
status_notifications.publish_event(err_msg)
}
}
});
}
}
}
// =================
// === Presenter ===
// =================
/// The root presenter, handling the synchronization between IDE controller and root view.
///
/// See [`crate::presenter`] docs for information about presenters in general.
#[derive(Clone, CloneRef, Debug)]
pub struct Presenter {
model: Rc<Model>,
}
impl Presenter {
/// Create new root presenter.
///
/// The returned presenter is working and does not require any initialization. The current
/// project will be displayed (if any).
pub fn new(controller: controller::Ide, view: ide_view::root::View) -> Self {
let logger = Logger::new("Presenter");
let current_project = default();
let model = Rc::new(Model { logger, controller, view, current_project });
Self { model }.init()
}
fn init(self) -> Self {
self.setup_status_bar_notification_handler();
self.setup_controller_notification_handler();
self.model.clone_ref().setup_and_display_new_project();
self
}
fn setup_status_bar_notification_handler(&self) {
use controller::ide::BackgroundTaskHandle as ControllerHandle;
use ide_view::status_bar::process::Id as ViewHandle;
let logger = self.model.logger.clone_ref();
let process_map = SharedHashMap::<ControllerHandle, ViewHandle>::new();
let status_bar = self.model.view.status_bar().clone_ref();
let status_notifications = self.model.controller.status_notifications().subscribe();
let weak = Rc::downgrade(&self.model);
spawn_stream_handler(weak, status_notifications, move |notification, _| {
match notification {
StatusNotification::Event { label } => {
status_bar.add_event(ide_view::status_bar::event::Label::new(label));
}
StatusNotification::BackgroundTaskStarted { label, handle } => {
status_bar.add_process(ide_view::status_bar::process::Label::new(label));
let view_handle = status_bar.last_process.value();
process_map.insert(handle, view_handle);
}
StatusNotification::BackgroundTaskFinished { handle } => {
if let Some(view_handle) = process_map.remove(&handle) {
status_bar.finish_process(view_handle);
} else {
warning!(logger, "Controllers finished process not displayed in view");
}
}
}
futures::future::ready(())
});
}
fn setup_controller_notification_handler(&self) {
let stream = self.model.controller.subscribe();
let weak = Rc::downgrade(&self.model);
spawn_stream_handler(weak, stream, move |notification, model| {
match notification {
controller::ide::Notification::NewProjectCreated
| controller::ide::Notification::ProjectOpened =>
model.setup_and_display_new_project(),
}
futures::future::ready(())
});
}
}

View File

@ -0,0 +1,401 @@
//! The module with the [`Graph`] presenter. See [`crate::presenter`] documentation to know more
//! about presenters in general.
mod state;
use crate::prelude::*;
use crate::executor::global::spawn_stream_handler;
use enso_frp as frp;
use ide_view as view;
use ide_view::graph_editor::component::node as node_view;
use ide_view::graph_editor::EdgeEndpoint;
// ===============
// === Aliases ===
// ===============
type ViewNodeId = view::graph_editor::NodeId;
type AstNodeId = ast::Id;
type ViewConnection = view::graph_editor::EdgeId;
type AstConnection = controller::graph::Connection;
// =============
// === Model ===
// =============
#[derive(Clone, Debug)]
struct Model {
logger: Logger,
controller: controller::ExecutedGraph,
view: view::graph_editor::GraphEditor,
state: Rc<state::State>,
}
impl Model {
pub fn new(
controller: controller::ExecutedGraph,
view: view::graph_editor::GraphEditor,
) -> Self {
let logger = Logger::new("presenter::Graph");
let state = default();
Self { logger, controller, view, state }
}
/// Node position was changed in view.
fn node_position_changed(&self, id: ViewNodeId, position: Vector2) {
self.update_ast(
|| {
let ast_id = self.state.update_from_view().set_node_position(id, position)?;
Some(self.controller.graph().set_node_position(ast_id, position))
},
"update node position",
);
}
/// Node was removed in view.
fn node_removed(&self, id: ViewNodeId) {
self.update_ast(
|| {
let ast_id = self.state.update_from_view().remove_node(id)?;
Some(self.controller.graph().remove_node(ast_id))
},
"remove node",
)
}
/// Connection was created in view.
fn new_connection_created(&self, id: ViewConnection) {
self.update_ast(
|| {
let connection = self.view.model.edges.get_cloned_ref(&id)?;
let ast_to_create = self.state.update_from_view().create_connection(connection)?;
Some(self.controller.connect(&ast_to_create))
},
"create connection",
);
}
/// Connection was removed in view.
fn connection_removed(&self, id: ViewConnection) {
self.update_ast(
|| {
let ast_to_remove = self.state.update_from_view().remove_connection(id)?;
Some(self.controller.disconnect(&ast_to_remove))
},
"delete connection",
);
}
fn update_ast<F>(&self, f: F, action: &str)
where F: FnOnce() -> Option<FallibleResult> {
if let Some(Err(err)) = f() {
error!(self.logger, "Failed to {action} in AST: {err}");
}
}
/// Extract all types for subexpressions in node expressions, update the state,
/// and return the events for graph editor FRP input setting all of those types.
///
/// The result includes the types not changed according to the state. That's because this
/// function is used after node expression change, and we need to reset all the types in view.
fn all_types_of_node(
&self,
node: ViewNodeId,
) -> Vec<(ViewNodeId, ast::Id, Option<view::graph_editor::Type>)> {
let subexpressions = self.state.expressions_of_node(node);
subexpressions
.iter()
.map(|id| {
let a_type = self.expression_type(*id);
self.state.update_from_controller().set_expression_type(*id, a_type.clone());
(node, *id, a_type)
})
.collect()
}
/// Extract all method pointers for subexpressions, update the state, and return events updating
/// view for expressions where method pointer actually changed.
fn all_method_pointers_of_node(
&self,
node: ViewNodeId,
) -> Vec<(ast::Id, Option<view::graph_editor::MethodPointer>)> {
let subexpressions = self.state.expressions_of_node(node);
subexpressions.iter().filter_map(|id| self.refresh_expression_method_pointer(*id)).collect()
}
/// Refresh type of the given expression.
///
/// If the view update is required, the GraphEditor's FRP input event is returned.
fn refresh_expression_type(
&self,
id: ast::Id,
) -> Option<(ViewNodeId, ast::Id, Option<view::graph_editor::Type>)> {
let a_type = self.expression_type(id);
let node_view =
self.state.update_from_controller().set_expression_type(id, a_type.clone())?;
Some((node_view, id, a_type))
}
/// Refresh method pointer of the given expression.
///
/// If the view update is required, the GraphEditor's FRP input event is returned.
fn refresh_expression_method_pointer(
&self,
id: ast::Id,
) -> Option<(ast::Id, Option<view::graph_editor::MethodPointer>)> {
let method_pointer = self.expression_method(id);
self.state
.update_from_controller()
.set_expression_method_pointer(id, method_pointer.clone())?;
Some((id, method_pointer))
}
/// Extract the expression's current type from controllers.
fn expression_type(&self, id: ast::Id) -> Option<view::graph_editor::Type> {
let registry = self.controller.computed_value_info_registry();
let info = registry.get(&id)?;
Some(view::graph_editor::Type(info.typename.as_ref()?.clone_ref()))
}
/// Extract the expression's current method pointer from controllers.
fn expression_method(&self, id: ast::Id) -> Option<view::graph_editor::MethodPointer> {
let registry = self.controller.computed_value_info_registry();
let method_id = registry.get(&id)?.method_call?;
let suggestion_db = self.controller.graph().suggestion_db.clone_ref();
let method = suggestion_db.lookup_method_ptr(method_id).ok()?;
Some(view::graph_editor::MethodPointer(Rc::new(method)))
}
}
// ==================
// === ViewUpdate ===
// ==================
/// Structure handling view update after graph invalidation.
///
/// Because updating various graph elements (nodes, connections, types) bases on the same data
/// extracted from controllers, the data are cached in this structure.
#[derive(Clone, Debug, Default)]
struct ViewUpdate {
state: Rc<state::State>,
nodes: Vec<controller::graph::Node>,
trees: HashMap<AstNodeId, controller::graph::NodeTrees>,
connections: HashSet<AstConnection>,
}
impl ViewUpdate {
/// Create ViewUpdate information from Graph Presenter's model.
fn new(model: &Model) -> FallibleResult<Self> {
let displayed = model.state.clone_ref();
let nodes = model.controller.graph().nodes()?;
let connections_and_trees = model.controller.connections()?;
let connections = connections_and_trees.connections.into_iter().collect();
let trees = connections_and_trees.trees;
Ok(Self { state: displayed, nodes, trees, connections })
}
/// Remove nodes from the state and return node views to be removed.
fn remove_nodes(&self) -> Vec<ViewNodeId> {
self.state.update_from_controller().retain_nodes(&self.node_ids().collect())
}
/// Returns number of nodes view should create.
fn count_nodes_to_add(&self) -> usize {
self.node_ids().filter(|n| self.state.view_id_of_ast_node(*n).is_none()).count()
}
/// Set the nodes expressions in state, and return the events to be passed to Graph Editor FRP
/// input for nodes where expression changed.
///
/// The nodes not having views are also updated in the state.
fn set_node_expressions(&self) -> Vec<(ViewNodeId, node_view::Expression)> {
self.nodes
.iter()
.filter_map(|node| {
let id = node.main_line.id();
let trees = self.trees.get(&id).cloned().unwrap_or_default();
self.state.update_from_controller().set_node_expression(node, trees)
})
.collect()
}
/// Set the nodes position in state, and return the events to be passed to GraphEditor FRP
/// input for nodes where position changed.
///
/// The nodes not having views are also updated in the state.
fn set_node_positions(&self) -> Vec<(ViewNodeId, Vector2)> {
self.nodes
.iter()
.filter_map(|node| {
let id = node.main_line.id();
let position = node.position()?.vector;
let view_id =
self.state.update_from_controller().set_node_position(id, position)?;
Some((view_id, position))
})
.collect()
}
/// Remove connections from the state and return views to be removed.
fn remove_connections(&self) -> Vec<ViewConnection> {
self.state.update_from_controller().retain_connections(&self.connections)
}
/// Add connections to the state and return endpoints of connections to be created in views.
fn add_connections(&self) -> Vec<(EdgeEndpoint, EdgeEndpoint)> {
let ast_conns = self.connections.iter();
ast_conns
.filter_map(|connection| {
self.state.update_from_controller().set_connection(connection.clone())
})
.collect()
}
fn node_ids(&self) -> impl Iterator<Item = AstNodeId> + '_ {
self.nodes.iter().map(controller::graph::Node::id)
}
}
// =============
// === Graph ===
// =============
/// The Graph Presenter, synchronizing graph state between graph controller and view.
///
/// This presenter focuses on the graph structure: nodes, their expressions and types, and
/// connections between them. It does not integrate Searcher nor Breadcrumbs - integration of
/// these is still to-be-delivered.
#[derive(Debug)]
pub struct Graph {
network: frp::Network,
model: Rc<Model>,
}
impl Graph {
/// Create graph presenter. The returned structure is working and does not require any
/// initialization.
pub fn new(
controller: controller::ExecutedGraph,
view: view::graph_editor::GraphEditor,
) -> Self {
let network = frp::Network::new("presenter::Graph");
let model = Rc::new(Model::new(controller, view));
Self { network, model }.init()
}
fn init(self) -> Self {
let logger = &self.model.logger;
let network = &self.network;
let model = &self.model;
let view = &self.model.view.frp;
frp::extend! { network
update_view <- source::<()>();
update_data <- update_view.map(
f_!([logger,model] match ViewUpdate::new(&*model) {
Ok(update) => Rc::new(update),
Err(err) => {
error!(logger,"Failed to update view: {err:?}");
Rc::new(default())
}
})
);
// === Refreshing Nodes ===
remove_node <= update_data.map(|update| update.remove_nodes());
update_node_expression <= update_data.map(|update| update.set_node_expressions());
set_node_position <= update_data.map(|update| update.set_node_positions());
view.remove_node <+ remove_node;
view.set_node_expression <+ update_node_expression;
view.set_node_position <+ set_node_position;
view.add_node <+ update_data.map(|update| update.count_nodes_to_add()).repeat();
added_node_update <- view.node_added.filter_map(f!((view_id)
model.state.assign_node_view(*view_id)
));
init_node_expression <- added_node_update.filter_map(|update| Some((update.view_id?, update.expression.clone())));
view.set_node_expression <+ init_node_expression;
view.set_node_position <+ added_node_update.filter_map(|update| Some((update.view_id?, update.position)));
// === Refreshing Connections ===
remove_connection <= update_data.map(|update| update.remove_connections());
add_connection <= update_data.map(|update| update.add_connections());
view.remove_edge <+ remove_connection;
view.connect_nodes <+ add_connection;
// === Refreshing Expressions ===
reset_node_types <- any(update_node_expression, init_node_expression)._0();
set_expression_type <= reset_node_types.map(f!((view_id) model.all_types_of_node(*view_id)));
set_method_pointer <= reset_node_types.map(f!((view_id) model.all_method_pointers_of_node(*view_id)));
view.set_expression_usage_type <+ set_expression_type;
view.set_method_pointer <+ set_method_pointer;
update_expressions <- source::<Vec<ast::Id>>();
update_expression <= update_expressions;
view.set_expression_usage_type <+ update_expression.filter_map(f!((id) model.refresh_expression_type(*id)));
view.set_method_pointer <+ update_expression.filter_map(f!((id) model.refresh_expression_method_pointer(*id)));
// === Changes from the View ===
eval view.node_position_set_batched(((node_id, position)) model.node_position_changed(*node_id, *position));
eval view.node_removed((node_id) model.node_removed(*node_id));
eval view.on_edge_endpoints_set((edge_id) model.new_connection_created(*edge_id));
eval view.on_edge_endpoint_unset(((edge_id,_)) model.connection_removed(*edge_id));
}
update_view.emit(());
self.setup_controller_notification_handlers(update_view, update_expressions);
self
}
fn setup_controller_notification_handlers(
&self,
update_view: frp::Source<()>,
update_expressions: frp::Source<Vec<ast::Id>>,
) {
use crate::controller::graph::executed;
use crate::controller::graph::Notification;
let graph_notifications = self.model.controller.subscribe();
self.spawn_sync_stream_handler(graph_notifications, move |notification, model| {
info!(model.logger, "Received controller notification {notification:?}");
match notification {
executed::Notification::Graph(graph) => match graph {
Notification::Invalidate => update_view.emit(()),
Notification::PortsUpdate => update_view.emit(()),
},
executed::Notification::ComputedValueInfo(expressions) =>
update_expressions.emit(expressions),
executed::Notification::EnteredNode(_) => {}
executed::Notification::SteppedOutOfNode(_) => {}
}
})
}
fn spawn_sync_stream_handler<Stream, Function>(&self, stream: Stream, handler: Function)
where
Stream: StreamExt + Unpin + 'static,
Function: Fn(Stream::Item, Rc<Model>) + 'static, {
let model = Rc::downgrade(&self.model);
spawn_stream_handler(model, stream, move |item, model| {
handler(item, model);
futures::future::ready(())
})
}
}

View File

@ -0,0 +1,756 @@
//! The module containing the Graph Presenter [`State`]
use crate::prelude::*;
use crate::presenter::graph::AstConnection;
use crate::presenter::graph::AstNodeId;
use crate::presenter::graph::ViewConnection;
use crate::presenter::graph::ViewNodeId;
use bimap::BiMap;
use bimap::Overwritten;
use ide_view as view;
use ide_view::graph_editor::component::node as node_view;
use ide_view::graph_editor::EdgeEndpoint;
// =============
// === Nodes ===
// =============
/// A single node data.
#[allow(missing_docs)]
#[derive(Clone, Debug, Default)]
pub struct Node {
pub view_id: Option<ViewNodeId>,
pub position: Vector2,
pub expression: node_view::Expression,
}
/// The set of node states.
///
/// This structure allows to access data of any node by Ast id, or view id. It also keeps list
/// of the AST nodes with no view assigned, and allows to assign View Id to the next one.
#[derive(Clone, Debug, Default)]
pub struct Nodes {
// Each operation in this structure should keep the following constraints:
// * Each `nodes_without_view` entry has an entry in `nodes` with `view_id` being `None`.
// * All values in `ast_node_by_view_id` has corresponding element in `nodes` with `view_id`
// being equal to key of the value.
nodes: HashMap<AstNodeId, Node>,
nodes_without_view: Vec<AstNodeId>,
ast_node_by_view_id: HashMap<ViewNodeId, AstNodeId>,
}
impl Nodes {
/// Get the state of the node by Ast id.
pub fn get(&self, id: AstNodeId) -> Option<&Node> {
self.nodes.get(&id)
}
/// Get mutable reference of the node's state by Ast id.
pub fn get_mut(&mut self, id: AstNodeId) -> Option<&mut Node> {
self.nodes.get_mut(&id)
}
/// Get the mutable reference, creating an default entry without view if it's missing.
///
/// The entry will be also present on the "nodes without view" list and may have view assigned
/// using [`assign_newly_created_node`] method.
pub fn get_mut_or_create(&mut self, id: AstNodeId) -> &mut Node {
let nodes_without_view = &mut self.nodes_without_view;
self.nodes.entry(id).or_insert_with(|| {
nodes_without_view.push(id);
default()
})
}
/// Get the AST id of the node represented by given view. Returns None, if the node view does
/// not represent any AST node.
pub fn ast_id_of_view(&self, view_id: ViewNodeId) -> Option<AstNodeId> {
self.ast_node_by_view_id.get(&view_id).copied()
}
/// Assign a node view to the one of AST nodes without view. If there is any of such nodes,
/// `None` is returned. Otherwise, returns the node state - the newly created view must be
/// refreshed with the data from the state.
pub fn assign_newly_created_node(&mut self, view_id: ViewNodeId) -> Option<&mut Node> {
let ast_node = self.nodes_without_view.pop()?;
let mut opt_displayed = self.nodes.get_mut(&ast_node);
if let Some(displayed) = &mut opt_displayed {
displayed.view_id = Some(view_id);
self.ast_node_by_view_id.insert(view_id, ast_node);
}
opt_displayed
}
/// Update the state retaining given set of nodes. Returns the list of removed nodes' views.
pub fn retain_nodes(&mut self, nodes: &HashSet<AstNodeId>) -> Vec<ViewNodeId> {
self.nodes_without_view.drain_filter(|id| !nodes.contains(id));
let removed = self.nodes.drain_filter(|id, _| !nodes.contains(id));
let removed_views = removed.filter_map(|(_, data)| data.view_id).collect();
for view_id in &removed_views {
self.ast_node_by_view_id.remove(view_id);
}
removed_views
}
/// Remove node represented by given view (if any) and return it's AST id.
pub fn remove_node(&mut self, node: ViewNodeId) -> Option<AstNodeId> {
let ast_id = self.ast_node_by_view_id.remove(&node)?;
self.nodes.remove(&ast_id);
Some(ast_id)
}
}
// ===================
// === Connections ===
// ===================
/// A structure keeping pairs of AST connections with their views (and list of AST connections
/// without view).
#[derive(Clone, Debug, Default)]
pub struct Connections {
connections: BiMap<AstConnection, ViewConnection>,
connections_without_view: HashSet<AstConnection>,
}
impl Connections {
/// Remove all connections not belonging to the given set.
///
/// Returns the views of removed connections.
pub fn retain_connections(
&mut self,
connections: &HashSet<AstConnection>,
) -> Vec<ViewConnection> {
self.connections_without_view.retain(|x| connections.contains(x));
let to_remove = self.connections.iter().filter(|(con, _)| !connections.contains(con));
let to_remove_vec = to_remove.map(|(_, edge_id)| *edge_id).collect_vec();
self.connections.retain(|con, _| connections.contains(con));
to_remove_vec
}
/// Add a new AST connection without view.
pub fn add_ast_connection(&mut self, connection: AstConnection) -> bool {
if !self.connections.contains_left(&connection) {
self.connections_without_view.insert(connection)
} else {
false
}
}
/// Add a connection with view.
///
/// Returns `true` if the new connection was added, and `false` if it already existed. In the
/// latter case, the new `view` is assigned to it (replacing possible previous view).
pub fn add_connection_view(&mut self, connection: AstConnection, view: ViewConnection) -> bool {
let existed_without_view = self.connections_without_view.remove(&connection);
match self.connections.insert(connection, view) {
Overwritten::Neither => !existed_without_view,
Overwritten::Left(_, _) => false,
Overwritten::Right(previous, _) => {
self.connections_without_view.insert(previous);
!existed_without_view
}
Overwritten::Pair(_, _) => false,
Overwritten::Both(_, (previous, _)) => {
self.connections_without_view.insert(previous);
false
}
}
}
/// Remove the connection by view (if any), and return it.
pub fn remove_connection(&mut self, connection: ViewConnection) -> Option<AstConnection> {
let (ast_connection, _) = self.connections.remove_by_right(&connection)?;
Some(ast_connection)
}
}
// ===================
// === Expressions ===
// ===================
/// A single expression data.
#[derive(Clone, Debug, Default)]
pub struct Expression {
pub node: AstNodeId,
pub expression_type: Option<view::graph_editor::Type>,
pub method_pointer: Option<view::graph_editor::MethodPointer>,
}
/// The data of node's expressions.
///
/// The expressions are all AST nodes of the line representing the node in the code.
#[derive(Clone, Debug, Default)]
pub struct Expressions {
expressions: HashMap<ast::Id, Expression>,
expressions_of_node: HashMap<AstNodeId, Vec<ast::Id>>,
}
impl Expressions {
/// Remove all expressions not belonging to the any of the `nodes`.
pub fn retain_expression_of_nodes(&mut self, nodes: &HashSet<AstNodeId>) {
let nodes_to_remove =
self.expressions_of_node.drain_filter(|node_id, _| !nodes.contains(node_id));
let expr_to_remove = nodes_to_remove.map(|(_, exprs)| exprs).flatten();
for expression_id in expr_to_remove {
self.expressions.remove(&expression_id);
}
}
/// Update information about node expressions.
///
/// New node's expressions are added, and those which stopped to be part of the node are
/// removed.
pub fn update_node_expressions(&mut self, node: AstNodeId, expressions: Vec<ast::Id>) {
let new_set: HashSet<ast::Id> = expressions.iter().copied().collect();
let old_set = self.expressions_of_node.insert(node, expressions).unwrap_or_default();
for old_expression in &old_set {
if !new_set.contains(old_expression) {
self.expressions.remove(old_expression);
}
}
for new_expression in new_set {
if !old_set.contains(&new_expression) {
self.expressions.insert(new_expression, Expression { node, ..default() });
}
}
}
/// Get mutable reference to given expression data.
pub fn get_mut(&mut self, id: ast::Id) -> Option<&mut Expression> {
self.expressions.get_mut(&id)
}
/// Get the list of all expressions of the given node.
pub fn expressions_of_node(&self, id: AstNodeId) -> &[ast::Id] {
self.expressions_of_node.get(&id).map_or(&[], |v| v.as_slice())
}
}
// =============
// === State ===
// =============
/// The Graph Presenter State.
///
/// This structure keeps the information how the particular graph elements received from controllers
/// are represented in the view. It also handles updates from the controllers and
/// the view in `update_from_controller` and `update_from_view` respectively.
#[derive(Clone, Debug, Default)]
pub struct State {
nodes: RefCell<Nodes>,
connections: RefCell<Connections>,
expressions: RefCell<Expressions>,
}
impl State {
/// Get node's view id by the AST id.
pub fn view_id_of_ast_node(&self, node: AstNodeId) -> Option<ViewNodeId> {
self.nodes.borrow().get(node).and_then(|n| n.view_id)
}
/// Convert the AST connection to pair of [`EdgeEndpoint`]s.
pub fn view_edge_targets_of_ast_connection(
&self,
connection: AstConnection,
) -> Option<(EdgeEndpoint, EdgeEndpoint)> {
let nodes = self.nodes.borrow();
let src_node = nodes.get(connection.source.node)?.view_id?;
let dst_node = nodes.get(connection.destination.node)?.view_id?;
let src = EdgeEndpoint::new(src_node, connection.source.port);
let data = EdgeEndpoint::new(dst_node, connection.destination.port);
Some((src, data))
}
/// Convert the pair of [`EdgeEndpoint`]s to AST connection.
pub fn ast_connection_from_view_edge_targets(
&self,
source: EdgeEndpoint,
target: EdgeEndpoint,
) -> Option<AstConnection> {
let nodes = self.nodes.borrow();
let src_node = nodes.ast_id_of_view(source.node_id)?;
let dst_node = nodes.ast_id_of_view(target.node_id)?;
Some(controller::graph::Connection {
source: controller::graph::Endpoint::new(src_node, source.port),
destination: controller::graph::Endpoint::new(dst_node, target.port),
})
}
/// Get id of all node's expressions (ids of the all corresponding line AST nodes).
pub fn expressions_of_node(&self, node: ViewNodeId) -> Vec<ast::Id> {
let ast_node = self.nodes.borrow().ast_id_of_view(node);
ast_node.map_or_default(|id| self.expressions.borrow().expressions_of_node(id).to_owned())
}
/// Apply the update from controller.
pub fn update_from_controller(&self) -> ControllerChange {
ControllerChange { state: self }
}
/// Apply the update from the view.
pub fn update_from_view(&self) -> ViewChange {
ViewChange { state: self }
}
/// Assign a node view to the one of AST nodes without view. If there is any of such nodes,
/// `None` is returned. Otherwise, returns the node state - the newly created view must be
/// refreshed with the data from the state.
pub fn assign_node_view(&self, view_id: ViewNodeId) -> Option<Node> {
self.nodes.borrow_mut().assign_newly_created_node(view_id).cloned()
}
}
// ========================
// === ControllerChange ===
// ========================
/// The wrapper for [`State`] reference providing the API to be called when presenter is notified
/// by controllers about graph change.
///
/// All of its operations updates the [`State`] to synchronize it with the graph in AST, and returns
/// the information how to update yje view, to have the view synchronized with the state.
///
/// In the particular case, when the graph was changed due to user interations with the view, these
/// method should discover that no change in state is needed (because it was updated already by
/// [`ViewChange`]), and so the view's. This way we avoid an infinite synchronization cycle.
#[derive(Deref, DerefMut, Debug)]
pub struct ControllerChange<'a> {
state: &'a State,
}
// === Nodes ===
impl<'a> ControllerChange<'a> {
/// Remove all nodes not belonging to the given set. Returns the list of to-be-removed views.
pub fn retain_nodes(&self, nodes: &HashSet<AstNodeId>) -> Vec<ViewNodeId> {
self.expressions.borrow_mut().retain_expression_of_nodes(nodes);
self.nodes.borrow_mut().retain_nodes(nodes)
}
/// Set the new node position. If the node position actually changed, the to-be-updated view
/// is returned.
pub fn set_node_position(&self, node: AstNodeId, position: Vector2) -> Option<ViewNodeId> {
let mut nodes = self.nodes.borrow_mut();
let mut displayed = nodes.get_mut_or_create(node);
if displayed.position != position {
displayed.position = position;
displayed.view_id
} else {
None
}
}
/// Set the new node expression. If the expression actually changed, the to-be-updated view
/// is returned with the new expression to set.
pub fn set_node_expression(
&self,
node: &controller::graph::Node,
trees: controller::graph::NodeTrees,
) -> Option<(ViewNodeId, node_view::Expression)> {
let ast_id = node.main_line.id();
let new_displayed_expr = node_view::Expression {
pattern: node.info.pattern().map(|t| t.repr()),
code: node.info.expression().repr(),
whole_expression_id: node.info.expression().id,
input_span_tree: trees.inputs,
output_span_tree: trees.outputs.unwrap_or_else(default),
};
let mut nodes = self.nodes.borrow_mut();
let displayed = nodes.get_mut_or_create(ast_id);
if displayed.expression != new_displayed_expr {
displayed.expression = new_displayed_expr.clone();
let new_expressions =
node.info.ast().iter_recursive().filter_map(|ast| ast.id).collect();
self.expressions.borrow_mut().update_node_expressions(ast_id, new_expressions);
Some((displayed.view_id?, new_displayed_expr))
} else {
None
}
}
}
// === Connections ===
impl<'a> ControllerChange<'a> {
/// If given connection does not exists yet, add it and return the endpoints of the
/// to-be-created edge.
pub fn set_connection(
&self,
connection: AstConnection,
) -> Option<(EdgeEndpoint, EdgeEndpoint)> {
self.connections
.borrow_mut()
.add_ast_connection(connection.clone())
.and_option_from(move || self.view_edge_targets_of_ast_connection(connection))
}
/// Remove all connection not belonging to the given set. Returns the list of to-be-removed
/// views.
pub fn retain_connections(&self, connections: &HashSet<AstConnection>) -> Vec<ViewConnection> {
self.connections.borrow_mut().retain_connections(connections)
}
}
// === Expressions ===
impl<'a> ControllerChange<'a> {
/// Set the new type of expression. If the type actually changes, the to-be-updated view is
/// returned.
pub fn set_expression_type(
&self,
id: ast::Id,
new_type: Option<view::graph_editor::Type>,
) -> Option<ViewNodeId> {
let mut expressions = self.expressions.borrow_mut();
let to_update = expressions.get_mut(id).filter(|d| d.expression_type != new_type);
if let Some(displayed) = to_update {
displayed.expression_type = new_type;
self.nodes.borrow().get(displayed.node).and_then(|node| node.view_id)
} else {
None
}
}
/// Set the new expression's method pointer. If the method pointer actually changes, the
/// to-be-updated view is returned.
pub fn set_expression_method_pointer(
&self,
id: ast::Id,
method_ptr: Option<view::graph_editor::MethodPointer>,
) -> Option<ViewNodeId> {
let mut expressions = self.expressions.borrow_mut();
let to_update = expressions.get_mut(id).filter(|d| d.method_pointer != method_ptr);
if let Some(displayed) = to_update {
displayed.method_pointer = method_ptr;
self.nodes.borrow().get(displayed.node).and_then(|node| node.view_id)
} else {
None
}
}
}
// ==================
// === ViewChange ===
// ==================
/// The wrapper for [`State`] reference providing the API to be called when presenter is notified
/// about view change.
///
/// All of its operations updates the [`State`] to synchronize it with the graph view, and returns
/// the information how to update the AST graph, to have the AST synchronized with the state.
///
/// In particular case, when the view was changed due to change in controller, these method should
/// discover that no change in state is needed (because it was updated already by
/// [`ControllerChange`]), and so the AST graph's. This way we avoid an infinite synchronization
/// cycle.
#[derive(Deref, DerefMut, Debug)]
pub struct ViewChange<'a> {
state: &'a State,
}
// === Nodes ===
impl<'a> ViewChange<'a> {
/// Set the new node position. If the node position actually changed, the AST node to-be-updated
/// id is returned.
pub fn set_node_position(&self, id: ViewNodeId, new_position: Vector2) -> Option<AstNodeId> {
let mut nodes = self.nodes.borrow_mut();
let ast_id = nodes.ast_id_of_view(id)?;
let displayed = nodes.get_mut(ast_id)?;
if displayed.position != new_position {
displayed.position = new_position;
Some(ast_id)
} else {
None
}
}
/// Remove the node, and returns its AST id.
pub fn remove_node(&self, id: ViewNodeId) -> Option<AstNodeId> {
self.nodes.borrow_mut().remove_node(id)
}
}
// === Connections ===
impl<'a> ViewChange<'a> {
/// If the connections does not already exist, it is created and corresponding to-be-created
/// Ast connection is returned.
pub fn create_connection(&self, connection: view::graph_editor::Edge) -> Option<AstConnection> {
let source = connection.source()?;
let target = connection.target()?;
self.create_connection_from_endpoints(connection.id(), source, target)
}
/// If the connections with provided endpoints does not already exist, it is created and
/// corresponding to-be-created Ast connection is returned.
pub fn create_connection_from_endpoints(
&self,
connection: ViewConnection,
source: EdgeEndpoint,
target: EdgeEndpoint,
) -> Option<AstConnection> {
let ast_connection = self.ast_connection_from_view_edge_targets(source, target)?;
let mut connections = self.connections.borrow_mut();
let should_update_controllers =
connections.add_connection_view(ast_connection.clone(), connection);
should_update_controllers.then_some(ast_connection)
}
/// Remove the connection and return the corresponding AST connection which should be removed.
pub fn remove_connection(&self, id: ViewConnection) -> Option<AstConnection> {
self.connections.borrow_mut().remove_connection(id)
}
}
// =============
// === Tests ===
// =============
#[cfg(test)]
mod tests {
use super::*;
use engine_protocol::language_server::MethodPointer;
use parser::Parser;
fn create_test_node(expression: &str) -> controller::graph::Node {
let parser = Parser::new_or_panic();
let ast = parser.parse_line_ast(expression).unwrap();
controller::graph::Node {
info: double_representation::node::NodeInfo {
documentation: None,
main_line: double_representation::node::MainLine::from_ast(&ast).unwrap(),
},
metadata: None,
}
}
fn node_trees_of(node: &controller::graph::Node) -> controller::graph::NodeTrees {
controller::graph::NodeTrees::new(&node.info, &span_tree::generate::context::Empty).unwrap()
}
struct TestNode {
node: controller::graph::Node,
view: ViewNodeId,
}
struct Fixture {
state: State,
nodes: Vec<TestNode>,
}
impl Fixture {
fn setup_nodes(expressions: impl IntoIterator<Item: AsRef<str>>) -> Self {
let nodes = expressions.into_iter().map(|expr| create_test_node(expr.as_ref()));
let state = State::default();
let displayed_nodes = nodes
.enumerate()
.map(|(i, node)| {
let view = ensogl::display::object::Id::from(i).into();
state.update_from_controller().set_node_expression(&node, node_trees_of(&node));
state.assign_node_view(view);
TestNode { node, view }
})
.collect();
Fixture { state, nodes: displayed_nodes }
}
}
#[wasm_bindgen_test]
fn adding_and_removing_nodes() {
let state = State::default();
let node1 = create_test_node("node1 = 2 + 2");
let node2 = create_test_node("node2 = node1 + 2");
let node_view_1 = ensogl::display::object::Id::from(1).into();
let node_view_2 = ensogl::display::object::Id::from(2).into();
let from_controller = state.update_from_controller();
let from_view = state.update_from_view();
assert_eq!(from_controller.set_node_expression(&node1, node_trees_of(&node1)), None);
assert_eq!(from_controller.set_node_expression(&node2, node_trees_of(&node2)), None);
assert_eq!(state.view_id_of_ast_node(node1.id()), None);
assert_eq!(state.view_id_of_ast_node(node2.id()), None);
let assigned = state.assign_node_view(node_view_2);
assert_eq!(assigned.map(|node| node.expression.code), Some("node1 + 2".to_owned()));
let assigned = state.assign_node_view(node_view_1);
assert_eq!(assigned.map(|node| node.expression.code), Some("2 + 2".to_owned()));
assert_eq!(state.view_id_of_ast_node(node1.id()), Some(node_view_1));
assert_eq!(state.view_id_of_ast_node(node2.id()), Some(node_view_2));
let node1_exprs =
node1.info.main_line.ast().iter_recursive().filter_map(|a| a.id).collect_vec();
assert_eq!(state.expressions_of_node(node_view_1), node1_exprs);
let node2_exprs =
node2.info.main_line.ast().iter_recursive().filter_map(|a| a.id).collect_vec();
assert_eq!(state.expressions_of_node(node_view_2), node2_exprs);
let views_to_remove = from_controller.retain_nodes(&[node1.id()].iter().copied().collect());
assert_eq!(views_to_remove, vec![node_view_2]);
assert_eq!(state.view_id_of_ast_node(node1.id()), Some(node_view_1));
assert_eq!(state.view_id_of_ast_node(node2.id()), None);
assert_eq!(from_view.remove_node(node_view_1), Some(node1.id()));
assert_eq!(state.view_id_of_ast_node(node1.id()), None)
}
#[wasm_bindgen_test]
fn adding_and_removing_connections() {
use controller::graph::Endpoint;
let Fixture { state, nodes } = Fixture::setup_nodes(&["node1 = 2", "node1 + node1"]);
let src = Endpoint {
node: nodes[0].node.id(),
port: default(),
var_crumbs: default(),
};
let dest1 = Endpoint {
node: nodes[1].node.id(),
port: span_tree::Crumbs::new(vec![0]),
var_crumbs: default(),
};
let dest2 = Endpoint {
node: nodes[1].node.id(),
port: span_tree::Crumbs::new(vec![2]),
var_crumbs: default(),
};
let ast_con1 = AstConnection { source: src.clone(), destination: dest1.clone() };
let ast_con2 = AstConnection { source: src.clone(), destination: dest2.clone() };
let view_con1 = ensogl::display::object::Id::from(1).into();
let view_con2 = ensogl::display::object::Id::from(2).into();
let view_src = EdgeEndpoint { node_id: nodes[0].view, port: src.port.clone() };
let view_tgt1 = EdgeEndpoint { node_id: nodes[1].view, port: dest1.port.clone() };
let view_tgt2 = EdgeEndpoint { node_id: nodes[1].view, port: dest2.port.clone() };
let view_pair1 = (view_src.clone(), view_tgt1.clone());
let from_controller = state.update_from_controller();
let from_view = state.update_from_view();
assert_eq!(from_controller.set_connection(ast_con1.clone()), Some(view_pair1.clone()));
assert_eq!(
from_view.create_connection_from_endpoints(view_con1, view_src.clone(), view_tgt1),
None
);
assert_eq!(
from_view.create_connection_from_endpoints(view_con2, view_src.clone(), view_tgt2),
Some(ast_con2.clone())
);
let all_connections = [ast_con1.clone(), ast_con2.clone()].into_iter().collect();
assert_eq!(from_controller.retain_connections(&all_connections), vec![]);
assert_eq!(
from_controller.retain_connections(&[ast_con2.clone()].into_iter().collect()),
vec![view_con1]
);
assert_eq!(from_view.remove_connection(view_con2), Some(ast_con2.clone()));
}
#[wasm_bindgen_test]
fn refreshing_node_expression() {
let Fixture { state, nodes } = Fixture::setup_nodes(&["foo bar"]);
let node_id = nodes[0].node.id();
let new_ast = Parser::new_or_panic().parse_line_ast("foo baz").unwrap().with_id(node_id);
let new_node = controller::graph::Node {
info: double_representation::node::NodeInfo {
documentation: None,
main_line: double_representation::node::MainLine::from_ast(&new_ast).unwrap(),
},
metadata: None,
};
let new_subexpressions = new_ast.iter_recursive().filter_map(|ast| ast.id).collect_vec();
let new_trees = node_trees_of(&new_node);
let view = nodes[0].view;
let expected_new_expression = view::graph_editor::component::node::Expression {
pattern: None,
code: "foo baz".to_string(),
whole_expression_id: Some(node_id),
input_span_tree: new_trees.inputs.clone(),
output_span_tree: default(),
};
let updater = state.update_from_controller();
assert_eq!(
updater.set_node_expression(&new_node, new_trees.clone()),
Some((view, expected_new_expression))
);
assert_eq!(updater.set_node_expression(&new_node, new_trees), None);
assert_eq!(state.expressions_of_node(view), new_subexpressions);
}
#[wasm_bindgen_test]
fn updating_node_position() {
let Fixture { state, nodes } = Fixture::setup_nodes(&["foo"]);
let node_id = nodes[0].node.id();
let view_id = nodes[0].view;
let position_from_ast = Vector2(1.0, 2.0);
let position_from_view = Vector2(3.0, 4.0);
let from_controller = state.update_from_controller();
let from_view = state.update_from_view();
assert_eq!(from_controller.set_node_position(node_id, position_from_ast), Some(view_id));
assert_eq!(from_view.set_node_position(view_id, position_from_ast), None);
assert_eq!(from_view.set_node_position(view_id, position_from_view), Some(node_id));
assert_eq!(from_controller.set_node_position(node_id, position_from_view), None);
}
#[wasm_bindgen_test]
fn refreshing_expression_types() {
use ast::crumbs::InfixCrumb;
let Fixture { state, nodes } = Fixture::setup_nodes(&["2 + 3"]);
let view = nodes[0].view;
let node_ast = nodes[0].node.main_line.expression();
let left_operand = node_ast.get(&InfixCrumb::LeftOperand.into()).unwrap().id.unwrap();
let right_operand = node_ast.get(&InfixCrumb::RightOperand.into()).unwrap().id.unwrap();
let updater = state.update_from_controller();
let number_type = Some(view::graph_editor::Type::from("Number".to_owned()));
assert_eq!(updater.set_expression_type(left_operand, number_type.clone()), Some(view));
assert_eq!(updater.set_expression_type(right_operand, number_type.clone()), Some(view));
assert_eq!(updater.set_expression_type(left_operand, number_type.clone()), None);
assert_eq!(updater.set_expression_type(right_operand, number_type), None);
assert_eq!(updater.set_expression_type(left_operand, None), Some(view));
assert_eq!(updater.set_expression_type(right_operand, None), Some(view));
}
#[wasm_bindgen_test]
fn refreshing_expression_method_pointers() {
let Fixture { state, nodes } = Fixture::setup_nodes(&["foo bar"]);
let view = nodes[0].view;
let expr = nodes[0].node.id();
let updater = state.update_from_controller();
let method_ptr = MethodPointer {
module: "Foo".to_string(),
defined_on_type: "Foo".to_string(),
name: "foo".to_string(),
};
let method_ptr = Some(view::graph_editor::MethodPointer::from(method_ptr));
assert_eq!(updater.set_expression_method_pointer(expr, method_ptr.clone()), Some(view));
assert_eq!(updater.set_expression_method_pointer(expr, method_ptr), None);
assert_eq!(updater.set_expression_method_pointer(expr, None), Some(view));
}
}

View File

@ -0,0 +1,64 @@
//! The module with the [`Project`] presenter. See [`crate::presenter`] documentation to know more
//! about presenters in general.
use crate::prelude::*;
use crate::presenter;
use ide_view as view;
// =============
// === Model ===
// =============
// Those fields will be probably used when Searcher and Breadcrumbs integration will be implemented.
#[allow(unused)]
#[derive(Debug)]
struct Model {
controller: controller::Project,
view: view::project::View,
graph: presenter::Graph,
}
// ===============
// === Project ===
// ===============
/// The Project Presenter, synchronizing state between project controller and project view.
#[derive(Clone, CloneRef, Debug)]
pub struct Project {
model: Rc<Model>,
}
impl Project {
/// Construct new project presenter, basing of the project initialization result.
///
/// The returned presenter will be already working: it will display the initial main graph, and
/// react to all notifications.
pub fn new(
controller: controller::Project,
init_result: controller::project::InitializationResult,
view: view::project::View,
) -> Self {
let graph_controller = init_result.main_graph;
let graph = presenter::Graph::new(graph_controller, view.graph().clone_ref());
let model = Model { controller, view, graph };
Self { model: Rc::new(model) }
}
/// Initialize project and return working presenter.
///
/// This calls the [`controller::Project::initialize`] method and use the initialization result
/// to construct working presenter.
pub async fn initialize(
controller: controller::Project,
view: view::project::View,
) -> FallibleResult<Self> {
let init_result = controller.initialize().await?;
Ok(Self::new(controller, init_result, view))
}
}

View File

@ -24,6 +24,7 @@ ide-view-graph-editor = { path = "graph-editor" }
parser = { path = "../language/parser" }
span-tree = { path = "../language/span-tree" }
js-sys = { version = "0.3.28" }
multi-map = { version = "1.3.0" }
nalgebra = { version = "0.26.1", features = ["serde-serialize"] }
ordered-float = { version = "2.7.0" }
serde_json = { version = "1.0" }

View File

@ -857,7 +857,7 @@ pub struct LocalCall {
// === EdgeEndpoint ===
// ==================
#[derive(Clone, CloneRef, Debug, Default)]
#[derive(Clone, CloneRef, Debug, Default, Eq, PartialEq)]
pub struct EdgeEndpoint {
pub node_id: NodeId,
pub port: span_tree::Crumbs,

View File

@ -816,6 +816,7 @@ class Config {
public authentication_enabled: boolean
public email: string
public application_config_url: string
public rust_new_presentation_layer: boolean
static default() {
let config = new Config()
@ -878,6 +879,9 @@ class Config {
this.application_config_url = ok(other.application_config_url)
? tryAsString(other.application_config_url)
: this.application_config_url
this.rust_new_presentation_layer = ok(other.rust_new_presentation_layer)
? tryAsBoolean(other.rust_new_presentation_layer)
: this.rust_new_presentation_layer
}
}

View File

@ -892,6 +892,16 @@ impl Network {
{
self.register(OwnedAllWith8::new(label, t1, t2, t3, t4, t5, t6, t7, t8, f))
}
// === Repeat ===
/// Repeat node listens for input events of type [`usize`] and emits events in number equal to
/// the input event value.
pub fn repeat<T>(&self, label: Label, src: &T) -> Stream<()>
where T: EventOutput<Output = usize> {
self.register(OwnedRepeat::new(label, src))
}
}
@ -3839,3 +3849,45 @@ impl<T1, T2, T3, T4, T5, T6, T7, T8, F> Debug for AllWith8Data<T1, T2, T3, T4, T
write!(f, "AllWith8Data")
}
}
// ==============
// === Repeat ===
// ==============
#[derive(Debug)]
pub struct RepeatData<T> {
#[allow(dead_code)]
/// This is not accessed in this implementation but it needs to be kept so the source struct
/// stays alive at least as long as this struct.
event: T,
// behavior: watch::Ref<T2>,
}
pub type OwnedRepeat<T> = stream::Node<RepeatData<T>>;
pub type Repeat<T> = stream::WeakNode<RepeatData<T>>;
impl<T> HasOutput for RepeatData<T> {
type Output = ();
}
impl<T> OwnedRepeat<T>
where T: EventOutput<Output = usize>
{
/// Constructor.
pub fn new(label: Label, src: &T) -> Self {
let event = src.clone_ref();
let definition = RepeatData { event };
Self::construct_and_connect(label, src, definition)
}
}
impl<T> stream::EventConsumer<usize> for OwnedRepeat<T>
where T: EventOutput<Output = usize>
{
fn on_event(&self, stack: CallStack, event: &Output<T>) {
for _ in 0..*event {
self.emit_event(stack, &())
}
}
}