Skip to content

Commit 5836416

Browse files
authored
Optimize editor performance for node selection, click target bounds, and batched messages (#3162)
* Don't clone messages during batch processing * Improve selected nodes perf and memoize network hash computation * Reuse click target bounding boxes for document bounds * Early terminate computing the connected count * Cleanup
1 parent ad5d8fc commit 5836416

12 files changed

Lines changed: 97 additions & 37 deletions

File tree

editor/src/dispatcher.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,7 +236,7 @@ impl Dispatcher {
236236
}
237237
Message::NoOp => {}
238238
Message::Batched { messages } => {
239-
messages.iter().for_each(|message| self.handle_message(message.to_owned(), false));
239+
messages.into_iter().for_each(|message| self.handle_message(message, false));
240240
}
241241
}
242242

editor/src/messages/portfolio/document/utility_types/document_metadata.rs

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -154,21 +154,7 @@ impl DocumentMetadata {
154154
pub fn bounding_box_with_transform(&self, layer: LayerNodeIdentifier, transform: DAffine2) -> Option<[DVec2; 2]> {
155155
self.click_targets(layer)?
156156
.iter()
157-
.filter_map(|click_target| match click_target.target_type() {
158-
ClickTargetType::Subpath(subpath) => subpath.bounding_box_with_transform(transform),
159-
ClickTargetType::FreePoint(_) => click_target.bounding_box_with_transform(transform),
160-
})
161-
.reduce(Quad::combine_bounds)
162-
}
163-
164-
/// Get the loose bounding box of the click target of the specified layer in the specified transform space
165-
pub fn loose_bounding_box_with_transform(&self, layer: LayerNodeIdentifier, transform: DAffine2) -> Option<[DVec2; 2]> {
166-
self.click_targets(layer)?
167-
.iter()
168-
.filter_map(|click_target| match click_target.target_type() {
169-
ClickTargetType::Subpath(subpath) => subpath.loose_bounding_box_with_transform(transform),
170-
ClickTargetType::FreePoint(_) => click_target.bounding_box_with_transform(transform),
171-
})
157+
.filter_map(|click_target| click_target.bounding_box_with_transform(transform))
172158
.reduce(Quad::combine_bounds)
173159
}
174160

editor/src/messages/portfolio/document/utility_types/network_interface.rs

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
mod deserialization;
2+
mod memo_network;
23

34
use super::document_metadata::{DocumentMetadata, LayerNodeIdentifier, NodeRelations};
45
use super::misc::PTZ;
@@ -26,6 +27,7 @@ use graphene_std::vector::{PointId, Vector, VectorModificationType};
2627
use interpreted_executor::dynamic_executor::ResolvedDocumentNodeTypes;
2728
use interpreted_executor::node_registry::NODE_REGISTRY;
2829
use kurbo::BezPath;
30+
use memo_network::MemoNetwork;
2931
use serde_json::{Value, json};
3032
use std::collections::{HashMap, HashSet, VecDeque};
3133
use std::hash::{DefaultHasher, Hash, Hasher};
@@ -36,7 +38,7 @@ use std::ops::Deref;
3638
pub struct NodeNetworkInterface {
3739
/// The node graph that generates this document's artwork. It recursively stores its sub-graphs, so this root graph is the whole snapshot of the document content.
3840
/// A public mutable reference should never be created. It should only be mutated through custom setters which perform the necessary side effects to keep network_metadata in sync
39-
network: NodeNetwork,
41+
network: MemoNetwork,
4042
/// Stores all editor information for a NodeNetwork. Should automatically kept in sync by the setter methods when changes to the document network are made.
4143
network_metadata: NodeNetworkMetadata,
4244
// TODO: Wrap in TransientMetadata Option
@@ -71,7 +73,7 @@ impl PartialEq for NodeNetworkInterface {
7173
impl NodeNetworkInterface {
7274
/// Add DocumentNodePath input to the PathModifyNode protonode
7375
pub fn migrate_path_modify_node(&mut self) {
74-
fix_network(&mut self.network);
76+
fix_network(self.document_network_mut());
7577
fn fix_network(network: &mut NodeNetwork) {
7678
for node in network.nodes.values_mut() {
7779
if let Some(network) = node.implementation.get_network_mut() {
@@ -91,18 +93,25 @@ impl NodeNetworkInterface {
9193
impl NodeNetworkInterface {
9294
/// Gets the network of the root document
9395
pub fn document_network(&self) -> &NodeNetwork {
94-
&self.network
96+
self.network.network()
97+
}
98+
pub fn document_network_mut(&mut self) -> &mut NodeNetwork {
99+
self.network.network_mut()
95100
}
96101

97102
/// Gets the nested network based on network_path
98103
pub fn nested_network(&self, network_path: &[NodeId]) -> Option<&NodeNetwork> {
99-
let Some(network) = self.network.nested_network(network_path) else {
104+
let Some(network) = self.document_network().nested_network(network_path) else {
100105
log::error!("Could not get nested network with path {network_path:?} in NodeNetworkInterface::network");
101106
return None;
102107
};
103108
Some(network)
104109
}
105110

111+
pub fn network_hash(&self) -> u64 {
112+
self.network.current_hash()
113+
}
114+
106115
/// Get the specified document node in the nested network based on node_id and network_path
107116
pub fn document_node(&self, node_id: &NodeId, network_path: &[NodeId]) -> Option<&DocumentNode> {
108117
let network = self.nested_network(network_path)?;
@@ -161,7 +170,7 @@ impl NodeNetworkInterface {
161170
.back()
162171
.cloned()
163172
.unwrap_or_default()
164-
.filtered_selected_nodes(network_metadata.persistent_metadata.node_metadata.keys().cloned().collect()),
173+
.filtered_selected_nodes(|node_id| network_metadata.persistent_metadata.node_metadata.contains_key(node_id)),
165174
)
166175
}
167176

@@ -1556,7 +1565,7 @@ impl NodeNetworkInterface {
15561565
log::error!("Could not get network or network_metadata in upstream_flow_back_from_nodes");
15571566
return FlowIter {
15581567
stack: Vec::new(),
1559-
network: &self.network,
1568+
network: &self.document_network(),
15601569
network_metadata: &self.network_metadata,
15611570
flow_type: FlowType::UpstreamFlow,
15621571
};
@@ -1708,7 +1717,7 @@ impl NodeNetworkInterface {
17081717
}
17091718
}
17101719
Self {
1711-
network: node_network,
1720+
network: MemoNetwork::new(node_network),
17121721
network_metadata,
17131722
document_metadata: DocumentMetadata::default(),
17141723
resolved_types: ResolvedDocumentNodeTypes::default(),
@@ -1744,7 +1753,7 @@ fn random_protonode_implementation(protonode: &graph_craft::ProtoNodeIdentifier)
17441753
// Private mutable getters for use within the network interface
17451754
impl NodeNetworkInterface {
17461755
fn network_mut(&mut self, network_path: &[NodeId]) -> Option<&mut NodeNetwork> {
1747-
self.network.nested_network_mut(network_path)
1756+
self.document_network_mut().nested_network_mut(network_path)
17481757
}
17491758

17501759
fn network_metadata_mut(&mut self, network_path: &[NodeId]) -> Option<&mut NodeNetworkMetadata> {
@@ -3497,8 +3506,7 @@ impl NodeNetworkInterface {
34973506
}
34983507

34993508
self.document_metadata
3500-
.click_targets
3501-
.get(&layer)
3509+
.click_targets(layer)
35023510
.map(|click| click.iter().map(ClickTarget::target_type))
35033511
.map(|target_types| Vector::from_target_types(target_types, true))
35043512
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
use graph_craft::document::NodeNetwork;
2+
use std::cell::Cell;
3+
use std::hash::{Hash, Hasher};
4+
5+
#[derive(Debug, Default, Clone, PartialEq)]
6+
pub struct MemoNetwork {
7+
network: NodeNetwork,
8+
hash_code: Cell<Option<u64>>,
9+
}
10+
11+
impl<'de> serde::Deserialize<'de> for MemoNetwork {
12+
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
13+
where
14+
D: serde::Deserializer<'de>,
15+
{
16+
Ok(Self::new(NodeNetwork::deserialize(deserializer)?))
17+
}
18+
}
19+
20+
impl serde::Serialize for MemoNetwork {
21+
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
22+
where
23+
S: serde::Serializer,
24+
{
25+
self.network.serialize(serializer)
26+
}
27+
}
28+
29+
impl Hash for MemoNetwork {
30+
fn hash<H: Hasher>(&self, state: &mut H) {
31+
self.current_hash().hash(state);
32+
}
33+
}
34+
35+
impl MemoNetwork {
36+
pub fn network(&self) -> &NodeNetwork {
37+
&self.network
38+
}
39+
40+
pub fn network_mut(&mut self) -> &mut NodeNetwork {
41+
self.hash_code.set(None);
42+
&mut self.network
43+
}
44+
45+
pub fn new(network: NodeNetwork) -> Self {
46+
Self { network, hash_code: None.into() }
47+
}
48+
49+
pub fn current_hash(&self) -> u64 {
50+
let mut hash_code = self.hash_code.get();
51+
if hash_code.is_none() {
52+
hash_code = Some(self.network.current_hash());
53+
self.hash_code.set(hash_code);
54+
}
55+
hash_code.unwrap()
56+
}
57+
}

editor/src/messages/portfolio/document/utility_types/nodes.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,8 +167,8 @@ impl SelectedNodes {
167167
std::mem::replace(&mut self.0, new)
168168
}
169169

170-
pub fn filtered_selected_nodes(&self, node_ids: std::collections::HashSet<NodeId>) -> SelectedNodes {
171-
SelectedNodes(self.0.iter().filter(|node_id| node_ids.contains(node_id)).cloned().collect())
170+
pub fn filtered_selected_nodes(&self, filter: impl Fn(&NodeId) -> bool) -> SelectedNodes {
171+
SelectedNodes(self.0.iter().copied().filter(filter).collect())
172172
}
173173
}
174174

editor/src/messages/tool/common_functionality/snapping.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ impl SnapManager {
333333
return;
334334
}
335335
// We use a loose bounding box here since these are potential candidates which will be filtered later anyway
336-
let Some(bounds) = document.metadata().loose_bounding_box_with_transform(layer, DAffine2::IDENTITY) else {
336+
let Some(bounds) = document.metadata().bounding_box_with_transform(layer, DAffine2::IDENTITY) else {
337337
return;
338338
};
339339
let layer_bounds = document.metadata().transform_to_document(layer) * Quad::from_box(bounds);

editor/src/node_graph_executor.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ impl NodeGraphExecutor {
115115

116116
/// Update the cached network if necessary.
117117
fn update_node_graph(&mut self, document: &mut DocumentMessageHandler, node_to_inspect: Option<NodeId>, ignore_hash: bool) -> Result<(), String> {
118-
let network_hash = document.network_interface.document_network().current_hash();
118+
let network_hash = document.network_interface.network_hash();
119119
// Refresh the graph when it changes or the inspect node changes
120120
if network_hash != self.node_graph_hash || self.previous_node_to_inspect != node_to_inspect || ignore_hash {
121121
let network = document.network_interface.document_network().clone();

node-graph/gcore/src/vector/click_target.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ pub struct ClickTarget {
4040

4141
impl ClickTarget {
4242
pub fn new_with_subpath(subpath: Subpath<PointId>, stroke_width: f64) -> Self {
43-
let bounding_box = subpath.loose_bounding_box();
43+
let bounding_box = subpath.bounding_box();
4444
Self {
4545
target_type: ClickTargetType::Subpath(subpath),
4646
stroke_width,

node-graph/gcore/src/vector/vector_attributes.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,11 @@ impl SegmentDomain {
426426
self.all_connected(point).count()
427427
}
428428

429+
/// Enumerate the number of segments connected to a point. If a segment starts and ends at a point then it is counted twice.
430+
pub(crate) fn any_connected(&self, point: usize) -> bool {
431+
self.all_connected(point).next().is_some()
432+
}
433+
429434
/// Iterates over segments in the domain.
430435
///
431436
/// Tuple is: (id, start point, end point, handles)

node-graph/gcore/src/vector/vector_types.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,11 @@ impl Vector {
322322
self.point_domain.resolve_id(point).map_or(0, |point| self.segment_domain.connected_count(point))
323323
}
324324

325+
/// Enumerate the number of segments connected to a point. If a segment starts and ends at a point then it is counted twice.
326+
pub fn any_connected(&self, point: PointId) -> bool {
327+
self.point_domain.resolve_id(point).is_some_and(|point| self.segment_domain.any_connected(point))
328+
}
329+
325330
pub fn check_point_inside_shape(&self, transform: DAffine2, point: DVec2) -> bool {
326331
let number = self
327332
.stroke_bezpath_iter()

0 commit comments

Comments
 (0)