diff --git a/src/DynamoCoreWpf/ViewModels/Core/AnnotationViewModel.cs b/src/DynamoCoreWpf/ViewModels/Core/AnnotationViewModel.cs index 6d8f87c6fee..f13b1601173 100644 --- a/src/DynamoCoreWpf/ViewModels/Core/AnnotationViewModel.cs +++ b/src/DynamoCoreWpf/ViewModels/Core/AnnotationViewModel.cs @@ -959,10 +959,7 @@ private void CollapseConnectors() foreach (var connector in connectorsToHide) { - var connectorViewModel = WorkspaceViewModel - .Connectors - .Where(x => connector.GUID == x.ConnectorModel.GUID) - .FirstOrDefault(); + var connectorViewModel = WorkspaceViewModel.FindConnector(connector.GUID); connectorViewModel.IsCollapsed = true; } @@ -977,10 +974,7 @@ private void RedrawConnectors() foreach (var connector in allNodes.SelectMany(x => x.AllConnectors)) { - var connectorViewModel = WorkspaceViewModel - .Connectors - .Where(x => connector.GUID == x.ConnectorModel.GUID) - .FirstOrDefault(); + var connectorViewModel = WorkspaceViewModel.FindConnector(connector.GUID); connectorViewModel.Redraw(); connector.Start.Owner.ReportPosition(); @@ -1031,14 +1025,11 @@ private void UpdateConnectorsAndPortsOnShowContents(IEnumerable nodes { foreach (var nodeModel in nodes.OfType()) { - var connectorGuids = nodeModel.AllConnectors - .Select(x => x.GUID); + var connectorViewModels = nodeModel.AllConnectors + .Select(x => WorkspaceViewModel.FindConnector(x.GUID)) + .Where(c => c != null); - var connectorViewModels = WorkspaceViewModel.Connectors - .Where(x => connectorGuids.Contains(x.ConnectorModel.GUID)) - .ToList(); - - connectorViewModels.ForEach(x => x.IsCollapsed = false); + foreach(var c in connectorViewModels) c.IsCollapsed = false; // Set IsProxyPort back to false when the group is expanded. nodeModel.InPorts.ToList().ForEach(x => x.IsProxyPort = false); diff --git a/src/DynamoCoreWpf/ViewModels/Core/ConnectorViewModel.cs b/src/DynamoCoreWpf/ViewModels/Core/ConnectorViewModel.cs index cf6e719d70c..55cd4f80c04 100644 --- a/src/DynamoCoreWpf/ViewModels/Core/ConnectorViewModel.cs +++ b/src/DynamoCoreWpf/ViewModels/Core/ConnectorViewModel.cs @@ -483,7 +483,7 @@ public NodeViewModel Nodevm { get { - return workspaceViewModel.Nodes?.FirstOrDefault(x => x.NodeLogic.GUID == model.Start.Owner.GUID); + return workspaceViewModel.FindNode(model.Start.Owner.GUID); } } @@ -491,7 +491,7 @@ public NodeViewModel NodeEnd { get { - return workspaceViewModel.Nodes?.FirstOrDefault(x => x.NodeLogic.GUID == model.End.Owner.GUID); + return workspaceViewModel.FindNode(model.End.Owner.GUID); } } diff --git a/src/DynamoCoreWpf/ViewModels/Core/HomeWorkspaceViewModel.cs b/src/DynamoCoreWpf/ViewModels/Core/HomeWorkspaceViewModel.cs index 972e1ddf214..6823f101045 100644 --- a/src/DynamoCoreWpf/ViewModels/Core/HomeWorkspaceViewModel.cs +++ b/src/DynamoCoreWpf/ViewModels/Core/HomeWorkspaceViewModel.cs @@ -232,8 +232,7 @@ private void UpdateNodesDeltaState(List nodeGuids, bool graphExecuted) foreach (Guid t in nodeGuids) { - var nodeViewModel = Nodes.FirstOrDefault(x => x.NodeModel.GUID == t); - if (nodeViewModel != null) + if (FindNode(t) is NodeViewModel nodeViewModel) { nodeViewModel.ShowExecutionPreview = nodeViewModel.DynamoViewModel.ShowRunPreview; nodeViewModel.IsNodeAddedRecently = false; @@ -358,7 +357,7 @@ private void UpdateNodeInfoBubbleContent(EvaluationCompletedEventArgs evalargs) foreach (var messageID in evalargs.MessageKeys) { - var node = Nodes.FirstOrDefault(n => n.Id == messageID); + var node = FindNode(messageID); if (node == null) continue; node.UpdateBubbleContent(); diff --git a/src/DynamoCoreWpf/ViewModels/Core/NodeViewModel.cs b/src/DynamoCoreWpf/ViewModels/Core/NodeViewModel.cs index e45034ac7e0..c43d2cb48b1 100644 --- a/src/DynamoCoreWpf/ViewModels/Core/NodeViewModel.cs +++ b/src/DynamoCoreWpf/ViewModels/Core/NodeViewModel.cs @@ -1970,7 +1970,7 @@ private void RaisePropertyChangedOnDownStreamNodes() foreach (var inode in nodes) { - var current = this.WorkspaceViewModel.Nodes.FirstOrDefault(x => x.NodeLogic == inode); + var current = WorkspaceViewModel.FindNode(inode.GUID); if (current != null) { current.RaisePropertyChanged("IsFrozen"); diff --git a/src/DynamoCoreWpf/ViewModels/Core/NoteViewModel.cs b/src/DynamoCoreWpf/ViewModels/Core/NoteViewModel.cs index 1a3bfb86bb7..128ea76ab9d 100644 --- a/src/DynamoCoreWpf/ViewModels/Core/NoteViewModel.cs +++ b/src/DynamoCoreWpf/ViewModels/Core/NoteViewModel.cs @@ -136,9 +136,7 @@ public NodeViewModel PinnedNode return null; } - return WorkspaceViewModel.Nodes - .Where(x => x.Id == Model.PinnedNode.GUID) - .FirstOrDefault(); + return WorkspaceViewModel.FindNode(Model.PinnedNode.GUID); } } diff --git a/src/DynamoCoreWpf/ViewModels/Core/OutPortViewModel.cs b/src/DynamoCoreWpf/ViewModels/Core/OutPortViewModel.cs index b197d44c550..8721214fdc6 100644 --- a/src/DynamoCoreWpf/ViewModels/Core/OutPortViewModel.cs +++ b/src/DynamoCoreWpf/ViewModels/Core/OutPortViewModel.cs @@ -302,9 +302,9 @@ private void BreakConnections(object parameter) Analytics.TrackEvent(Actions.Break, Categories.ConnectorOperations, port.PortType.ToString(), port.Connectors.Count); for (var i = port.Connectors.Count - 1; i >= 0; i--) { + var portConnectorGuid = port.Connectors[i].GUID; // Attempting to get the relevant ConnectorViewModel via matching GUID - ConnectorViewModel connectorViewModel = node.WorkspaceViewModel.Connectors - .FirstOrDefault(x => x.ConnectorModel.GUID == port.Connectors[i].GUID); + ConnectorViewModel connectorViewModel = node.WorkspaceViewModel.FindConnector(portConnectorGuid); if (connectorViewModel == null) { @@ -325,8 +325,7 @@ private void HideConnections(object parameter) foreach(var connector in port.Connectors) { // Attempting to get the relevant ConnectorViewModel via matching GUID - var connectorViewModel = node.WorkspaceViewModel.Connectors - .FirstOrDefault(x => x.ConnectorModel.GUID == connector.GUID); + var connectorViewModel = node.WorkspaceViewModel.FindConnector(connector.GUID); connectorViewModel?.ShowhideConnectorCommand.Execute(!AreConnectorsHidden); } if (AreConnectorsHidden) diff --git a/src/DynamoCoreWpf/ViewModels/Core/WorkspaceViewModel.cs b/src/DynamoCoreWpf/ViewModels/Core/WorkspaceViewModel.cs index 209a89ea37b..00331d116c6 100644 --- a/src/DynamoCoreWpf/ViewModels/Core/WorkspaceViewModel.cs +++ b/src/DynamoCoreWpf/ViewModels/Core/WorkspaceViewModel.cs @@ -285,11 +285,17 @@ public bool IsCursorForced [JsonIgnore] public CompositeCollection WorkspaceElements { get; } = new CompositeCollection(); + [JsonIgnore] public ObservableCollection Connectors { get; } = new ObservableCollection(); + private Dictionary connectorDict = new(); + [JsonProperty("NodeViews")] public ObservableCollection Nodes { get; } = new ObservableCollection(); + + private Dictionary nodeDict = new(); + // Do not serialize notes, they will be converted to annotations during serialization [JsonIgnore] public ObservableCollection Notes { get; } = new ObservableCollection(); @@ -713,9 +719,11 @@ public override void Dispose() Connectors.ToList().ForEach(connectorViewmModel => connectorViewmModel.Dispose()); Annotations.ToList().ForEach(AnnotationViewModel => AnnotationViewModel.Dispose()); Nodes.Clear(); + nodeDict.Clear(); Notes.Clear(); Pins.Clear(); Connectors.Clear(); + connectorDict.Clear(); Errors.Clear(); Annotations.Clear(); InCanvasSearchViewModel?.Dispose(); @@ -860,15 +868,18 @@ void CopyPasteChanged(object sender, EventArgs e) void Connectors_ConnectorAdded(ConnectorModel c) { - var viewModel = new ConnectorViewModel(this, c); - if (Connectors.All(x => x.ConnectorModel != c)) + if (!connectorDict.ContainsKey(c.GUID)) + { + // Only create the view model if needed. This prevents event handler leaks + var viewModel = new ConnectorViewModel(this, c); + connectorDict[c.GUID] = viewModel; Connectors.Add(viewModel); + } } void Connectors_ConnectorDeleted(ConnectorModel c) { - var connector = Connectors.FirstOrDefault(x => x.ConnectorModel == c); - if (connector != null) + if (connectorDict.Remove(c.GUID, out var connector)) { Connectors.Remove(connector); connector.Dispose(); @@ -930,6 +941,7 @@ void Model_NodesCleared() nodeViewModel.Dispose(); } Nodes.Clear(); + nodeDict.Clear(); } Errors.Clear(); @@ -948,7 +960,7 @@ void Model_NodeRemoved(NodeModel node) NodeViewModel nodeViewModel; lock (Nodes) { - nodeViewModel = Nodes.First(x => x.NodeLogic == node); + nodeDict.Remove(node.GUID, out nodeViewModel); if (nodeViewModel.ErrorBubble != null) Errors.Remove(nodeViewModel.ErrorBubble); Nodes.Remove(nodeViewModel); @@ -970,6 +982,7 @@ void Model_NodeAdded(NodeModel node) lock (Nodes) { Nodes.Add(nodeViewModel); + nodeDict[node.GUID] = nodeViewModel; } if (nodeViewModel.ErrorBubble != null) Errors.Add(nodeViewModel.ErrorBubble); @@ -979,6 +992,20 @@ void Model_NodeAdded(NodeModel node) SetStopNodeZoomAnimationBehavior(zoomAnimationThresholdFeatureFlagVal); } + internal NodeViewModel? FindNode(Guid nodeGuid) + { + return nodeDict.TryGetValue(nodeGuid, out var nodeModel) + ? nodeModel + : null; + } + + internal ConnectorViewModel? FindConnector(Guid connectorGuid) + { + return connectorDict.TryGetValue(connectorGuid, out var connectorModel) + ? connectorModel + : null; + } + void PostNodeChangeActions() { if (RunSettingsViewModel == null) return; @@ -1896,10 +1923,10 @@ private void RefreshViewOnSelectionChange(object sender, NotifyCollectionChanged /// Found object. internal ViewModelBase GetViewModelInternal(Guid modelGuid) { - ViewModelBase foundModel = (Connectors.FirstOrDefault(c => c.ConnectorModel.GUID == modelGuid) - ?? Nodes.FirstOrDefault(node => node.NodeModel.GUID == modelGuid) as ViewModelBase) - ?? (Notes.FirstOrDefault(note => note.Model.GUID == modelGuid) - ?? Annotations.FirstOrDefault(annotation => annotation.AnnotationModel.GUID == modelGuid) as ViewModelBase); + ViewModelBase foundModel = FindConnector(modelGuid) as ViewModelBase + ?? FindNode(modelGuid) as ViewModelBase + ?? Notes.FirstOrDefault(note => note.Model.GUID == modelGuid) as ViewModelBase + ?? Annotations.FirstOrDefault(annotation => annotation.AnnotationModel.GUID == modelGuid) as ViewModelBase; return foundModel; }